Javascript Pagination Issue - javascript

I'm trying to make a simple Javascript pagination function, but I'm having this issue where instead of iterating through the array, it keeps adding new list items to the innerhtml.
I have tried creating an element and appending it to the DOM.
I have tried using if/else statements to display the list items I
want.
<body>
<div class='result'></div>
<button class="add">+</button>
<script src='pretty.js'></script>
</body>
let dogs = [
'goldendoodle',
'poodle',
'afghan hound',
'golden retriever',
'labrador',
'chihuahua',
'pitbull',
'german shepherd',
'greyhound',
'bull terrier'
]
let high = 1;
let low = 0;
let result = document.querySelector('.result');
let add = document.querySelector('.add');
function Pagination(low,high) {
for(var i = 0 ; i < dogs.length;i++) {
let answer = document.createElement('div');
answer.classList.add('dogs-dom');
answer.innerHTML = dogs[i];
result.appendChild(answer);
if(i >= low && i < high) {
answer.style.display ='block';
}
if(i < low || i > high) {
answer.style.display ='none';
}
}
}
Pagination(low,high);
add.addEventListener('click', () => {
low += 2;
high += 2;
Pagination(low,high);
});
When I click the button, I want the next two array items to appear and replace the last two shown.

To use the approach you've outlined above you'll need to clear the innerHtml of the result element before appending new children. At the top of your Pagination function try result.innerHtml = '';.
That said if you are using a hide/show approach to paginate the list it would be more efficient to create the dom elements only once and modify the style.display property of each instead of clearing out the result and re-creating all of the answer divs on every click.

Your Pagination function only adds elements to the dom each time it is called.
You can either remove the existing elements every time Pagination is called, and render only those that should be displayed, e.g.:
function Pagination(low,high) {
result.innerHTML = ''; // remove all children of result
// only render the children which should be visible
for(var i = low ; i < high;i++) {
let answer = document.createElement('div');
answer.classList.add('dogs-dom');
answer.innerHTML = dogs[i];
result.appendChild(answer);
}
}
Or you can use display: block; / display: none. (Will not scale very well with large lists)
function Pagination(low,high) {
// only append all dogs once
if(result.childElementCount === 0) {
for(var i = 0; i < dogs.length;i++) {
let answer = document.createElement('div');
answer.classList.add('dogs-dom');
answer.style.display ='none';
answer.innerHTML = dogs[i];
result.appendChild(answer);
}
}
// toggle display: none / block for each element
for(var i = 0; i < dogs.length;i++) {
if(i >= low && i < high)
answer.style.display ='block';
else
answer.style.display ='none';
}
}
As a bonus, heres a reusable pagination class example:
function Pagination(container, items) {
this.container = container;
this.result = container.querySelector('.result');
this.prevBtn = container.querySelector('.prev');
this.nextBtn = container.querySelector('.next');
this.items = items;
this.offset = 0;
this.limit = 5;
this.updateDom();
this.prevBtn.onclick = this.prevPage.bind(this);
this.nextBtn.onclick = this.nextPage.bind(this);
}
Pagination.prototype.nextPage = function() {
if((this.offset + this.limit) < this.items.length)
this.offset += this.limit;
this.updateDom();
};
Pagination.prototype.prevPage = function() {
if(this.offset >= this.limit)
this.offset -= this.limit;
this.updateDom();
};
Pagination.prototype.updateDom = function() {
this.result.innerHTML = '';
let stop = Math.min(this.offset + this.limit, this.items.length);
for(let i = this.offset; i < stop; i++) {
let el = document.createElement("div");
el.appendChild(document.createTextNode(this.items[i]));
this.result.appendChild(el);
}
let hasPrev = this.offset > 0;
if(hasPrev)
this.prevBtn.classList.remove('hide');
else
this.prevBtn.classList.add('hide');
let hasNext = (this.offset + this.limit) < this.items.length;
if(hasNext)
this.nextBtn.classList.remove('hide');
else
this.nextBtn.classList.add('hide');
};
let items = [];
for (let i = 1; i <= 50; i++)
items.push(`Item ${i}`);
let pagination = new Pagination(document.querySelector(".paginate"), items);
// You can also programatically switch to the next / prev page:
// pagination.nextPage();
// pagination.prevPage();
.hide { visibility: hidden; }
<div class="paginate">
<div class="result"></div>
<button class="prev">PREV</button>
<button class="next">NEXT</button>
</div>

