Javascript Google Style Pagination Menu - javascript

The math to correctly display a pagination menu via Javascript is driving me insane. Hopefully someone who understands this a little better than me can help me out.
All I need is to understand how to properly display a pagination menu on the client side with javascript.
Lets say I have 10 total items.
The max I want show at once is 1.
The menu should display 5 numbers and the last number should be the last page with a ... if we aren't on the last set of numbers.
So for instance < 1 2 3 4 ... 10 >
User clicks 2 - Start number should be 1, last 4 < 1 2 3 4 ... 10 >
User clicks 4 - start number should 2, last 5 < 2 3 4 5 ... 10 >
User clicks 5 - start should be 3, last 6 < 3 4 5 6 ... 10 >
User clicks 7 - we are on the last set so < 6 7 8 9 10 >
So far all I have is:
var pages = Math.ceil(count / amount);
var maxIterations = 5;
var iterations = Math.min(pages, maxIterations);
var offset = Math.max(0, page-Math.round(maxIterations/2));
for(var i=0; i<iterations; i++) {
var label = page + i;
if(i == maxIterations-1) label = pages; //Last page number
paginatorObjects.push({label: label}); //This create the menu button
}
Basically the point is that I need the numbers to iterate up and down as the user clicks through them (how google does it) without having to use the arrow buttons. I understand jQuery has a nice plugin for this sort of thing, but this has to be done in vanilla javascript.

This problem had me scratching my head for a while too, I recently had to implement it for an AngularJS application, but the pagination logic is pure javascript.
There's a working demo on CodePen at http://codepen.io/cornflourblue/pen/KVeaQL
I also wrote this blog post with further details
Here's the pagination logic
function GetPager(totalItems, currentPage, pageSize) {
// default to first page
currentPage = currentPage || 1;
// default page size is 10
pageSize = pageSize || 10;
// calculate total pages
var totalPages = Math.ceil(totalItems / pageSize);
var startPage, endPage;
if (totalPages <= 10) {
// less than 10 total pages so show all
startPage = 1;
endPage = totalPages;
} else {
// more than 10 total pages so calculate start and end pages
if (currentPage <= 6) {
startPage = 1;
endPage = 10;
} else if (currentPage + 4 >= totalPages) {
startPage = totalPages - 9;
endPage = totalPages;
} else {
startPage = currentPage - 5;
endPage = currentPage + 4;
}
}
// calculate start and end item indexes
var startIndex = (currentPage - 1) * pageSize;
var endIndex = startIndex + pageSize;
// create an array of pages to ng-repeat in the pager control
var pages = _.range(startPage, endPage + 1);
// return object with all pager properties required by the view
return {
totalItems: totalItems,
currentPage: currentPage,
pageSize: pageSize,
totalPages: totalPages,
startPage: startPage,
endPage: endPage,
startIndex: startIndex,
endIndex: endIndex,
pages: pages
};
}

