I'm trying to make an infinite scroll (without jQuery) to show more results in a page. I'm using an IntersectionObserver to detect a div called #paginate and everytime it enters the screen, the #result div will be refreshed.
var result = document.querySelector('#result');
var paginate = document.querySelector('#paginate');
var observer = new IntersectionObserver(entries => {
if (entries.some(entry => entry.isIntersecting))
{
var pagination = 10;
fetch('/kernel/search.php?pagination='+pagination)
.then((response) => {
return response.text();
})
.then((html) => {
result.innerHTML = html;
});
}
});
observer.observe(paginate);
Here's the full code view with HTML:
<html>
<body>
<div class="row justify-content-sm-center justify-content-md-center justify-content-lg-center justify-content-xl-start no-gutters min-vw-100" id="result">
<div class="col-sm-11 col-md-11 col-lg-9-result col-xl-4-result order-0">
<div class="card mx-3 mt-3">
<div class="card-body">
<a class="text-decoration-none" href="?topic=result-1">
<h5 class="card-title">
Result 1
</h5>
</a>
<p class="card-text text-truncate">
Result 1 description.</p>
</div>
</div>
<div class="card mx-3 mt-3">
<div class="card-body">
<a class="text-decoration-none" href="?topic=result-2">
<h5 class="card-title">
Result 2
</h5>
</a>
<p class="card-text text-truncate">
Result 2 description.</p>
</div>
</div>
<div class="alert alert-light text-dark text-center border mx-3 my-3" id="paginate">
More results
</div>
</div>
</div>
<script>
var result = document.querySelector('#result');
var paginate = document.querySelector('#paginate');
var observer = new IntersectionObserver(entries => {
if (entries.some(entry => entry.isIntersecting))
{
var pagination = 10;
fetch('/kernel/search.php?pagination='+pagination)
.then((response) => {
return response.text();
})
.then((html) => {
result.innerHTML = html;
});
}
});
observer.observe(paginate);
</script>
</body>
</html>
It works, but it only works the first time and it doesn't refresh the #result div thereafter. I can see the fetch working in Web Browser > Inspect > Network tab, but there's no activity after the first refresh of the #result div meaning it doesn't detect the #paginate div anymore.
What's going on here? I assume it's because that I'm using an innerHTML and the observer somehow can't detect the #paginate div after the first refresh of the #result div. How can I solve this?
I did it with jQuery and .scroll function and used ajax like this, maybe my code can help you and adapt it to your needs.
$('#customersList').scroll(function () {
if ($(this).scrollTop() + $(this).innerHeight() >= $(this)[0].scrollHeight - 5000) {
// Do your stuff here.
getCustomers();
}
})
function getCustomers(){
let $customerList = $('#customersList');
let offset = ($customerList.attr('data-offset')) ?
$customerList.attr('data-offset') : 0;
if ($customerList.data('requestRunning')) {
return;
}
$customerList.data('requestRunning', true);
return $.ajax({
type: "GET",
data: {offset: offset},
url : routes.routes.customers.get
})
.done(function (data) {
let _htmlCustomersList = ($customerList.is(':empty')) ? '' : $customerList.html();
let response = data.data;
if (response) {
for (const i in response) {
let v = JSON.parse(response[i]);
_htmlCustomersList += '<div class="client-group edit" data-id="' + v['id'] + '"></div><hr>';
}
offset = parseInt(offset) + 200;
$customerList.attr('data-offset', offset).html(_htmlCustomersList);
}
})
.always(function () {
$customerList.data('requestRunning', false);
});
}
my getCustomer function runs before reaching the end of the page and loads 200 more items each time.
I hope this can help you a little bit
It seems you are removing #paginate after the first update, because it is in the #result.
Please, use indentation :)
The #result div is the main wrapper of the content
using innerHTML to update the contents of the div, will result in replacing the entire content inside of the div...
Beyond the fact that innerHTML is absolute, and erases any DOM objects (and their events) hence bad practice, it's not that good solution either, since you'd like to append rather then replace the data, upon scrolling
I would suggest to add a div above the paginate, which will hold the added data, something like:
...
<div id="result"></div>
<div class="alert alert-light text-dark text-center border mx-3 my-3" id="paginate">
More results
</div>
Then use some sort of appending, for the content added
so something like:
.then((html) => {
let res = new DOMParser().parseFromString(html, "text/xml");
document.getElementById("result").appendChild(res);
});
Hope that helps
I have removed innerHTML and replaced it with insertAdjacentHTML.
Because innerHTML seems to reset/forget #paginate after the first refresh of the #result, so the observer can't detect #paginate anymore to trigger the next #result refresh.
I could have used innerHTML with appendChild to do the same but appendChild adds a new div on each #result refresh, which I didn't really like.
As the solution, I'm inserting the refreshed html data before the beginning of #paginate without resetting/forgetting it's element id that's required for the observer to trigger the next #result refresh.
.then((html) => {
paginate.insertAdjacentHTML('beforebegin', html);
});
Related
I tried to make this function getWeekly() run by default when the site first loads but it only runs this part of the code:
dailyBtn.classList.add("active");
weeklyBtn.classList.remove("active");
monthlyBtn.classList.remove("active");
but not the loop under. But it'll show data when I click on the tags. Any ideas? Thanks.
Git link: https://github.com/thusmiley/time-tracking-dashboard.git
Live site link: https://thusmiley.github.io/time-tracking-dashboard
index.html
<div class="report-bottom">
Daily
Weekly
Monthly
</div>
</div>
<div class="stat-wrapper">
<div class="work-bg bg"></div>
<div class="stat" id="work">
<div class="category">
<h2>Work</h2>
<img src="./images/icon-ellipsis.svg" alt="" />
</div>
<div class="card">
<h3 class="work-current"></h3>
<p class="work-previous"></p>
</div>
</div>
</div>
script.js
let Data = [];
fetch("./data.json")
.then((response) => response.json())
.then((data) => Data.push(...data));
let card = document.querySelectorAll(".card");
let dailyBtn = document.getElementById("daily");
let weeklyBtn = document.getElementById("weekly");
let monthlyBtn = document.getElementById("monthly");
function getDaily() {... }
function getWeekly() {
dailyBtn.classList.remove("active");
weeklyBtn.classList.add("active");
monthlyBtn.classList.remove("active");
for (let i = 0; i < Data.length; i++) {
let splitTitle = Data[i].title.split("");
splitTitle = splitTitle.filter((e) => String(e).trim());
let joinTitle = splitTitle.join("");
let current = document.querySelector(`.${joinTitle.toLowerCase()}-current`);
let previous = document.querySelector(
`.${joinTitle.toLowerCase()}-previous`
);
current.innerHTML = `${Data[i].timeframes.weekly.current + "hrs"}`;
previous.innerHTML = `${
"Last Week - " + Data[i].timeframes.weekly.previous + "hrs"
}`;
}
}
function getMonthly() {... }
window.onload = getWeekly();
The very first time that you load the page Data.length is equal to 0, and that's why the loop doesn't iterate. You are using an asynchronous call to load Data, and when getWeekly() is called for the first time, Data is not ready with the info yet (and it only works after when its ready).
You should wait until Data is completely load first, you can try a callback function or even try $.when() using jquery.
I'm trying to make a some dynamically generated bootstrap cards with a button clearing the page and showing the info (which I have stored on a Firebase project). I managed to make the cards show properly but I'm blocked and can't find a way to make each button show a different information.
So basically I have this:
Cards
Content showing
It has to be something close to the eventListener or another way to write the code... I think the way I did it is why its showing all the information on the same card, but how can I write the code right? I'm trying for 2 days and can't work around it.
OBS: I know my code can be a little messy or could be best ways to do some stuff but I'm still learning.
import { getWikiTI } from './prevenirWiki.js'
const dataCard = document.getElementById('data');
async function loadData() {
let i = 0;
const data = await getWikiTI()
dataCard.innerHTML = data.map(d => `
<div class="card border-primary mb-3 fb-item" style="max-width: 20rem;">
<div class="card-header">${d.setor}</div>
<div class="card-body">
<h4 class="card-title">${d.descricao}</h4>
<button type="button" class="btn btn-lg btn-primary fb-item" id="carregar-card">Carregar</button>
</div>
</div>
`,
console.log(data),
console.log(i)
).join('')
const btnCarregar = document.getElementById('carregar-card')
btnCarregar.addEventListener('click', () => {
dataCard.innerHTML = data.map(function (d) {
return `${d.conteudo}`;
})
document.body.appendChild(dataCard)
})
}
document.onload = loadData();
EDIT: Alright ! I managed to get it working... not the best code in the world but it's something :) If anyone has any opinions or ideias on how to improve it, im all ears.
import { getWikiTI } from './prevenirWiki.js'
const dataCard = document.getElementById('data');
async function loadData() {
let i = 0;
const data = await getWikiTI()
dataCard.innerHTML = data.map(d => `
<div class="card border-primary mb-3 fb-item" style="max-width: 20rem;">
<div class="card-header">${d.setor}</div>
<div class="card-body">
<h4 class="card-title">${d.descricao}</h4>
<button type="button" class="btn btn-lg btn-primary fb-item" id="carregar-card${i++}">Carregar</button>
<p hidden class="card-text">${d.conteudo}</p>
</div>
</div>
`,
).join('')
for (let i = 0; i <= data.length; i++) {
let btnCarregar = document.getElementById(`carregar-card${i}`)
btnCarregar.addEventListener('click', () => {
dataCard.innerHTML = data[i].conteudo;
document.body.appendChild(dataCard)
})
}
}
document.onload = loadData();
I have a HTML/CSS/JS script that is allowing me to rotate text for a certain part of it. I'm just having the problem of making it work for multiple sections as the script targets a span[data-up] & span[data-show].
Any help appreciated, code shown below.
<section class="rotating-text-section">
<h2>
We educate by
<div class="wrapper">
<span data-up>teaching.</span>
<span data-show>showing.</span>
<span>doing.</span>
<span>repeating.</span>
</div>
</h2>
</section>
setInterval(() => {
const up = document.querySelector('span[data-up]');
const show = document.querySelector('span[data-show]');
const down = show.nextElementSibling || document.querySelector('span:first-child');
up.removeAttribute('data-up');
show.removeAttribute('data-show');
show.setAttribute('data-up', '');
down.setAttribute('data-show', '');
}, 2000);
I just changed the class names of each span element and targeted them by this in the JavaScript script.
My second snippet of HTML was:
<div class="wrapper-two">
<span class="second-span" data-up>technology.</span>
<span class="second-span" data-show>experience.</span>
<span class="second-span">listening.</span>
<span class="second-span">experimenting.</span>
</div>
My second JS script is as follows:
setInterval(() => {
const up = document.querySelector('.second-span[data-up]');
const show = document.querySelector('.second-span[data-show]');
const down = show.nextElementSibling ||
document.querySelector('.second-span:first-child');
up.removeAttribute('data-up');
show.removeAttribute('data-show');
show.setAttribute('data-up', '');
down.setAttribute('data-show', '');
}, 1000);
I am trying to dynamically load a bunch of posts from a API and then implement a like button for each of them.
function load_allposts(){
fetch("/posts")
.then(response => response.json())
.then(posts => {
var enc = document.createElement('div');
enc.className = "post-enc";
let s = ``;
posts.forEach(element => {
s += `<div class="p-container">
<div>
<button type="button" class="btn btn-link" class="profile-btn" data-id=${element[0].author_id}> ${element[0].author_name} </button>
</div>
<div class="post-body">
${element[0].body}
</div>
<div class="p1">
<span class="like-status">${element[0].likes}</span> people like this
<button class="like-btn">${element[1]}</button>
</div>
<div class="post-time">
${element[0].timestamp}
</div>
</div>`;
});
enc.innerHTML = s;
document.querySelector('#all-posts').appendChild(enc);
});
}
I would to like to modify the <span class="like-status"> element when I click the <button class="like-btn">. The only way that I can think of to get a reference to <span class="like-status"> is by adding a ID to it by implementing some kind of counter, which I feel is more like a hack rather than real solution.
I tried googling but almost all solutions involved JQuery, which I am not familiar with. Any help would be appreciated.
You can use delegate event binding document.addEventListener('click', function(event) { to trigger click event for dynamically added button.
It will raise click on every element inside document you need to find if it is one which you expect with event.target.matches('button.like-btn').
Then you can find your span with getting parent and then finding span.like-status using querySelector.
Try it below. For demo modified load_allposts. You do not need to do any change in it.
load_allposts();
document.addEventListener('click', function(event) {
if (event.target.matches('button.like-btn')) {
let span = event.target.parentElement.querySelector('span.like-status');
span.innerText = 'Modified';
}
});
function load_allposts() {
let posts = [1]
var enc = document.createElement('div');
enc.className = "post-enc";
let s = ``;
posts.forEach(element => {
s += `<div class="p-container">
<div>
<button type="button" class="btn btn-link" class="profile-btn" data-id=element[0].author_id> element[0].author_name </button>
</div>
<div class="post-body">
element[0].body
</div>
<div class="p1">
<span class="like-status">element[0].likes</span> people like this
<button class="like-btn">element[1]</button>
</div>
<div class="post-time">
element[0].timestamp
</div>
</div>`;
});
enc.innerHTML = s;
document.querySelector('#all-posts').appendChild(enc);
}
<div id='all-posts'>
</div>
Note event delegation have extra overhead so alternatively you can use below code.
Here added two functions added as below and added one line bindClickEvent(enc); at end of load_allposts function.
likeClick - perform custom logic to update span.like-status
bindClickEvent - bind click event to all button.like-btn inside div
Call bindClickEvent(enc); at end of load_allposts function.
Try it below.
load_allposts();
// perform custom logic to update span.like-status
function likeClick(event) {
// querySelector will return first matching element
let span = event.target.parentElement.querySelector('span.like-status');
span.innerText = 'Modified';
}
// bind click event to all button.like-btn inside div
function bindClickEvent(enc) {
// querySelectorAll will return array of all matching elements
let buttons = enc.querySelectorAll('button.like-btn');
// loop over each button and assign click function
for (let i = 0; i < buttons.length; i++) {
buttons[i].onclick = likeClick;
}
}
function load_allposts() {
let posts = [1]
var enc = document.createElement('div');
enc.className = "post-enc";
let s = ``;
posts.forEach(element => {
s += `<div class="p-container">
<div>
<button type="button" class="btn btn-link" class="profile-btn" data-id=element[0].author_id> element[0].author_name </button>
</div>
<div class="post-body">
element[0].body
</div>
<div class="p1">
<span class="like-status">element[0].likes</span> people like this
<button class="like-btn">element[1]</button>
</div>
<div class="post-time">
element[0].timestamp
</div>
</div>`;
});
enc.innerHTML = s;
document.querySelector('#all-posts').appendChild(enc);
// assign click event to buttons inside enc div.
bindClickEvent(enc);
}
<div id='all-posts'>
</div>
Please help a little bit.
I have a list of 7 events displayed already with Angularjs. I'd like when I click on the <h2> (the event name) of some event, to open an ovelay that displays the same data from the database but only for this event which is clicked.
I'm sure that 'filter' will do the work but it seems I'm doing something wrong.
Here is my code. The ng-app and ng-controller are in the <main> tag.
Angularjs version: 1.7.9
My Html:
<main ng-app="eventsApp" ng-controller="eventsCtrl">
<!-- Overlay that holds and displays a single event -->
<div>
<div ng-repeat="x in singlePageEvent | filter:hasName(x.eventName)">
<div>
<img ng-src="{{x.eventImgSrc}}" alt="{{x.eventImgName}}"/>
<h2 class="event-name">{{x.eventName}}</h2>
<p>{{x.eventTime}}</p>
<p>{{x.eventPlace}}</p>
</div>
</div>
</div>
<!-- A list with all the events -->
<div ng-repeat="x in events">
<div>
<img ng-src="{{x.eventImgSrc}}" alt="{{x.eventImgName}}"/>
<h2 ng-click="singleEventOpen(x)" class="event-name">{{x.eventName}}</h2>
<p>{{x.eventTime}}</p>
<p>{{x.eventPlace}}</p>
</div>
</div>
</main>
My script:
let eventsApp = angular.module('eventsApp', []);
this filter below is not working at all. It continues to show all the events.
eventsApp.filter('hasName', function() {
return function(events, evName) {
var filtered = [];
angular.forEach(events, function(ev) {
if (ev.eventName && ev.eventName.indexOf(evName) >-1) {
filtered.push(ev);
}
});
return filtered;
}
});
eventsApp.controller('eventsCtrl', function($scope, $http) {
let x = window.matchMedia("(max-width: 450px)");
let singleEventOverlay = angular.element(document.querySelector('div.single-event.overlay'));
let singleEvent = singleEventOverlay;
function responsiveEventImages(x) { //this displays the list with events
if (x.matches) {
$http.get('./includes/events_res.inc.php').then(function(response) {
$scope.events = response.data.events_data;
});
} else {
$http.get('./includes/events.inc.php').then(function(response) {
$scope.events = response.data.events_data;
});
}
}
...and then by invoking singleEventOpen() the overlay appears, but it displays all the data, not just the clicked event
$scope.singleEventOpen = function(singleEvent) {
let clickedEvent = singleEvent.eventName; //I got the value of each h2 click thanx to #georgeawg but now what?
console.log("Fetching info for ", singleEvent.eventName);
$http.get('./includes/single_event.inc.php').then(function(response) {
$scope.singlePageEvent = response.data.events_data;
});
singleEventOverlay.removeClass('single-event-close').addClass('single-event-open');
}
});
The php file with the database extraction is working fine so I won't display it here.
What should I do to make the overlay display only the event which <h2> is clicked?
Here is a pic of the list with events
Here is a pic of the overlay
Thanx in advance.
EDITED
I got the value of each h2 click thanx to #georgeawg but now what?
UPDATE
Hey, thanx a lot #georgeawg . After many attempts I finally did this:
$scope.singleEventOpen = function(singleEvent) {
$http.get('./includes/single_event.inc.php').then(function(response) {
let allEvents = response.data.events_data;
for (var i = 0; i < allEvents.length; i++) {
singleEvent = allEvents[i];
}
});
console.log('Fetching data for', singleEvent);
$scope.ex = singleEvent;
});
And it works well.
Change the ng-click to pass an argument to the singleEventOpen function:
<div ng-repeat="x in events">
<div>
<img ng-src="{{x.eventImgSrc}}" alt="{{x.eventImgName}}"/>
<h2 ng-click="singleEventOpen(x)" class="event-name">{{x.eventName}}</h2>
<p>{{x.eventTime}}</p>
<p>{{x.eventPlace}}</p>
</div>
</div>
Then use that argument:
$scope.singleEventOpen = function(singleEvent) {
console.log("Fetching info for ", singleEvent.eventName);
//...
//Fetch and filter the data
$scope.ex = "single item data";
}
Adding an argument is the key to knowing which <h2> element was clicked.
Update
Don't use ng-repeat in the overlay, just display the single item:
<!-- Overlay that holds and displays a single event -->
̶<̶d̶i̶v̶ ̶n̶g̶-̶r̶e̶p̶e̶a̶t̶=̶"̶x̶ ̶i̶n̶ ̶s̶i̶n̶g̶l̶e̶P̶a̶g̶e̶E̶v̶e̶n̶t̶ ̶|̶ ̶f̶i̶l̶t̶e̶r̶:̶h̶a̶s̶N̶a̶m̶e̶(̶x̶.̶e̶v̶e̶n̶t̶N̶a̶m̶e̶)̶"̶>̶
<div ng-if="ex"">
<div>
<img ng-src="{{ex.eventImgSrc}}" alt="{{ex.eventImgName}}"/>
<h2 class="event-name">{{ex.eventName}}</h2>
<p>{{ex.eventTime}}</p>
<p>{{ex.eventPlace}}</p>
</div>
</div>