Maybe this is along the lines of what you want to do?
It tracks only a globalIndex (which would be like like your 'low' variable).
The showNextTwoItems function:
- Notes the indexes where we should start and end
- Clears the container div
- Enters a while loop that appends items and increments the current index
- Updates the globalIndex when enough items have been added
let dogs = [ 'goldendoodle', 'poodle', 'afghan hound', 'golden retriever', 'labrador', 'chihuahua', 'pitbull', 'german shepherd', 'greyhound', 'bull terrier' ],
containerDiv = document.querySelector('.result'),
addBtn = document.querySelector('.add'),
globalIndex = 0; // Tracks where we left off (starts at zero)
const NUMBER_TO_SHOW = 2;
addBtn.addEventListener("click", showNextTwoItems); // Calls function on click
function showNextTwoItems(){
let numberToShow = NUMBER_TO_SHOW, // In case we ever want to change numberToShow
currentIndex = globalIndex, // Gets local copy of globalIndex (always < dogs.length)
// Lets us stop looping when we've shown enough or reach the end of the array
stopBeforeIndex = Math.min(currentIndex + numberToShow, dogs.length);
containerDiv.innerHTML = ""; // Clears div
while(currentIndex < stopBeforeIndex){
// Creates and appends a text node with the next dog
const newItem = document.createTextNode(dogs[currentIndex]);
containerDiv.appendChild(newItem);
// Creates and appends a line break
const lineBreak = document.createElement("BR");
containerDiv.appendChild(lineBreak);
// Moves on to the next index
currentIndex++;
}
// Updates global index (making sure it is not too big for the array)
globalIndex = currentIndex < dogs.length ? currentIndex : 0;
}
<button class="add">+</button>
<div class='result'></div>

Related

Javascript passing info from one function to another