What I ended up doing is this. This is directly from my source code, so there is some templating logic that doesn't make sense, but anyone who might be reading this in the future should be able to adjust according to their needs.
function paginate() {
var paginator = sb.find('#' + moduleName + 'Pagination')[0];
var container = paginator.getElementsByClass('container')[0];
sb.dom.clearAll(container);
if(count && count > 0) {
sb.dom.removeClass(paginator, 'hidden');
var pages = Math.ceil(count / amount);
var maxIterations = 5;
var iterations = Math.min(pages, maxIterations);
var paginatorObjects = [];
var center = Math.round(maxIterations/2);
var offset = page-center;
if(page < center) offset = 0; //Don't go lower than first page.
if(offset + iterations > pages) offset -= (offset + iterations) - pages; //Don't go higher than total pages.
for(var i=0; i<iterations; i++) {
var label = (i+1) + offset;
paginatorObjects.push({label: label});
}
sb.template.loadFrom(templateUrl, 'paginator').toHTML(paginatorObjects).appendTo(container, function(template) {
var pageNumber = template.obj.label;
if(pageNumber != page) {
sb.addEvent(template, 'click', function() {
getAppointments(pageNumber);
});
} else {
sb.dom.addClass(template, 'highlight');
}
});
if(offset + iterations < pages) {
var dots = document.createTextNode(' ... ');
container.appendChild(dots);
sb.template.loadFrom(templateUrl, 'paginator').toHTML({label: pages}).appendTo(container, function(template) {
sb.addEvent(template, 'click', function() {
getAppointments(pages);
});
});
}
var backBtn = paginator.getElementsByClass('icon back')[0];
var forwardBtn = paginator.getElementsByClass('icon forward')[0];
sb.removeEvent(backBtn, 'click');
sb.removeEvent(forwardBtn, 'click');
if(page - 1 > 0) {
sb.dom.removeClass(backBtn, 'hidden');
sb.addEvent(backBtn, 'click', function() {
getAppointments(page-1);
});
} else {
sb.dom.addClass(backBtn, 'hidden');
}
if(page + 1 <= pages) {
sb.dom.removeClass(forwardBtn, 'hidden');
sb.addEvent(forwardBtn, 'click', function() {
getAppointments(page+1);
});
} else {
sb.dom.addClass(forwardBtn, 'hidden');
}
} else {
sb.dom.addClass(paginator, 'hidden');
}
}
When getAppointments() is called from the buttons, I use AJAX to get the next pages results, then this pagination() function is called again from that function so everything gets reset. The variables that determine the amount to show per page and the current page number are global (to the container function) and are not shown in this code sample.
The logic behind this is fairly simple. The center is determined (in my case the max it shows at once is 5, so the center is 3) and then an offset is calculated based on the page number. Clicking 4 will adjust the start number +1, and 2 would adjust it -1.

Related

JavaScript Pagination Only show 5 buttons

I have created a pagination system using JavaScript. I have used this code to create the pagination system:
function setUpPagination(items, wrapper, rows_per_page) {
wrapper.innerHTML = "";
let page_count = Math.ceil(items.length / rows_per_page);
for (let i = 1; i < page_count + 1; i++) {
let btn = paginationButton(i, items);
wrapper.appendChild(btn);
}
}
function paginationButton(page, items) {
let button = document.createElement('button');
button.innerText = page;
if (current_page == page) {
button.classList.add('active');
}
button.addEventListener('click', function() {
current_page = page;
displayList(items, list_element, rows, current_page);
let current_btn = document.querySelector('.pagination button.active');
current_btn.classList.remove('active');
button.classList.add('active');
})
return button;
}
However, this code shows all of the pagination buttons. If I have 100 pages, all 100 pagination buttons will be shown.
I only want to show 5 buttons at a time. These buttons should be: the page the user is currently on, the 2 pages before the page the user is currently on, the 2 pages after the page the user is currently on.
If the user is on page 1, pages 1 - 5 should be shown.
If the user is on the last page, the last page button and the 4 page buttons before that should be shown.
I have added this code to stop the user from selecting a page which is too high:
if (i >= page_count) {
i = page_count;
}
However this means the user cannot select the last two pages.
I want to give the user the ability to select the last two pages. However, no extra pages should be shown.
With the way you have set up your for-loop bounds in your setUpPagination function, you are iterating over every single page and making a button for each one. I recommend you update the loop bounds to only cover the range of pages you want buttons for.
I envision an update that looks something like this:
for (let i = current_page - 2; i <= current_page + 2; i++) { // <-- HERE!
let btn = paginationButton(i, items);
wrapper.appendChild(btn);
}
}
Of note, this won't properly account for when you are looking at the first few or last few pages, so you will want to update this slightly to account for these edge cases (for example, if you are viewing page 1 currently this would attempt to make buttons for pages -1, 0, 1, 2, & 3). I think this should give you a good push in the right direction though!
This function will return five pages, current in the middle and first/last five if necessary:
const result = getPages(3, 19);
console.log(result);
function getPages(totalPages, currentPage) {
if (totalPages <= 5) return Array.from(Array(totalPages).keys()).map( r => { return r + 1 });
let diff = 0;
const result = [currentPage - 2, currentPage - 1, currentPage, currentPage + 1, currentPage + 2];
if (result[0] < 3 ) {
diff = 1 - result[0];
}
if (result.slice(-1) > totalPages - 2 ) {
diff = totalPages - result.slice(-1);
}
return result.map( r => { return r + diff });
}
Case total 5, current from 1 to 5 returns [1,2,3,4,5].
Case total less than 5, returns an array of number of total length.
Case total 15, current 13, 14 or 15 returns [11,12,13,14,15].

