Script that keeps checking if a collection of items is fully loaded - javascript

I have a page with a long list about 880 items (and growing), which are loading in batches. When the window loads the first batch has about 52 items. So I would like to write a function that until the list isn't complete, shows a simple "loading more..." disclaimer on the page and keeps checking with the "allItems" variable until these are fully loaded.
So far I came up with this function, but it just take the first loaded batch of 52 (allItems.length) into account and then of course meets its condition and stops.... (see screenshots) What am I doing wrong?
$(window).on("load", function() {
// get all Items
var calItems = $(".collection-item.w-dyn-item");
// count them
var calCount = calItems.length;
// loaded elements flag
var loadedCount = 0;
function countItems() {
var interval = setInterval(function() {
loadedCount++;
if (loadedCount > calCount) {
$(".load-more-wrapper").hide();
clearInterval(interval);
} else {
loadedCount++;
}
}, 1000);
}
countItems();
});

Just by what code is posted in OP I'm assuming that the page takes too long to load. There's a way to dynamically load a ton of HTML without any human perceptible delay.
get a copy of all of your divs as a string, if it changes have updates stored in localStorage (that is an entirely different problem which warrants it's own question). In the example, I have 999 divs generated as one string:
const htmlString = [...new Array(999)].map((_, idx) => {
`<div class='dyn'>${idx}</div>`).join('')
});
Next, create a document fragment. A document fragment is a "ghost" element, it's not part of the DOM, yet it can contain elements. When it is added to the DOM it disappears and leaves it's children within the DOM. This takes no time at all since it's only a single DOM operation instead of 999 nodes loading.
let frag = new DocumentFragment();
Then create a block element (in this example, it'll be a section). Then render that string (ie htmlString) in the section and add the section to the fragment.
const sec = document.createElement('section');
sec.innerHTML = htmlString;
frag.append(sec);
Finally, collect all of the divs into an array and then add the fragment to the DOM.
const nodeList = [...sec.querySelectorAll('.dyn')];
document.querySelector('main').append(frag);
Go ahead and test if you have a collection
console.log(nodeList[888].textContent);
.as-console-row::after { width: 0; font-size: 0; }
.as-console-row-code { width: 100%; word-break: break-word; }
.as-console-wrapper { max-height: 1.5em !important; max-width: 50%; margin-left: 50%; }
<!DOCTYPE html>
<html lang="en">
<head>
<title></title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<style></style>
</head>
<body>
<main></main>
<script>
const htmlString = [...new Array(999)].map((_, idx) => `<div class='dyn'>${idx}</div>`).join('');
let frag = new DocumentFragment();
const sec = document.createElement('section');
sec.innerHTML = htmlString;
frag.append(sec);
const nodeList = [...sec.querySelectorAll('.dyn')];
document.querySelector('main').append(frag);
console.log(nodeList[888].textContent);
</script>
</body>
</html>

Related

JS: calling functions via buttons does not work anymore, if page is updated without reloading the page

I want to write a program with one html page, which i can update and fill with different elements via javascript with one button that stays the same in every version, which displays a modalBox. I made a very basic version of this: One page, that is filled with two buttons (next and last) for navigating through the pages and one to display the modal. In addition, i added a number, which is incremented oder decremented accordingly, when you click through the updated versions of the page.
var counter = 1;
function setUp(){
var c = document.getElementById("container");
var d = document.createElement("div");
d.setAttribute("id", "main");
d.innerHTML = counter;
var nxt = document.createElement("button");
var bck = document.createElement("button");
var modalBtn = document.createElement("button");
nxt.innerText = ">";
bck.innerText = "<";
modalBtn.innerText="Show Modal";
nxt.setAttribute("onclick","nextPage()");
bck.setAttribute("onclick","lastPage()");
modalBtn.setAttribute("onclick","showModal()");
d.appendChild(bck);
c.appendChild(d);
d.appendChild(nxt);
d.appendChild(modalBtn);
}
function showModal(){
var m = document.getElementById("modal");
m.style.display = "block";
}
function closeModal(){
var m = document.getElementById("modal");
m.style.display = "none";
}
function nextPage(){
var c = document.getElementById("container");
c.innerHTML="";
counter++;
setUp();
}
function lastPage(){
var c = document.getElementById("container");
c.innerHTML="";
counter--;
setUp();
}
setUp();
*{
padding: 0;
margin: 0;
}
#modal{
position: absolute;
width: 500px;
height: 300px;
background-color: black;
display: none;
}
#main{
background-color: aliceblue;
height: 500px;
width: 800px;
}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="tryout.css">
<script src="tryout.js" defer></script>
<title>Document</title>
</head>
<body>
<div id="container">
<div id="modal"><button id="closeButton" onclick="closeModal()">Close</button></div>
</div>
</body>
</html>
The problem is: on onload, the modal button works fine (on click, the modal is displayed). As soon as i update (not reloading!) the page via the next- or back button, the modal button stops working (Error-message says the type of modalbutton is null). I have no clue why, because to my knowledge, the buttons are reinitiated by clicking on the next or back button (because the setUp()-function is called in the functions triggered by the buttons). As soon as I reload the page via the reload-button, it is working until i use one of the next and back buttons.
I am new to js, it's probable that I'm missing sth. obvious here :) Many Thanks!
On the "nextPage" and "lastPage" function , you're just removing the whole content (including the modal) from the div which associated container class.
That's why when you calling the "showModal" function , there is no element with modal id on the DOM. That's why its saying null.
you can follow what Sakil said on the comment or, in your both(nextPage & lastPage) functions,you can just remove the div with id main and add it later on "setUp" function.
I'm adding some code snippet below, hope it will help;
function nextPage() {
//grab the div with "main" id
let main = document.getElementById("main");
//remove it from document
main.remove();
counter++;
//add it again; what you're doing actually.
setUp();
}
function lastPage() {
//grab the div with "main" id
let main = document.getElementById("main");
//remove it from document
main.remove();
counter--;
//add it again; what you're doing actually.
setUp();
}
of course you should refactor the code base, cause there is lot more copy-pasting staff present, but I'm leaving that up to you.