I've created a JS function that hides a certain amount of breadcrumbs if there are too many. They are replaced by a button (ellipsis), when you click the button the hidden breadcrumbs are revealed.
The Problem: I loop through the breadcrumbs to see if there are enough to hide. If there are I hide them. But I can't figure out how to then call the code to create the button. If I call the button code in the loop I get more than 1 button generated.
Right now the button will always appear whether there are enough breadcrumbs to hide or not.
In my mind, I would have the for loop with the if statement return true to what would then be the button function. But I can't figure out how to do this. Please offer any pointers for restructuring this code if you can.
Here's a Codepen: https://codepen.io/sibarad/pen/GRvpEbp
Basic HTML:
<nav aria-label="breadcrumb">
<ol class="c-breadcrumb mb-7 md:mb-8">
<li class="c-breadcrumb-item">
Breadcrumb 1
</li>
<li class="c-breadcrumb-item">
Breadcrumb 2
</li>
<li class="c-breadcrumb-item">
Longer Breadcrumb Name 03
</li>
</ol>
</nav>
Javascript:
function breadcrumb() {
// Target specific breadcrumbs, not 1st or last 2
let hiddenbreadcrumb = document.querySelectorAll('.c-breadcrumb-item:nth-child(1n+2):nth-last-child(n+3)');
// Loop through select breadcrumbs, if length is greater than x hide them.
for (var i = 0; i < hiddenbreadcrumb.length; i++) {
if(hiddenbreadcrumb.length >= 3) {
hiddenbreadcrumb[i].style.display = "none";
}
}
// This would be the button function, but I don't know how to engage this only if the if statement above was met.
let li = document.createElement('li');
li.className = 'c-breadcrumb-item';
let ellipbutton = document.createElement('button');
ellipbutton.type = 'button';
ellipbutton.innerHTML = '...';
ellipbutton.className = 'c-breadcrumb_btn u-btn-clear';
ellipbutton.onclick = function() {
console.log("clicked");
for (var i = 0; i < hiddenbreadcrumb.length; i++) {
hiddenbreadcrumb[i].style.display = "flex";
}
li.style.display = "none";
};
li.appendChild(ellipbutton);
let container = document.querySelector('.c-breadcrumb-item:first-child');
container.insertAdjacentElement("afterend", li);
}
breadcrumb();
We can refactor your code slightly to achieve this - the if statement which checks whether there are more than 3 breadcrumbs doesn't need to be inside the for loop - it's redundant to keep checking the same value multiple times.
If we move that outside the loop then it can
a) prevent unnecessary looping when there aren't enough breadcrumbs, and
b) wrap around the button creation code as well, which should solve your problem.
For example:
if (hiddenbreadcrumb.length >= 3) {
for (var i = 0; i < hiddenbreadcrumb.length; i++) {
hiddenbreadcrumb[i].style.display = "none";
}
let li = document.createElement('li');
li.className = 'c-breadcrumb-item';
let ellipbutton = document.createElement('button');
ellipbutton.type = 'button';
ellipbutton.innerHTML = '...';
ellipbutton.className = 'c-breadcrumb_btn u-btn-clear';
ellipbutton.onclick = function() {
console.log("clicked");
for (var i = 0; i < hiddenbreadcrumb.length; i++) {
hiddenbreadcrumb[i].style.display = "flex";
}
li.style.display = "none";
};
let container = document.querySelector('.c-breadcrumb-item:first-child');
container.insertAdjacentElement("afterend", li);
}
It looks like some small initialization issues. This should correct it:
Change this:
let hiddenbreadcrumb = document.querySelectorAll('.c-breadcrumb-item:nth-child(1n+2):nth-last-child(n+3)');
// Loop through select breadcrumbs, if length is greater than x hide them.
for (var i = 0; i < hiddenbreadcrumb.length; i++) {
if(hiddenbreadcrumb.length >= 3) {
hiddenbreadcrumb[i].style.display = "none";
}
}
to this:
let hiddenbreadcrumb = document.querySelectorAll('.c-breadcrumb-item');
if(hiddenbreadcrumb.length < 3)
return
for (var i = 1; i < hiddenbreadcrumb.length - 1; i++) {
hiddenbreadcrumb[i].style.display = "none";
}
Try this... it allows 3 li items as item1 ... item2ndLast, itemLast
(function () {
"use strict";
function breadcrumb() {
let hiddenbreadcrumb = document.querySelectorAll(".c-breadcrumb-item:nth-child(1n+2)");
if (hiddenbreadcrumb.length <= 3) return;
for (var i = 1; i < hiddenbreadcrumb.length - 1; i++) {
hiddenbreadcrumb[i].style.display = "none";
}
let li = document.createElement("li");
li.className = "c-breadcrumb-item";
let ellipbutton = document.createElement("button");
ellipbutton.type = "button";
ellipbutton.innerHTML = "...";
ellipbutton.className = "c-breadcrumb_btn u-btn-clear";
ellipbutton.onclick = function () {
console.log("clicked");
for (var i = 0; i < hiddenbreadcrumb.length; i++) {
hiddenbreadcrumb[i].style.display = "flex";
}
li.style.display = "none";
};
li.appendChild(ellipbutton);
let container = document.querySelector(".c-breadcrumb-item:first-child");
container.insertAdjacentElement("afterend", li);
}
breadcrumb();
})();

Is there a way to "count" elements that haven't rendered on the webpage?