Optimizing process determining license usage in time

I am trying to find an efficient way to go through a big amount of data to determine how many units are processed at once.
So the data that I am receiving are just simple pairs:
{timestamp: *, solvetime: *}
What I need, is to see how many things are processed at each second.
To help you visualize what I mean: here is an example of data that I receive:
{{timestamp: 5, solvetime: 3},{timestamp: 7, solvetime: 5},{timestamp: 8, solvetime: 2},{timestamp: 12, solvetime: 10},{timestamp: 14, solvetime: 7}}
The chart below should help you understand how it looks in time:
https://i.stack.imgur.com/LEIhW.png
This is a simple case where the final calculation contains every second, but if the timeframe is much wider I show only 205 different times in this timeframe. E.g. if the time btw the first and the last timestamp is 20500 seconds I would calculate the usage for every second and divide the time into 205 parts - 100 seconds each and show only the second with the highest usage.
What I am doing right now is to iterate through all the pairs of input data and create a map of all the seconds, once I have it I go through this map again to find the highest usage in each time period (of 205 time periods I divide the whole time-frame in) and append it to the map of 205 timestamps.
It's working correctly, but it's very very slow and I feel like there is some better way to do it, a table might be faster but it is still not too efficient is it?
Here is the actual code that does it:
// results contain all the timestamps and solvetimes
// Timeframe of the chart
var start = Math.min.apply(Math, msgDetailsData.results.map((o) => { return o.timestamp; }))
var end = Math.max.apply(Math, msgDetailsData.results.map((o) => { return o.timestamp; }))
// map of all seconds in desired range (keys) the values are counter ofprocesses run in a given second
let mapOfSecondsInRange = new Map();
for (let i = start; i <= end; i++) {
mapOfSecondsInRange.set(i, 0);
}
// we go through every proces and add +1 to the value of each second in which the task was active
for (let element of msgDetailsData.results) {
var singleTaskStart = element.timestamp - Math.ceil(element.solveTime);
if (singleTaskStart < start) {
for (let i = singleTaskStart; i < start; i++) {
mapOfSecondsInRange.set(i, 0);
}
start = singleTaskStart;
}
for (let i = singleTaskStart; i < element.timestamp; i++) {
mapOfSecondsInRange.set(i, mapOfSecondsInRange.get(i) + 1);
}
}
// Preparation for the final map - all the seconds in the range divided into 205 parts.
const numberOfPointsOnChart = 205;
var numberOfSecondsForEachDataPoint = Math.floor((end - start) / numberOfPointsOnChart) + 1;
var leftoverSeconds = ((end - start) % numberOfPointsOnChart) + 1;
var highestUsageInGivenTimeframe = 0;
var timestampOfHighestUsage = 0;
let mapOfXXXDataPoints = new Map();
var currentElement = start;
for (let i = 0; i < numberOfPointsOnChart; i++) {
if (leftoverSeconds === 0) {
numberOfSecondsForEachDataPoint = numberOfSecondsForEachDataPoint - 1;
}
if (currentElement <= end) {
for (let j = 0; j < numberOfSecondsForEachDataPoint; j++) {
if (j === 0) {
highestUsageInGivenTimeframe = mapOfSecondsInRange.get(currentElement);
timestampOfHighestUsage = currentElement;
}
else {
if (mapOfSecondsInRange.get(currentElement) > highestUsageInGivenTimeframe) {
highestUsageInGivenTimeframe = mapOfSecondsInRange.get(currentElement);
timestampOfHighestUsage = currentElement;
}
}
currentElement = currentElement + 1;
}
mapOfXXXDataPoints.set(timestampOfHighestUsage, highestUsageInGivenTimeframe);
leftoverSeconds = leftoverSeconds - 1;
}
}

