Pagination with node js - javascript

I'm trying to solve that problem, but it seems that I'm missing something. So, I've watched that video about backend pagination https://www.youtube.com/watch?v=ZX3qt0UWifc, I've implemented it on my project, but now I have problems implementing that to frontend. My posts are showing correctly, according to that URL(ex: "/posts?page=3&limit=5") but I have a problem with the pagination bar, that one with <Prev 1 2 3 Next>. How can I make that page numbers generate based on how many posts I have and on what page I am actually. For example if i'm on page 3, it would be <Prev 2 [3] 4 Next>. 3 is active. And If I've reached the last page, it would be <Prev 5 6 [7].
I've hard-coded three 's href, but I'm pretty sure that is not the way to do it.
If someone could help me with that problem, I would be extremely grateful. Thank you in advance!
//posts.js
function paginatedResults(model) {
return async (req, res, next) => {
const page = parseInt(req.query.page);
const limit = parseInt(req.query.limit);
const startIndex = (page - 1) * limit;
const endIndex = page * limit;
const results = {};
if (endIndex < await model.countDocuments().exec()) {
results.next = {
page: page + 1,
limit: limit
};
}
if (startIndex > 0) {
results.previous = {
page: page - 1,
limit: limit
};
}
try {
results.results = await model.find().limit(limit).skip(startIndex).exec();
res.paginatedResults = results;
next();
} catch(e) {
res.status(500).json({ message: e.message});
}
};
}
router.get("/", paginatedResults(Post), function (req, res) {
var posts = res.paginatedResults;
res.render("posts/index", {posts: res.paginatedResults, limit: 2})
});
//index.ejs
<div class="col-sm-6">
<ul class="pagination">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li class="active">1</li>
<li>2</li>
<li>3</li>
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</div>

You might consider sending the information of how many pages are there in total. For example like this:
const allResults = await model.find().exec()
res.totalPages = Math.ceil(allResults.length / limit)
...
res.render("posts/index", {totalPages: res.totalPages, posts: res.paginatedResults, limit: 2})
Then on the ejs file you can start with an empty <ul class="pagination"> tag that you will inject with this script.
<script>
function generateUrl(page, limit) {
return `http://localhost:3000/posts?page=${page}&limit=${limit}`
}
function injectPagination() {
// get current page & total pages
const urlParams = new URLSearchParams(window.location.search)
const page = +urlParams.get("page")
const totalPages = +('<%- JSON.stringify(totalPages) %>')
// get the pagination ul
const ul = document.getElementsByClassName('pagination')[0]
// handle link to page - 2
let previous = page <= 2 ? false : true
let previousList = ''
if (previous) {
previousList = `
<li>
<a href="${generateUrl(page-2, 2)}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
`
}
// handle link to page + 2
let next = page + 1 >= totalPages ? false : true
let nextList = ''
if (next) {
nextList = `
<li>
<a href="${generateUrl(page+2, 2)}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
`
}
// handle link to page - 1
let beforePageActive = page - 1 < 1 ? false : true
let beforePageActiveList = ''
if(beforePageActive) {
beforePageActiveList += `<li>${page-1}</li>`
}
// handle link to page + 1
let afterPageActive = page + 1 > totalPages ? false : true
let afterPageActiveList = ''
if(afterPageActive) {
afterPageActiveList += `<li>${page+1}</li>`
}
// handle active page
const pageActiveList = `<li class="active">[${page}]</li>`
ul.innerHTML = previousList + beforePageActiveList + pageActiveList + afterPageActiveList + nextList
}
injectPagination()
</script>

Related

[Plain javascript]My mouseover effect is buggy