Problem with slideshow in Javascript, images don't load

I've a problem with a slider I've to do for school.
The console return no error, but the images don't show in the slider. I'm on it for four days now and I can't figure out what is the problem, so it seems that I need your lights ! ^^
I used the console to check if "diaporama.js" is working and it is, the console.log at the end of "slider.js" is to check if my image path is ok and it is. I've absolutely no clue of what is going wrong.
Thank you in advance !
Here is my code :
HTML
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta property="og:url" content="" />
<title>Slider</title>
<link rel="stylesheet" href="slide.css">
</head>
<body>
<h1>Test</h1>
<div id="caroussel">
<img src="" alt="diapo1" id="diapo">
<div id="precedent" ><</div>
<div id="suivant" >></div>
</div>
<script src="diaporama.js"></script>
<script src="slide.js"></script>
</body>
</html>
diaporama.js
class Diaporama {
constructor(src, images) {
this.src = src;
this.images = images;
this.position = 0;
this.start();
}
slideLeft() {
if (this.position <= 0) {
this.position = this.images.length - 1;
} else {
this.position--;
}
this.src = this.images[this.position];
}
slideRight() {
if (this.position > this.length-1) {
this.position = 0;
}
else {
this.position++;
}
this.src = this.images[this.position];
}
start() {
this.src = this.images[this.position];
}
}
slide.js
var images = Array('img/caroussel1.png', 'img/caroussel2.jpg', 'img/caroussel3.jpg', 'img/caroussel4.jpg', 'img/caroussel5.jpg');
var src = document.getElementById("diapo").src;
var diaporama = new Diaporama(src, images);
setInterval(function () { diaporama.slideLeft(); }, 5000);
console.log(src);
At a minimum, it looks like one problem is that:
You have an image element with ID diapo in your HTML, and then in the DOM, which has an empty src attribute.
In slide.js, you attempt to create a new instance of the class Diaporama, called diaporama, using the empty src attribute that you have stored in the variable src in slide.js.
Since an img element needs a src attribute with an actual URL, in order to show the image at that URL, you are seeing nothing, since you have not provided a URL (You won't get an error either, since an empty src attribute is perfectly valid HTML, and causes no JS errors)
UPDATE IN RESPONSE TO COMMENTS
The critical issue (or oversight), is that you have:
a carousel element in the index.html file, which is then represented in the DOM (which is what we expect)
an instance of class Diaporama called diaporama in slide.js, which has no link to the DOM carousel that you want it to have: all diaporama has is a String, taken from the src attribute of the DOM carousel, which refers to the URL path of various images. The diaporama instance can never "reach out" and update the DOM carousel, given the code you have written.
Thankfully, the fix is very simple.
As you know, there needs to be link between the DOM and object that you have created; creating such a link is straight-forward and just involves DOM queries.
I have added a solution (I have placed ALL the JS in one file, rather than two files -- as you have -- but this is not important)
class Diaporama {
constructor(imgElem, images) {
this.imgElem = imgElem;
this.images = images;
this.position = 0;
this.start();
}
slideLeft() {
if (this.position <= 0) {
this.position = this.images.length - 1;
} else {
this.position--;
}
// this is part of the 'bridge' between "carousel.js" and the DOM
this.imgElem.src = this.images[this.position];
}
slideRight() {
// there was an error in your original "slideRight" method: a typo and an "off-by-error"
if (this.position >= this.images.length-1) {
this.position = 0;
}
else {
this.position++;
}
// this is part of the 'bridge' between "carousel.js" and the DOM
this.imgElem.src = this.images[this.position];
}
start() {
// this is part of the 'bridge' between "carousel.js" and the DOM
this.imgElem.src = this.images[this.position];
}
}
// prefer an Array literal rather than call to Array -- less verbose, and slightly faster
var images = ['img/one.jpg', 'img/two.jpg', 'img/three.jpg'];
// This is where 'bridge' between "carousel.js" and the DOM is created: we 'cache' a reference to the carousel 'img' element,
// which we will then modify from within the 'carousel' instance of class Diaporama
var imgElem = window.document.getElementById('carousel').querySelector('img');
var carousel = new Diaporama(imgElem, images);
carousel.start();
// create 'delegated' event listener on document, and trigger correct method of 'carousel' in response to user interaction
window.document.addEventListener('click', ev => {
const target = ev.target;
if(target.id === 'back_button') {
carousel.slideLeft();
} else if(target.id === 'next_button') {
carousel.slideRight();
}
});
.carousel {
max-height: 400px;
max-width: 600px;
background: rgb(250,250,200);
overflow: hidden;
}
.button-wrapper {
display: flex;
justify-content: space-around;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Carousel</title>
</head>
<body>
<h1>Carousel slider (OOP)</h1>
<section id="carousel" class="carousel">
<div class="button-wrapper">
<button id="back_button">Back</button>
<button id="next_button">Next</button>
</div>
<img src="" alt="carousel image">
</section>
<script src="carousel.js"></script>
</body>
</html>
I hope this helps answer your question!
If it does, then please mark my answer as the accepted one.
If you still have questions, then let me know, and I will do my best to answer them.

How to create a fade-in and a fade-out loop with jQuery

So I am currently using a fade-in method on my program. However I want it to fade-in, fade-out and repeat the same process over and over again.
What do I need to do that?
$(document).ready(function() {
var $el = $(".intro");
var text = $el.text();
var words = text.split(" ");
var html = "";
for (var i = 0; i < words.length; i++) {
html += "<span>" + words[i] + " </span>";
}
$el
.html(html)
.children()
.hide()
.each(function(i) {
$(this)
.delay(i * 1500)
.fadeIn(1700)
});
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="test.js"></script>
<div class="intro">PREDICT SIMULATE OPTIMIZE</div>
</body>
</html>
Depending on what visual effect you seek (the question is open to interpretation) ...
You can set up two mutualy-triggering custom events on the containing div.
To get the timing right, exploit jQuery's .promise(), which returns a promise that will fulfill when the current animation(s) are complete.
To start the process, just trigger one or other of the custom events.
$(document).ready(function() {
var $el = $('.intro');
var $words = $el.html($el.text().split(' ').map(word => `<span>${word} </span>`).join('')).children().hide();
$el.on('fadeIn', function() { // custom event
$.when(...$words.map(function(i, word) => $(word).delay(i * 1500).fadeIn(1700).promise()))
.then(function() {
$el.trigger('fadeOut'); // trigger fadeOut when fadeIn is complete
});
}).on('fadeOut', function() { // custom event
$.when(...$words.map(function(i, word) => $(word).delay(i * 1500).fadeOut(1700).promise()))
.then(function() {
$el.trigger('fadeIn'); // trigger fadeIn when fadeOut is complete
});
}).trigger('fadeIn'); // start a never ending fadeIn/fadeOut cycle.
});
untested
The process can undoubtedly be made more efficient, principally by working with the promise from only the final word, but the code will probably get messy.

Javascript loading speed issue as variable increases

I am having an issue with page loading time. Currently right now I am running UBUNTU in Oracle Vm Virtual Box. I am using mozilla firefox as my browser and I am working on an etchasketch project from "The odin project".
My problem is the page loading time. The code takes a prompt at the start and generates a grid for the etch a sketch based on that prompt. I have not given it the minimum and maximum values (16 and 64) respectively, however any number when prompted at the beginning that is beyond 35 doesn't load or takes ages to load.
How do I speed up the process time? / why is it moving so slow? / how can I avoid this ? / is there a fix that I am over looking that can make this work a lot faster? / feel free to tackle any and all of those questions!
This is my HTML CODE:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8"/>
<title>
</title>
</head>
<body>
<div class="etchhead">
<p> Choose your grid size </p>
<input type = "text"></input>
<button id="startOver"> Clear Grid </button>
<p> Change color </p>
</div>
<div id="grid">
</div>
<script src="eas.js"></script>
</body>
</html>
And this is my CSS code:
p {
color: blue;
display: inline;
}
#grid {
display: grid;
width: 800px;
max-width: 800px;
height: 800px;
max-height: 800px;
line-height: 0;
}
.gridBox {
border: 1px solid black;
background-color: lightgrey
}
And this is my JAVASCRIPT code:
gridStart();
function gridStart(){
var boxes = 0
var selectBody = document.querySelector("#grid");
var addBox = document.createElement("div");
var boxCountStart = prompt("enter a number between 16 and 64");
var boxDimensions = (boxCountStart * boxCountStart);
function rowsAndColumns() {
var selectBody = document.querySelector("#grid");
var gridTemplateColumns = 'repeat('+boxCountStart+', 1fr)';
selectBody.style.gridTemplateColumns= gridTemplateColumns;
selectBody.style.gridTemplateRows= gridTemplateColumns;
};
function hoverColor(){
var divSelector = selectBody.querySelectorAll("div");
divSelector.forEach((div) => {
div.addEventListener("mouseover", (event) => {
event.target.style.backgroundColor = "grey";
});
});
};
rowsAndColumns();
for (boxes = 0; boxes < boxDimensions ; boxes++) {
var selectBody = document.querySelector("#grid");
var addBox = document.createElement("div");
addBox.classList.add("gridBox");
addBox.textContent = (" ");
selectBody.appendChild(addBox);
hoverColor();
};
};
There are two components to your issue. One is that you are repeatedly modifying the DOM in a loop. You can fix it by appending all your boxes to a DocumentFragment and then adding that to the DOM after your loop finishes. You are also calling hoverColor(); inside your loop which results in adding tons of event listeners that all do the same thing (since inside hoverColor you are adding a listener to every single div). You can fix both those issues like this:
var fragment = document.createDocumentFragment( );
for (var i = 0; i < boxDimensions ; i++) {
var addBox = document.createElement("div");
addBox.classList.add("gridBox");
addBox.textContent = (" ");
fragment.appendChild(addBox);
}
document.querySelector("#grid").appendChild( fragment );
hoverColor();
Here is a JSFiddle with your original code, and here is one with the modification.
You could also benefit from only having one event listener total. You don't need to loop and add an event listener to every div. Just add one to #grid and use event.target (like you already do, to find the div that the event originated from). Something like this:
function hoverColor(){
document.querySelector("#grid").addEventListener( 'mouseover', function ( event ) {
event.target.style.backgroundColor = "grey";
} );
}

An infinite carousel with vanilla JavaScript

I am trying to build my own carousel with pure JavaScript.
I'm struggling with picking up the most efficient way to add an infinite carousel option.
For some reasons, every element (photo, generic object) must have an id
The algorithm I see goes like that:
You check if the carousel is overflown (the are enough objects to fit
the whole container)
If not: append to the back a copy of the first element, then
a copy of the second element and so on. (But there will be an issue with the ids, because this object will have the same id)
- If the user is scrolling to the last object (to right) then append
the first DOM object to the array back
- If the user is scrolling to
the first object (to left) then add the last DOM child to array
front.
Is this going to work? Is there any other efficient way of doing an infinite carousel?
I have also heard that it's better to use translate property rather than changing the left, right properties, so it there would be more work for the GPU than for CPU.
I created a simple slider with css transformations as the animation technique and plain Javascript.
var img = document.getElementsByClassName("img")[0];
img.style.transform = 'translate('+value+'px)';
You can test it in this codepen snippet.
http://codepen.io/TobiObeck/pen/QKpaBr
A press on a button translates all images in the respective direction along the x-axis. An image on the edge, is set transparent outerImg.style.opacity = '0'; and translated to the other side. You can add or remove image elements in HTML and it still works.
In this second codepen snippet you can see how it works. The opacity is set to 0.5 so it is observable which image switches the side. Because overflow: hidden is removed, you can see how the images on the edge enqueue on the other side.
http://codepen.io/TobiObeck/pen/WGpdLE
Moreover it is notworthy that it is checked wether the animation is complete, otherwise the simultaneously added translations would look odd. Therefore a click won't trigger another animation until unless the animation is completed.
img.addEventListener("transitionend", transitionCompleted, true);
var transitionCompleted = function(){
translationComplete = true;
}
leftBtnCLicked(){
if(translationComplete === true){
//doAnimation
}
}
you can use this code to manipulate slides. This basically rotates the array back and front
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
width: 100%;
height: 100%;
}
.parentDiv {
height: 30%;
width: 100%;
display: flex;
}
</style>
<title>test</title>
</head>
<body>
<button class="fwd"> Fwd! </button>
<button class="bkwd"> Bkwd! </button>
<script type="text/javascript">
const arr = ['red', 'blue', 'coral', 'green', 'yellow'];
let narr = ['red', 'blue', 'coral'];
const parentDiv = document.createElement('div');
parentDiv.setAttribute('class', 'parentDiv');
document.body.insertAdjacentElement('afterbegin', parentDiv);
window.onload = ()=> {
narr.forEach(color => {
while(parentDiv.children.length < narr.length){
const childDiv = document.createElement('div');
parentDiv.appendChild(childDiv);
};
});
Array.from(parentDiv.children).forEach((child, index) => {
child.style.border = '1px #000 dotted';
child.style.minWidth = '20%';
child.style.minHeight = '20vh';
child.style.backgroundColor = narr[index]
});
};
document.querySelector('.fwd').addEventListener('click', ()=>{
narr.shift();
if(narr[narr.length-1] === arr[arr.length-1]){
narr.push(arr[0])
} else {
narr.push(arr[arr.indexOf(narr[narr.length-1])+1])
}
narr.forEach(color => {
while(parentDiv.children.length < narr.length){
const childDiv = document.createElement('div');
parentDiv.appendChild(childDiv);
};
});
Array.from(parentDiv.children).forEach((child, index) => {
child.style.border = '1px #000 dotted';
child.style.minWidth = '20%';
child.style.minHeight = '20vh';
child.style.backgroundColor = narr[index];
});
})
document.querySelector('.bkwd').addEventListener('click', ()=>{
narr.pop();
if(narr[0] === arr[0]){
narr.unshift(arr[arr.length-1])
} else {
narr.unshift(arr[arr.indexOf(narr[0])-1])
}
narr.forEach(color => {
while(parentDiv.children.length < narr.length){
const childDiv = document.createElement('div');
parentDiv.appendChild(childDiv);
};
});
Array.from(parentDiv.children).forEach((child, index) => {
child.style.border = '1px #000 dotted';
child.style.minWidth = '20%';
child.style.minHeight = '20vh';
child.style.backgroundColor = narr[index]
});
})
</script>
</body>
</html>

Categories