How to link a slider and checkbox to a counter?

I am new in JavaScript. I am trying to link a slider and checkbox to a counter. The slider should increase the counter's value depending on the range. And then the checkboxes have fixes values depending on the users(the slider's value are users, I will explain later) that they should add to the counter if it is checked or not.
var slider = document.querySelector('#myRange'); //Input
var output = document.querySelector("#value-range"); //Output
let rangeValue = document.querySelector('#final-price'); //Counter
const boxesContainer = document.querySelector('.price-boxes__container'); //Checkboxes
I created an event for the checkboxes, so when you click on it, adds the value.
boxesContainer.onchange = function(e) {
if (e.target.classList.contains('checkbox')){
const price = parseInt(e.target.getAttribute('value'))
if (e.target.checked === true) {
total += price;
} else {
total -= price;
}
rangeValue.innerHTML = total
}
}
And I created another event for the slider as well.
slider.oninput = function () {
let value = slider.value;
userPrice(value)
output.style.left = (value/2.88);
output.classList.add("show");
rangeValue.innerHTML = prices[value-25];
function userPrice (value) {
if (value == 25) {
output.innerHTML = '6 €p'
} else {
output.textContent = `${prices[value-25] - prices[value-26]} €p`;
}
}
}
The slider has min 25, max 1000, step = 1, value = 25. Each step is one user. I created an array that has the price depending on the slider's value. Between 26-50 users 6€ per user, 51-250 4€/p, 251-500 3€/p and 501-1000 2€/p. Therefore, I thought the easy way was to create an array and use the slider's value to sent the price to the counter.
const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1}, (_, i) => start + (i * step));
let rangePrices = range(25, 1000, 1);
const prices = [];
rangePrices.forEach((rangeValue, i)=> {
if(rangeValue === 25) {
prices.push(299)
} else if (rangeValue < 51) {
prices.push(prices[i-1] + 6)
} else if (rangeValue < 251){
prices.push(prices[i-1] + 4)
} else if (rangeValue < 501) {
prices.push(prices[i-1] + 3)
} else {
prices.push(prices[i-1] + 2)
}
});
But at the end, when I click on the checkboxes the counter adds the checkboxes' values, but it resets. I know that I have two rangeValue.innerHTML and is due to this that it does not work, but I do not know how to fix it...
As I said at the beginning the checkbox depends on the slider value. Between 26-50 0.7€/p.u., 51-250 0.5€/p.u., 251-500 0.4€/p.u. and 501-1000 0.3€/p.u.. Therefore, so the checkboxes are related to the slider. I do not know how to link both functions either. Finally say that there are 2 more checkboxes.
It would be great if someone could help me!
https://jsfiddle.net/NilAngelats/wq7tjh8c/
This is what I did:
let total = 0;
boxesContainer.onchange = function(e) {
...
rangeValue.innerHTML = prices[slider.value - 25] + total
}
slider.oninput = function() {
...
rangeValue.innerHTML = prices[value - 25] + total;
}
And move userPrice function out of the slider.oninput function so it's only declared once and not every time you move the slider.

Javascript Iteration to display custom list with pause/stop jquery fadeIn and fadeOut