I'm trying to achieve a mouse hover effect using Js on three <li>s.
The effect doesn't work on the first try, I have to keep hovering my mouse again and again to go back to its original string.
note: i linked the script at the right before </body>
HTML code:
<ul>
<li data-value="// ABOUT" ><a href="#" >// ABOUT </a></li>
<li data-value="// PROJECTS" ><a href='#' >// PROJECTS </a></li>
<li data-value="// CONTACT" ><a href="#" >// CONTACT </a></li>
</ul>
Javascript code:
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
let interval = null;
document.querySelectorAll("li").forEach(li => {
li.onmouseover = event => {
let iteration = 0;
clearInterval(interval);
interval = setInterval(() => {
event.target.innerText = event.target.innerText
.split("")
.map((letter, index) => {
if (index < iteration) {
return event.target.dataset.value[index];
}
return letters[Math.floor(Math.random() * 26)];
})
.join("");
if (iteration >= event.target.dataset.value.length) {
clearInterval(interval);
}
iteration += 1 / 3;
}, 30);
};
});
1- you don't need to use event.target because you only use arrow function.
2- the interval variable should be local to avoid conflicts in your multiples setInterval
3- your code remove all links -> ex <li><a href="#" >// ABOUT </a><li> by <li>// ABOUT <li>....
so, here it is... ( with some improvemnts )
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
document.querySelectorAll('li').forEach( li_Elm =>
{
let interval = null
, li_aLink = li_Elm.querySelector('a')
, textInit = li_aLink.textContent
, length = textInit.length
;
li_Elm.onmouseover =_=>
{
let iteration = 0;
clearInterval(interval);
interval = setInterval(() =>
{
li_aLink.textContent = Array
.from( ({length}), (letter,index) =>
( index < iteration)
? textInit[index]
: letters[Math.floor(Math.random() * 26)]
).join('');
if (iteration >= length)
clearInterval(interval);
iteration += 1 / 3;
}
, 30);
};
});
<ul>
<li><a href="#" >// ABOUT </a></li>
<li><a href='#' >// PROJECTS </a></li>
<li><a href="#" >// CONTACT </a></li>
</ul>

Star Rating Js + html with symfony

I'm working on a project with symfony 4. I want to implement a star rating system. I display 5 stars in each row in a table. I have two problems, one is more important than the other.
So the first issue is that I want to be able to retrieve the value of the star I selected. If I select 5 stars, I want to get that value back in my entity controller.
The second issue is that, currently, let's say I have 5 items in my table, so 5 rows. Currently, if I select 5 stars in one row it's selected for all rows and I can't select another value anymore. So, it's global or something.
Here is the javascript I'm using:
<script>
const stars = document.querySelectorAll('.star');
let check = false;
stars.forEach(star => {
star.addEventListener('mouseover', selectStars);
star.addEventListener('mouseleave', unselectStars);
star.addEventListener('click', activeSelect);
})
function selectStars(e) {
const data = e.target;
const etoiles = priviousSiblings(data);
if (!check) {
etoiles.forEach(etoile => {
etoile.classList.add('hover');
})
}
}
function unselectStars(e) {
const data = e.target;
const etoiles = priviousSiblings(data);
if (!check) {
etoiles.forEach(etoile => {
etoile.classList.remove('hover');
})
}
}
function activeSelect(e) {
if (!check) {
check = true;
document.querySelector('.note').innerHTML = 'Note ' + e.target.dataset.note;
}
}
function priviousSiblings(data) {
let values = [data];
while (data === data.previousSibling) {
if (data.nodeName === 'I') {
values.push(data);
}
}
return values;
}
</script>
And Here is the twig.html I'm displaying:
<td>
<i class="star" data-note="1">★</i>
<i class="star" data-note="2">★</i>
<i class="star" data-note="3">★</i>
<i class="star" data-note="4">★</i>
<i class="star" data-note="5">★</i>
<i class="note">Note:</i>
</td>
I want to be able to retrieve the value once I made a selection, and to have a different selection for each row I have.
The problem is with the "mouseover" and "mouseleave" event handlers - selectStars and unselectStars. In "selectStars", you are adding the class to only one star. And in "unselectStars", you were not resetting, or applying the "remove" class method to other stars.
Anyway, here is how I have achieved what you are trying to do:
const ratings = document.querySelectorAll('.rating');
ratings.forEach(rating =>
rating.addEventListener('mouseleave', ratingHandler)
);
const stars = document.querySelectorAll('.rating .star');
stars.forEach(star => {
star.addEventListener('mouseover', starSelection);
star.addEventListener('mouseleave', starSelection);
star.addEventListener('click', activeSelect);
});
function ratingHandler(e) {
const childStars = e.target.children;
for(let i = 0; i < childStars.length; i++) {
const star = childStars.item(i)
if (star.dataset.checked === "true") {
star.classList.add('hover');
}
else {
star.classList.remove('hover');
}
}
}
function starSelection(e) {
const parent = e.target.parentElement
const childStars = parent.children;
const dataset = e.target.dataset;
const note = +dataset.note; // Convert note (string) to note (number)
for (let i = 0; i < childStars.length; i++) {
const star = childStars.item(i)
if (+star.dataset.note > note) {
star.classList.remove('hover');
} else {
star.classList.add('hover');
}
}
}
function activeSelect(e) {
const parent = e.target.parentElement
const childStars = parent.children;
const dataset = e.target.dataset;
const note = +dataset.note; // Convert note (string) to note (number)
for (let i = 0; i < childStars.length; i++) {
const star = childStars.item(i)
if (+star.dataset.note > note) {
star.classList.remove('hover');
star.dataset.checked = "false";
} else {
star.classList.add('hover');
star.dataset.checked = "true";
}
}
const noteTextElement = parent.parentElement.lastElementChild.children.item(0)
noteTextElement.innerText = `Note: ${note}`;
}
You might notice I have a .rating class component. This is a div which I have created to hold all these "stars". Here is a link to a codepen I have created. Feel free to play around with it.
And as a note, please provide codepen (or any other) demos so that we can debug a bit better and faster.
I hope the codepen link would help you solve your problem.