I'm currently working on a chrome extension and I need to count the total number of books in a library. However, only half are counted in the array due to the other half not rendering until scroll down. I tried "MutationObserver", however, the count only shows once the changes are made (when I scroll down). I need the count to be displayed upon load.
// Variables
let list = document.querySelectorAll("ul > li");
let mlist = document.querySelector("ul");
let numBooks = [];
// Push books to array
for (let i = 0; i < list.length; i++) {
numBooks.push(i + 1);
}
// Observe changes
const observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.addedNodes.length) {
// Create Element: Book Count
let url = "myurl";
let nBooks = numBooks.length / 10;
let nChanges = mutation.addedNodes.length - 1;
if (url == window.location.href) {
let el = document.createElement("span");
el.innerHTML = nBooks + nChanges;
let par = document.getElementsByTagName("h2")[0];
par.appendChild(el);
}
}
});
});
observer.observe(mlist, {
childList: true,
});

Trying to print out step by step in a recursive function without printing them out all at once in Javascript

I'm trying to make an N-Queens visualizer and I hooked up my solver to the html doc. My issue is that I'm trying to display everything step by step with intervals of 1 second each for the place function, rather than showing the completed solutions right away.
In other words, I want to show each move done step by step.
My placing function, is also hooked up to the tables on the DOM so when it's being placed on the board, it also places it on the html table.
function place(board, row, col, val, table) {
const newRow = board[row].split('');
newRow[col] = val;
board[row] = newRow.join('');
//place on DOM as well
table.childNodes[row].childNodes[col].innerHTML = val;
}
and the code I have for my solver is this
function solver(board, row, col, solutions) {
if (col === board.length) {
solutions.push([...board]);
return;
}
const currentTable = listOfTables[solutions.length];
for (let i = 0; i < board.length; i++) {
if (canPlace(board, i, col)) {
place(board, i, col, 'Q', currentTable);
solver(board, row, col + 1, solutions);
place(board, i, col, '.', currentTable);
}
}
}
I was trying to wrap a setTimeout inside the solver function but that still runs the code all at once, when the timeout hits.
The currentTable variable is the used to know which of the tables in the DOM is currently going to be in use.
Here's the CodePen with all the code if anyone needs it https://codepen.io/vvus/pen/KKVKMrq?editors=1111
you can adjust the code bellow to your liking
you add the display="none" to all your tables and then you deactivate the attribute in a time interval using this code
var i=0
setInterval(() => {
for(z=i;z<listOfTables.length;z++) {
const x=document.getElementsByClassName('new-table')[z]
if (x.style.display === "none") {
x.style.display = "";
i++
}
break
}
}, 3000);
so your final code may look like this
const button = document.getElementById("button-click");
const selection = document.getElementById("selection");
let tablesContainer = document.getElementById("tables-container");
let listOfTables = tablesContainer.childNodes;
button.addEventListener("click", () => {
console.log(nQueens(Number(selection.value)));
listOfTables = tablesContainer.childNodes;
// add this code to your eventlistner or anywhre you want to show something in time interval
var i=0
setInterval(() => {
for(z=i;z<listOfTables.length;z++) {
const x=document.getElementsByClassName('new-table')[z]
console.log(z)
if (x.style.display === "none") {
x.style.display = "";
i++
}
break
}
}, 3000);
});
selection.addEventListener("change", (e) => {
let numOfTables = e.target.value;
if (numOfTables === 1) numOfTables = 1;
else if (numOfTables === 4) numOfTables = 3;
else if (numOfTables === 8) numOfTables = 93;
const rowsCols = e.target.value;
for (let allTables = 1; allTables < numOfTables; allTables++) {
const newTable = document.createElement("table");
newTable.classList = "new-table";
newTable.style.display="none"// add this attribute to all your tables
for (let i = 0; i < rowsCols; i++) {
const row = document.createElement("tr");
for (let j = 0; j < rowsCols; j++) {
const th = document.createElement("th");
th.innerHTML = ".";
row.appendChild(th);
}
newTable.appendChild(row);
}
tablesContainer.appendChild(newTable);
}
});