I have an array that is constantly being updated, and needs to display the items in the array 5 at a time. Sometimes there are more than 5 elements in the array, sometimes there are less. If there are more than 5 elements in the array, then I need to cycle them 5 at a time. For example, if there are 10 elements, I want to fade in 1-5, then fade out 1-5, then fade in 5-10. I have this working, and updating, however, if there are only 4 news articles available after the data update, it still fades in and out 1-4, over and over. I need to always fade in the first articles, and if there are less than the numberToShow, don't fade out, just update.
I have tried clearInterval, but that stops updating. I tried .stop().fadeOut(); but then the fade in keeps occurring. I tried .stop().fadeOut(); with .stop().fadeIn(); but the data never fades in. Should I pass the array in to display it, and cycle in there?
For testing, this is simulated with using the date. Every 8 seconds it should update the the data with an updated number. If there are 4 articles, fade in, and update the Date.now() number, but never fade out. If there are 10 articles, fade in and update each cycle.
var numberToShow = 5;
var newsArray = [];
var startRow = 0;
var endRow = 0;
function getData() {
// Simulate the data changing using date.
newsArray = [Date.now(), "News article 1", "News article 2", "News article 3", "News article 4",
"News article 5", "News article 6", "News article 7", "News article 8", "News article 9"];
showNews(numberToShow);
}
// Fade out the results for the next cycle
setInterval(function() {
$("span.text").fadeOut({
duration: 800
});
setTimeout(
function() {
getData();
},
(800)
);
}, 8000);
// Update the data
function updateData() {
getData();
setTimeout(updateData, 6000);
}
// Display the results
function showNews() {
if (endRow >= newsArray.length) {
startRow = 0;
}
endRow = startRow + numberToShow;
if (endRow >= newsArray.length) {
endRow = newsArray.length;
}
var results = "";
for (var k = startRow; k < endRow; k++) {
results += "<span class='text' style='display:none;'>" + newsArray[k] + "</span><br>";
}
startRow = startRow + numberToShow;
document.getElementById('showResults').innerHTML = results;
$("span.text").fadeIn({
duration: 800
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="showResults"></div>
While previous answer works fine - this one could be more like the case in your description...
Short description of the idea:
show list or the part of it.
if list is longer - repeat (go to step 1 in a couple of secs to show another part of the list)
when update comes - anytime - start again with new array
And working example (removed unneeded code and added button to help with tests):
var
numberToShow = 5,
newsArray = [],
startRow = 0,
endRow = 0,
$results = $("#showResults"),
timer;
function getData() {
// Simulate the data changing
newsArray = [Date.now()];
// add random number of items
var j = Math.floor(Math.random()*7)+1;
for(var i=0; i<j; i++){
newsArray.push('News article '+i);
}
// add one more item named "last"
newsArray.push('Last News article');
startCycle();
}
function startCycle() {
startRow = 0;
endRow = 0;
$results.fadeOut(800, function(){
renderList();
});
}
function renderList() {
if (endRow >= newsArray.length) {
startRow = 0;
}
endRow = startRow + numberToShow;
if (endRow > newsArray.length) {
endRow = newsArray.length;
}
var results = "";
for (var k = startRow; k < endRow; k++) {
results += "<span class='text'>" + newsArray[k] + "</span><br>";
}
startRow = startRow + numberToShow;
$results.html(results);
$results.fadeIn(800, function(){
nextCycle();
});
}
function nextCycle() {
// start cycling only if there is more results to be shown
if(newsArray.length > numberToShow){
timer = setTimeout(function(){
$results.fadeOut(800, function(){
renderList();
});
}, 4000);
}
}
// update on request
function updateData() {
clearTimeout(timer);
$results.stop();
getData();
}
// add button for tests
$results.before(
$('<button/>').text('Update now').click(function(){
updateData();
})
)
getData();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="showResults"></div>
Ok - not sure what exactly you tried to do - but you will easily change my code into anything you need
...lets start with clear algorithm:
getData
fade out if list is visible ...then continue to step 3.
render next portion of items from array
fade in if list is not visible ...then go to step 5.
wait a while...
check if all items has been shown (if not - show next portion with step 2, if so - update data with step 1)
hope the the code will give you a chance to adopt it to your needs:
var
numberToShow = 5,
newsArray = [],
startRow = 0,
endRow = 0,
$results = $("#showResults"),
visible = false,
timer;
function fadeInIfNeeded(callback) {
var is_visible = visible;
visible = true;
if(is_visible){
callback();
}else{
$results.fadeIn(800, callback);
}
}
function fadeOutIfNeeded(callback) {
var is_visible = visible;
visible = false;
if(is_visible){
$results.fadeOut(800, callback);
}else{
callback();
}
}
function getData() {
// Simulate the data changing
newsArray = [Date.now()];
// add random number of items
var j = Math.floor(Math.random()*6)+2;
for(var i=1; i<j; i++){
newsArray.push('News article '+i);
}
// add one more item named "last"
newsArray.push('Last News article');
startCycle();
}
function startCycle() {
startRow = 0;
endRow = 0;
fadeOutIfNeeded(function(){
renderList();
});
}
function renderList() {
if (endRow >= newsArray.length) {
startRow = 0;
}
endRow = startRow + numberToShow;
if (endRow > newsArray.length) {
endRow = newsArray.length;
}
var results = "";
for (var k = startRow; k < endRow; k++) {
results += "<span class='text'>" + newsArray[k] + "</span><br>";
}
startRow = startRow + numberToShow;
$results.html(results);
fadeInIfNeeded(function(){
nextCycle();
});
}
function nextCycle() {
// every portion of data will be seen for 6 + 0.8 + 0.8 = 7.6 sec
timer = setTimeout(function(){
if(startRow >= newsArray.length){
// if all items has been shown - get new data (update)
getData();
}else{
// if there is more to show - fade out and render
fadeOutIfNeeded(function(){
renderList();
});
}
}, 6000);
}
getData();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="showResults"></div>
Here is a modern view of how to implement your use case. Please note there
is not a single global variable and all DOM changes (side effects) occur in a single function.
You describe a constantly changing array. This code produces that array of integers
with one remarkable difference. After the data is set/retrieved, a Custom Event
is produced and fired. That event carries the changed/updated array.
function newData(data) { // Random size of array (between 1-15)
const previousLength = data.length;
// ~50% of the time the array grows, otherwise it shrinks
data.length = (Math.random() > .5)?
data.length + Math.ceil(Math.random() * (7 - 1) + 1) :
Math.ceil(Math.random() * (4 - 1) + 1);
for (let i = previousLength; i < data.length; i++) {
data[i] = Math.ceil(Math.random() * (15 - 1) + 1);
}
let dataEvent = new CustomEvent('gotData', { detail: data});
document.getElementById('showResults').dispatchEvent(dataEvent);
}
Your question describes what is the need for a custom iterator that provides no more than 5
array elements for each iteration. The following code provides a Generator function that follows
JavaScript's iterator protocol by returning a result with a .value property
containing an array of no more than 5 elements and a .done property containing a Boolean indicating if there
is no futher data. The yield statement returns data or the return statement results in
.done being set to true to indicate there is no further data. (see the MDN articles for details)
function* nextSet(data = [], numberToShow = 5) {
let current = [];
let currentStart = 0;
while (true) {
[current] = [data.slice(currentStart, currentStart + numberToShow)];
if (currentStart < data.length) {
yield current;
} else {
return;
}
currentStart += numberToShow;
}
}
With the data and iterator in place this code starts off the procession. Set up
an event listener for the custom event, then get some mock data (starting with an
empty array):
document.getElementById('showResults').addEventListener('gotData', doDOM);
newData([]);
All the DOM work is done in the event callback function below (doDOM()).
First create an iterator from the Generator Function.
Then start the interval timer so that we can repeatedly call .next() on the iterator.
Please note how dead-simple the animation actually is with
a bit of rethinking the approach to the entire problem. If result is undefined
then cancel the interval timer, mock more data and repeat the process with the updated array.
function doDOM(event) {
const data = event.detail;
const iterator = nextSet(data); // create iterator from Generator
let text = '';
let page = 0;
let interval = setInterval(()=>{
page++;
let result = iterator.next().value;
if(result) {
text = `Array size: ${data.length} (Page ${page}) -- ${JSON.stringify(result)}`;
// Dead simple animations...
$(event.target).fadeOut(1000, () => {
event.target.innerText = text;
$(event.target).fadeIn(1000);
});
} else {
event.target.innerText += " ----> getting more data..."
// all done, so kill this one
clearInterval(interval);
// Mock new data arrival
newData(data);
return;
}
}, 5000);
}
I do realize this seems to be a mile off from your question. But this answer addresses the whole
puzzle rather than just one bit.
/**
* A Generator function to produce number of data elements
*/
function* nextSet(data = [], numberToShow = 5) {
let current = [];
let currentStart = 0;
while (true) {
[current] = [data.slice(currentStart, currentStart + numberToShow)];
if (currentStart < data.length) {
yield current;
} else {
return;
}
currentStart += numberToShow;
}
}
/**
* CustomEvent Handler - fired when new data is received
*
* DOM manipulations
* All side effects are contained within one function
* #parm Event - contains detail with data
*/
function doDOM(event) {
const data = event.detail;
const iterator = nextSet(data); // create iterator from Generator
let text = '';
let page = 0;
let interval = setInterval(() => {
page++;
let result = iterator.next().value;
if (result) {
text = `Array size: ${data.length} (Page ${page}) -- ${JSON.stringify(result)}`;
// Dead simple animations...
$(event.target).fadeOut(1000, () => {
event.target.innerText = text;
$(event.target).fadeIn(1000);
});
} else {
event.target.innerText += " ----> That's it! Getting more data..."
// all done, so kill this one
clearInterval(interval);
// Mock new data arrival
newData(data);
return;
}
}, 5000);
}
// Array that is either growing or changing on each call
function newData(data) { // Random size of array (between 1-15)
const previousLength = data.length;
// ~50% of the time the array grows, otherwise it shrinks
data.length = (Math.random() > .5) ?
data.length + Math.ceil(Math.random() * (7 - 1) + 1) :
Math.ceil(Math.random() * (4 - 1) + 1);
for (let i = previousLength; i < data.length; i++) {
data[i] = Math.ceil(Math.random() * (15 - 1) + 1);
}
let dataEvent = new CustomEvent('gotData', {
detail: data
});
document.getElementById('showResults').dispatchEvent(dataEvent);
}
document.getElementById('showResults').addEventListener('gotData', doDOM);
newData([]);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script defer src="index.js"></script>
</head>
<body>
<main id="showResults">Welcome! ----> waiting for data...</main>
</body>
</html>
Expand the code snippet to see this work.

jQuery list slider stops when li elements are not evenly divisible

If someone has a better, more descriptive title for this question, let me know; I couldn't think of a good title to describe my problem.
Anyways, I have some jQuery code that allows me to rotate through list items by displaying 3 li elements at a time.
However, I run into a problem when the amount of li elements that I have isn't evenly divisible by 3.
Here is the jQuery:
$( document ).ready(function() {
var j = 1;
var inbetween = 7000; //milliseconds
function page() {
var jmax = $("#leader-slider li").length;
var count = 3;
var start = j;
var end = j + count - 1;
var complete = 0;
console.log(j, start, end);
var range = $('#leader-slider li:nth-child(n+'+ start + '):nth-child(-n+'+ end +')');
range
.fadeIn(400)
.delay(inbetween)
.fadeOut(400, 'swing', function() {
if (j++ >= jmax) {
j = 1;
}
if (++complete >= count) {
page();
}
});
};
page();
});
This works beautifully when I have 3,6,9,12,etc. li elements. But as soon as I have a number not divisible by 3, like 8, it will cycle through all of them but after it shows the last li elements it stops the rotation and no longer shows anymore.
This is happening because your complete is never greater-than or equal-to count if you don't find exactly three elements with your jQuery.
It looks to me like you are already detecting when you've incremented beyond the number of li elements here:
if (j++ >= jmax) {
j = 1;
}
If this is working correctly, you could call your page method here:
if (j++ >= jmax) {
j = 1;
page();
} else if (++complete >= count) {
page();
}

Categories