JS Slider for images - Is there an easier way to do it?

I was able to make this work, but I believe I made it really dirty work here. I was wondering, is there a simpler way to do this. I'm asking for learning purposes.
Fetch is pulling json objects and the objects have an image array in them, then populates the content inside their own containers with the images. I tried to make it switch between the images with prev and next buttons.
Every container holds 4 or 5 different images.
Here is my code. I tried to stick with ES6. I don't want to use jquery on this one.
fetch('./public/js/projects.json')
.then((Response) => {
return Response.json();
}).then((data) => {
const projects = data.projects;
projects.map((project, index) => {
const container_projects = document.getElementById('projects');
const project_img = project.img
const markup_project = `
<div class="project" aria-label="${index}">
<div class="project_image">
<div class="arrow_container">
<button class="arrows left_arrow"><img src="assets/svg/left_arrow.svg" class="left"/></button>
<div class="numbers">
<span class="current">1</span>
<span class="divider">/</span>
<span class="total">${project_img.length}</span>
</div>
<button class="arrows right_arrow"><img src="assets/svg/right_arrow.svg" class="right"/></button>
</div>
<div class="image_container">
${project_img.map(image => `<img src="${image}" class=""/>`).join('')}
</div>
</div>
<small class="project_name">${project.title}</small>
</div>
`
container_projects.innerHTML += markup_project;
})
return projects
}).then((data) => {
data.map((project, index) => {
const current_project = document.querySelectorAll(`.project`);
const images = [].slice.call(current_project[index].querySelectorAll('.image_container img'));
const arrows = current_project[index].querySelectorAll('.arrows');
const current = current_project[index].querySelector('.current');
images[0].classList.add('active');
arrows.forEach((arrow) => {
arrow.addEventListener('click', (e) => {
let num;
images.map((image, index)=> {
if (image.classList.contains('active')){
image.classList.remove('active');
if (e.target.className == 'right'){
num = index + 1
} else {
num = index - 1
}
if (num >= images.length){
num = 0;
}
else if (num <= -1 ){
num = images.length - 1
}
console.log(num)
return num;
}
return num;
})
images[num].classList.add('active');
current.innerHTML = num + 1;
});
})
})
}).catch((error) => {
console.log(error);
})
Thanks for your help.

Paginition refresh issue in AngularJS