Hide/Show div p5.js

I am using the p5.js library, and I am working on a speech recognition - text to speech project. Kind of a chatbot.
Input is voice input which becomes a string.
I am outputting the result from a txt file, using a markov chain. Output is a string contained in a div.
My question is:
Is there a way to hide/show the div containing my input/output (.myMessage and .robotMessage) in intervals?
I want the whole screen first showing only the input when I am talking, then input disappears and only output showing, then when the computer voice finishes speaking my input is shown in the screen and so on...
Here some parts of the code, let me know if it is clear enough.
//bot
function setup() {
noCanvas();
//reads and checks into the text file
for (var j = 0; j < names.length; j++) {
var txt = names[j];
for (var i = 0; i <= txt.length - order; i++) {
var gram = txt.substring(i, i + order);
if (i == 0) {
beginnings.push(gram);
}
if (!ngrams[gram]) {
ngrams[gram] = [];
}
ngrams[gram].push(txt.charAt(i + order));
}
}
//voice recognition
let lang = 'en-US';
let speechRec = new p5.SpeechRec(lang, gotSpeech);
let continuous = true;
let interim = false;
speechRec.start(continuous, interim);
//text-to-speach
speech = new p5.Speech();
speech.onLoad = voiceReady;
function voiceReady() {
console.log('voice ready');
}
//input-ouput
function gotSpeech() {
if (speechRec.resultValue) {
var p = createP(speechRec.resultString);
p.class('myMessage');
}
markovIt();
chooseVoice();
speech.speak(answer);
}
}
and
function markovIt() {
var currentGram = random(beginnings);
var result = currentGram;
for (var i = 0; i < 100; i++) {
var possibilities = ngrams[currentGram];
if (!possibilities) {
break;
}
var next = random(possibilities);
result += next;
var len = result.length;
currentGram = result.substring(len - order, len);
}
var answer = result;
window.answer = answer;
var p2 = createP(answer);
p2.class('robotMessage');
}
how the HTML looks
<div class="container">
<div class="myMessage"></div>
<div class="robotMessage"></div>
</div>
Use select() to get a document element by its id, class, or tag name. e.g:
let my_div = select("myMessage");
Change the style of an element by style().
e.g hide:
my_div.style("display", "none");
e.g. show:
my_div.style("display", "block");
See also Toggle Hide and Show

How to change every character while hovering?