I am showing items using pagination in AngularJS.
For example, I have 12 items and number of items per page is 5. So I have 3 pages to show with the first two pages have 5 items each and the last page has two items.
When I switch pages, say from page-1 to page-2, all 10 items from both page-1 and page-2 are displayed together for a while first before the page-2 items are displayed.
Same thing happened from page-2 to page-3, all 7 items from both page-2 and page-3 are displayed together for a while first before the page-3 items are displayed.
Whenever I switch pages, the same thing is observed.
What could be wrong?
My code is as follow.
html
<div class="adds-wrapper">
<div ng-show="Available">
<div class="item-list" ng-repeat="hotel in pagedItems[currentPage]">
<!-- this is how items are displayed -->
</div>
<div class="pagination-bar">
<ul class="pagination">
<li ng-class="{disabled: currentPage == 0}">
<a class="pagination-btn" href ng-click="prevPage()">« Prev</a></li>
<li ng-repeat="n in range(pagedItems.length)" ng-class="{active: n == currentPage}" ng-click="setPage()">
<a href ng-bind="n + 1">1</a>
</li>
<li ng-class="{disabled: currentPage == pagedItems.length - 1}">
<a class="pagination-btn" href ng-click="nextPage()">Next »</a></li>
</ul>
</div>
Javascript
function groupToPages() {
$scope.pagedItems = [];
for (var i = 0; i < $scope.filteredItems.length; i++) {
var j = Math.floor(i / $scope.itemsPerPage);
if (i % $scope.itemsPerPage === 0) {
$scope.pagedItems[j] = [$scope.filteredItems[i]];
} else {
$scope.pagedItems[j].push($scope.filteredItems[i]);
}
}
};
var loadPagination = function () {
$scope.sortingOrder = $scope.sortingOrder;
$scope.reverse = false;
$scope.filteredItems = [];
$scope.groupedItems = [];
$scope.itemsPerPage = 5;
$scope.pagedItems = [];
$scope.currentPage = 0;
$scope.items = $scope.HotelAndResorts;
$scope.filteredItems = $scope.HotelAndResorts;
$scope.hotelAvailable = true;
if ($scope.HotelAndResorts) {
if ($scope.HotelAndResorts.length == 0) {
$scope.hotelAvailable = false;
$scope.errorMessage = "No Post Found.";
}
} else {
$scope.hotelAvailable = false;
$scope.errorMessage = "No Post Found."
}
/*var searchMatch = function (haystack, needle) {
if (!needle) {
return true;
}
return haystack.toLowerCase().indexOf(needle.toLowerCase()) !== -1;
};*/
groupToPages();
$scope.range = function (start, end) {
var ret = [];
if (!end) {
end = start;
start = 0;
}
for (var i = start; i < end; i++) {
ret.push(i);
}
return ret;
};
$scope.prevPage = function () {
if ($scope.currentPage > 0) {
$scope.currentPage--;
}
};
$scope.nextPage = function () {
if ($scope.currentPage < $scope.pagedItems.length - 1) {
$scope.currentPage++;
}
};
$scope.setPage = function () {
$scope.currentPage = this.n;
};
$scope.sort_by = function (newSortingOrder) {
if ($scope.sortingOrder == newSortingOrder)
$scope.reverse = !$scope.reverse;
$scope.sortingOrder = newSortingOrder;
// icon setup
$('th i').each(function () {
// icon reset
$(this).removeClass().addClass('icon-sort');
});
if ($scope.reverse)
$('th.' + new_sorting_order + ' i').removeClass().addClass('icon-chevron-up');
else
$('th.' + new_sorting_order + ' i').removeClass().addClass('icon-chevron-down');
};
}
When you paginate after updating the paged items try calling $scope.apply() this tells AngularJS to look fixup various javascript things. Angular does some sorcery under the hood to make Javascript behave asynchronously, apply makes thing sync up. I'm oversimplifying, if you like you can read the exact documentation, but I find when htis kind of thing happens 95% of the time it's because I didn't apply.

backbone pagination current page bug

im trying to get a pagination in backbone/underscore to work properly. It works fine on init and on next/prev navigation.
But when i try to set the current page on run time it flips out and renders the wrong page numbers. It only gets wrong if i set the current page to something more than 8. Everyting under 8 works fine. Here is my template, it renders only 10 page numbers at a time, credits to
Rida BENHAMMANE!
Template:
<script type="text/template" id="pagination_template">
<section class="pagination">
<ul>
<li>
<div class="paging prev">◄</div>
</li>
<%
var renderPages;
for (var i = 0; i <= pages-1; i++) {
renderPages = false;
if (pages < 10) {
renderPages = true;
} else {
if (current <= 5) {
if (i < 10) {
renderPages = true;
}
} else if (current <= pages - 5) {
if ((current - 5) < i && (current + 5) > i) {
renderPages = true;
}
} else {
if ((pages - 9) < i) {
renderPages = true;
}
}
};
if(renderPages) { %>
<li>
<a href="#" data-offset="<%= i*9 %>" data-page="<%= i %>">
<%= i+1 %>
</a>
</li>
<% }
}
%>
<li>
<div class="paging next">►</div>
</li>
</ul> <br><br>
total pages <%= pages %>
</section>
</script>
And here is the function the changes the current page and renders the navigaton.
Current pages is set to a data-attribute of the clicked element.
updatePageNum: function () {
var self = this;
self.totalModel.fetch({
success:function(model,response) {
var total = response.data;
var p = total/self.perPage;
var r = total-Math.round(p)
self.maxPage = Math.ceil(p);
self.pagination = _.template($("#pagination_template").html(), {
pages:self.maxPage,
current:self.currentPage
});
}
});
},
Anyone that can help on this?
As it has been solved in the comments, I'm posting it as an answer so it can be accepted.
You should wrap your numbers with parseInt():
var total = parseInt(response.data, 10);
var p = parseInt(total/self.perPage, 10);
var r = parseInt(total-Math.round(p), 10);
Cheers.

Categories