I have word
<p class="item">Ultimatepp’s</p>
I need to change every letter while hovering
I have characters var characters = 'a#ldfhas?
kdjbihoEFGHIJKLMNOPQRS!adfjsd(&?aqdo0ap$dfs#dfqwqrwr'; and i need to insert random character instead of random character in the word Ultimatepp’s.
var item = $('.item');
var arr = Array.from(item);
item.on('mouseenter', function () {
var characters = 'a#ldfhas?kdjbihoEFGHIJKLMNOPQRS!adfjsd(&?aqdo0ap$dfs#dfqwqrwr';
var charactersArr = Array.from(characters);
var self = $(this);
var itemText = self.html();
var arr = Array.from(itemText);
var reversed = arr.reverse();
var string = reversed.join('');
var x = charactersArr.splice(Math.random() * itemText.length, itemText.length);
$(this).html(x);
});
I wrote this code that changes the whole word, but i need to replace characters. Maybe this code could be rewritten?
I would wrap each letter in a span so you can bind a separate hover event to each:
var characters = 'a#ldfhas?kdjbihoEFGHIJKLMNOPQRS!adfjsd(&?aqdo0ap$dfs#dfqwqrwr',
intervalVar,
timeoutVar;
intervalCount = 0;
$('.item').html(function() { // this just wraps the original words letters in spans
var text = $(this).text(),
html = '';
for (i = 0; i < text.length; i++) {
html += `<span data-original="${text[i]}">${text[i]}</span>`;
}
return html;
})
.on('mouseenter', function() {
var $item = $(this);
changeChars($item); // run immediately when you hover
setupInterval($item);
})
.on('mouseleave', function() {
clearInterval(intervalVar); // clear interval
clearTimeout(timeoutVar); // clear timeout
originalChars($(this)); // reset word
});
function originalChars($elem) { // resets word
var $spans = $elem.children('span');
$spans.text(function() {
return $(this).data('original');
});
return $spans;
}
function setupInterval($elem) {
clearTimeout(timeoutVar);
intervalVar = setInterval(function() { // set new interval to change the characters every second you are hovering
changeChars($elem);
}, 200);
}
function changeChars($elem) {
var $spans = originalChars($elem); // reset word before changing again
var changed = [];
// number of letters to change in original word (up to half of them can change) or change to a constant - eg 4
var toChange = Math.floor(Math.random() * ($spans.length / 2));
for (i = 0; i < toChange; i++) { // change up to a max of toChange characters
var changing = Math.floor(Math.random() * $spans.length); // get a random character in the original string
if (!changed.includes(changing)) { // see if it has already been changed
changed.push(changing); // if not add to list of changed
$spans.eq(changing).text(characters.substr(Math.floor(Math.random() * characters.length), 1)); // randomise letter
}
}
intervalCount++;
if (intervalCount == 5) { // add a pause after 5 changes
intervalCount = 0;
clearInterval(intervalVar);
timeoutVar = setTimeout(function() {
setupInterval($elem);
}, 1500); // pause for 1.5 secs before starting loop again
}
}
body {
font-size: 30px;
}
/* for easier hovering! */
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p class="item">Ultimatepp’s</p>
You can use a mouseenter event to start a timer that shuffles an array of letters that matches the hovered word, and writes them back to the html. Then the mouseleave event would stop the timer would clearInterval and set the original text back.
To be able to set the effect on multiple elements, the matching must be done with querySelectorAll and you need to iterate over the matched elements to set the events:
const spans = [...document.querySelectorAll("span")]; //array of spans
const originalTexts = spans.map(span => span.textContent); //original texts
let timer = null;
//forEach to iterate over each span and set the mouseenter event
spans.forEach(span => span.addEventListener("mouseenter", ()=> {
timer = setInterval(() => {
let currText = span.textContent.split('');
//shuffle the word letters
for (let i = currText.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
let temp = currText[i];
currText[i] = currText[j];
currText[j] = temp;
}
span.textContent = currText.join(''); //set the new word back
}, 350);
}));
//also iterate to set the mouseleave event
spans.forEach((span, index) => span.addEventListener("mouseleave", ()=> {
if (timer !== null){
clearInterval(timer);
}
span.textContent = originalTexts[index];
}));
<span>Hover Me</span><br>
<span>You can also hover me</span><br>
<span>or me!!</span><br>
If you want to randomize according to a characters list you only need to change the shuffling part to iterate over each letter and get a random one from the characters:
const characters = 'a#ldfhas?kdjbihoEFGHIJKLMNOPQRS!adfjsd(&?aqdo0ap$dfs#dfqwqrwr';
const spans = [...document.querySelectorAll("span")];
const originalTexts = spans.map(span => span.textContent);
let timer = null;
spans.forEach(span => span.addEventListener("mouseenter", ()=> {
timer = setInterval(() => {
let currText = span.textContent.split('');
//only this part is different now, that randomizes according to the characters
for (let i = 0; i < currText.length; ++i){
currText[i] = characters[Math.floor(Math.random() * characters.length)];
}
span.textContent = currText.join('');
}, 350);
}));
spans.forEach((span, index) => span.addEventListener("mouseleave", ()=> {
if (timer !== null){
clearInterval(timer);
}
span.textContent = originalTexts[index];
}));
<span>Hover Me</span><br>
<span>You can also hover me</span><br>
<span>or me!!</span><br>

Categories

Resources