I'm writing a paginated table with a page selector at the bottom that displays the different page numbers
I'm using knockout. The numbers are coming from a ko.computed array (self.pages) that calculates how many pages there are based on the number of results / results per page. The problem I'm running into is if the data array is very long and the results per page is set somewhat low, I get something like this:
What I want to do is limit the number of menu items to three, so if page #4 is selected, only items 3,4,5 are visible. Currently I'm implementing a second ko.computed that first retrieves the value of self.pages, then gets the value of the current page number (self.pageNumber), and slices the array so that only 3 items are returned:
self.availablePages = ko.computed(function() {
var pages = self.pages();
var current = self.pageNumber();
if (current === 0) {
return pages.slice(current, current + 3);
} else {
return pages.slice(current - 1, current + 2);
}
});
Now all of this seems to be working fine but there's one bug I have not been able to stamp out. Using the knockout css data-bind, I'm telling it to assign a class of 'selected' to whichever element holds the same value as self.pageNumber (see code below).
If the element selected does not require self.availablePages to change (i.e. selecting 2 when 1 was the previous selection), there are no problems; 2 becomes selected and 1 becomes un-selected.
However, if the selection does require self.availablePages to change (i.e. 1,2,3 visible, selecting 3 will change visible to 2,3,4), the correct numbers display, but instead of 3 being selected, 4 is selected. I'm assuming this is because the index of the array that 3 used to be located at (last) is now being occupied by 4.
Here's the menu:
<ul data-bind="foreach: availablePages">
<li data-bind="if: $index() < 1">
<a data-bind="click: $parent.toFirstPage">First</a>
</li>
<li>
<a data-bind="text: displayValue, click: $parent.goToPage(iterator), css: { selected: $parent.pageNumber() === iterator }"></a>
</li>
<li data-bind="if: $parent.isLastIteration($index)">
<a data-bind="click: $parent.toLastPage">Last</a>
</li>
</ul>
The array being iterated over was originally just an array of numbers, but in trying to fix this bug I changed it to be an array of the following object:
available.MenuModel = function(iterator) {
var self = this;
self.displayValue = iterator + 1;
self.iterator = iterator;
self.isSelected = ko.observable(false);
}
One thing I tried doing was adding the self.isSelected observable to all items in the menu, and then when self.availablePages gets re-computed, the function checks what the pageNumber is and then finds which item in the array matches that and sets self.isSelected(true), and then tried keying the css binding to that.
Unfortunately this did not work; it still has the exact same bug. I've been debugging the script like crazy and there doesn't seem to be an issue; everything seems to know that 3 should be selected, but what's actually selected is 4.
I'm guessing that the knockout bindings aren't smart enough to keep up with this. Is there something I can do or some pattern that would help knockout keep track of which element should be selected? I even tried taking knockout out of it completely, and had a function in the script manually remove/add the 'selected' class whenever self.pageNumber was changed and/or whenever self.availablePages changed but I still got the same issue, so maybe this isn't a knockout issue but something with javascript.
I've tried everything else I can think of; subscribing to various observables, promises, but like I said everything already knows what should be selected so additional checks and callbacks aren't altering anything nor eliminating the bug.
I'm hoping someone will either know the cause/solution of the bug or a smarter way to accomplish the task. This is the self.pages that self.availablePages keys off of, in case that's helpful:
self.pages = ko.computed(function() {
var start = self.totalPages();
var pages = [];
for (var i = 0; i < start + 1; ++i)
pages.push(new available.MenuModel(i));
return pages;
});
This is the entire javascript model (using requireJs):
define(['underscore', 'knockout'], function(_, ko) {
var available = available || {};
available.DynamicResponsiveModel = function(isDataObservable, isPaginated) {
var self = this;
self.workingArray = ko.observableArray([]);
self.backgroundArray = ko.observableArray([]);
self.pageNumber = ko.observable(0);
self.count = function () {
return 15;
}
self.resultsPerPage = ko.observable(self.count());
self.selectResultsPerPage = [25, 50, 100, 200, 500];
self.resultsPerPageOptions = ko.computed(function () {
return self.selectResultsPerPage;
});
self.activeSortFunction = isDataObservable ? available.sortAlphaNumericObservable : available.sortAlphaNumeric;
self.resetPageNumber = function() {
self.pageNumber(0);
}
self.initialize = function(data) {
var sortedList = data.sort(function(obj1, obj2) {
return obj2.NumberOfServices - obj1.NumberOfServices;
});
self.workingArray(sortedList);
self.backgroundArray(sortedList);
self.pageNumber(0);
}
self.intializeWithoutSort = function(data) {
self.workingArray(data);
self.backgroundArray(data);
self.pageNumber(0);
}
self.totalPages = ko.computed(function() {
var num = Math.floor(self.workingArray().length / self.resultsPerPage());
num += self.workingArray().length % self.resultsPerPage() > 0 ? 1 : 0;
return num - 1;
});
self.paginated = ko.computed(function () {
if (isPaginated) {
var first = self.pageNumber() * self.resultsPerPage();
return self.workingArray.slice(first, first + self.resultsPerPage());
} else {
return self.workingArray();
}
});
self.pages = ko.computed(function() {
var start = self.totalPages();
var pages = [];
for (var i = 0; i < start + 1; ++i)
pages.push(new available.MenuModel(i));
return pages;
});
self.availablePages = ko.computed(function() {
var pages = self.pages();
var current = self.pageNumber();
if (current === 0) {
return pages.slice(current, current + 3);
} else {
return pages.slice(current - 1, current + 2);
}
});
self.pageNumDisplay = ko.computed(function() {
return self.pageNumber() + 1;
});
self.hasPrevious = ko.computed(function() {
return self.pageNumber() !== 0;
});
self.hasNext = ko.computed(function() {
return self.pageNumber() !== self.totalPages();
});
self.next = function() {
if (self.pageNumber() < self.totalPages()) {
self.pageNumber(self.pageNumber() + 1);
}
}
self.previous = function() {
if (self.pageNumber() != 0) {
self.pageNumber(self.pageNumber() - 1);
}
}
self.toFirstPage = function() {
self.pageNumber(0);
}
self.toLastPage = function() {
self.pageNumber(self.totalPages());
}
self.setPage = function(data) {
return new Promise(function(resolve, reject) {
self.pageNumber(data);
});
}
self.goToPage = function(data) {
self.pageNumber(data);
}
self.isLastIteration = function (index) {
var currentIndex = index();
var count = self.pages().length;
return currentIndex === count - 1;
}
self.resultsPerPage.subscribe(function() {
self.pageNumber(0);
});
self.filterResults = function (filterFunction) {
self.resetPageNumber();
self.workingArray(filterFunction(self.backgroundArray()));
}
self.resetDisplayData = function() {
self.workingArray(self.backgroundArray());
}
self.updateVisibleResults = function(data) {
self.workingArray(data);
}
}
available.sortAlphaNumericObservable = function () {
//...
}
available.sortAlphaNumeric = function () {
//...
}
return available;
});
Here's the entire table:
<div data-bind="visible: showListOfEquipment, with: availableEquipmentModel">
<section class="panel panel-default table-dynamic">
<table class="primary-table table-bordered">
<thead>
<tr>
<th>
<div class="th">
Part Number
<span class="fa fa-angle-up" data-bind="click: function () { sortByFirstColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortByFirstColumn(true); }"></span>
</div>
</th>
<th>
<div class="th">
Serial Number
<span class="fa fa-angle-up" data-bind="click: function () { sortBySecondColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortBySecondColumn(true); }"></span>
</div>
</th>
<th>
<div class="th">
Type
<span class="fa fa-angle-up" data-bind="click: function () { sortByThirdColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortByThirdColumn(true); }"></span>
</div>
</th>
<th>
<div class="th">
Equipment Group
<span class="fa fa-angle-up" data-bind="click: function () { sortByFourthColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortByFourthColumn(true); }"></span>
</div>
</th>
<th>
<div class="th">
Operational
<span class="fa fa-angle-up" data-bind="click: function () { sortByFifthColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortByFifthColumn(true); }"></span>
</div>
</th>
<th>
<div class="th">
Valid
<span class="fa fa-angle-up" data-bind="click: function () { sortBySixthColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortBySixthColumn(true); }"></span>
</div>
</th>
</tr>
</thead>
<tbody data-bind="foreach: paginated">
<tr>
<td data-bind="text: $data.PartNumber"></td>
<td><a target="_blank" data-bind="text: $data.SerialNumber, click: function () { $root.setSerialNumberAndFindEquipment(SerialNumber) }" style="color:royalblue"></a></td>
<td data-bind="text: $data.Type"></td>
<td data-bind="text: $data.EquipmentGroup"></td>
<td>
<span data-bind="css: $root.operationalCss($data), text: $root.getOpStatus($data)"></span>
</td>
<td data-bind="text: $data.Validity"></td>
</tr>
</tbody>
</table>
<footer class="table-footer">
<div class="row">
<div class="col-md-6 page-num-info">
<span>Show <select style="min-width: 40px; max-width: 50px;" data-bind="options: selectResultsPerPage, value: resultsPerPage"></select> entries per page</span>
</div>
<div class="col-md-6 text-right pagination-container">
<ul class="pagination-sm pagination" data-bind="foreach: pages">
<li data-bind="if: $index() < 1"><a data-bind="click: $parent.toFirstPage">First</a> </li>
<li class="paginationLi"><a data-bind="text: displayValue, click: $parent.goToPage(iterator), css: { selected: isSelected }"></a></li>
<li data-bind="if: $parent.isLastIteration($index)"> <a data-bind="click: $parent.toLastPage">Last</a> </li>
</ul>
</div>
</div>
</footer>
</section>
I went ahead and built a paginator. Instead of using an array as you did, I used just the number of available pages, pageCount.
Probably the only thing worth looking into in more detail is the calculation which pages are to be displayed:
this.visiblePages = ko.computed(function() {
var previousHalf = Math.floor( (this.visiblePageCount() - 1) / 2 ),
nextHalf = Math.ceil( (this.visiblePageCount() - 1) / 2 ),
visiblePages = [],
firstPage,
lastPage;
// too close to the beginning
if ( this.currentPage() - previousHalf < 1 ) {
firstPage = 1;
lastPage = this.visiblePageCount();
if ( lastPage > this.pageCount() ) {
lastPage = this.pageCount();
}
// too close to the end
} else if ( this.currentPage() + nextHalf > this.pageCount() ) {
lastPage = this.pageCount();
firstPage = this.pageCount() - this.visiblePageCount() + 1;
if (firstPage < 1) {
firstPage = 1;
}
// just right
} else {
firstPage = this.currentPage() - previousHalf;
lastPage = this.currentPage() + nextHalf;
}
for (var i = firstPage; i <= lastPage; i++) {
visiblePages.push(i);
}
return visiblePages;
}, this);
Let's go through this piece by piece. We want our current page to be in the middle of all displayed pagination buttons, with some to its left and some to its right. But how many?
If we use an odd number such as three, that's simple: the number minus 1 (the selected one) divided by two. (3 - 1) / 2 = 1, or one to each side.
With an even number of pagination buttons to display, that doesn't work, so we calculate each side individually and round one result up and one result down:
var previousHalf = Math.floor( (this.visiblePageCount() - 1) / 2 ),
nextHalf = Math.ceil( (this.visiblePageCount() - 1) / 2 ),
There are three possible results:
our selection fits
we're too close to the beginning
we're too close to the end
If we're too close to the beginning:
if ( this.currentPage() - previousHalf < 1 ) {
firstPage = 1;
lastPage = this.visiblePageCount();
if ( lastPage > this.pageCount() ) {
lastPage = this.pageCount();
}
}
we start with 1 and try to display pages 1 up to visiblePageCount. If that doesn't work either, because we don't have enough pages, we simply display all we have.
If we're too close to the end:
} else if ( this.currentPage() + nextHalf > this.pageCount() ) {
lastPage = this.pageCount();
firstPage = this.pageCount() - this.visiblePageCount() + 1;
if (firstPage < 1) {
firstPage = 1;
}
}
we end with the last page and try to display as many as we need to the left. If that doesn't work, because we don't have enough pages, we simply display all we have.
Here's the full example:
var ViewModel;
ViewModel = function ViewModel() {
var that = this;
this.pageCount = ko.observable(20);
this.currentPage = ko.observable(1);
this.visiblePageCount = ko.observable(3);
this.gotoPage = function gotoPage(page) {
that.currentPage(page);
};
this.visiblePages = ko.computed(function() {
var previousHalf = Math.floor( (this.visiblePageCount() - 1) / 2 ),
nextHalf = Math.ceil( (this.visiblePageCount() - 1) / 2 ),
visiblePages = [],
firstPage,
lastPage;
if ( this.currentPage() - previousHalf < 1 ) {
firstPage = 1;
lastPage = this.visiblePageCount();
if ( lastPage > this.pageCount() ) {
lastPage = this.pageCount();
}
} else if ( this.currentPage() + nextHalf > this.pageCount() ) {
lastPage = this.pageCount();
firstPage = this.pageCount() - this.visiblePageCount() + 1;
if (firstPage < 1) {
firstPage = 1;
}
} else {
firstPage = this.currentPage() - previousHalf;
lastPage = this.currentPage() + nextHalf;
}
for (var i = firstPage; i <= lastPage; i++) {
visiblePages.push(i);
}
return visiblePages;
}, this);
};
ko.applyBindings( new ViewModel() );
ul {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
margin: 0;
padding: 0;
list-style-type: none;
}
ul li {
-webkit-box-flex: 0;
-webkit-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
}
button {
margin-right: 0.5rem;
padding: 0.5rem;
background-color: lightgrey;
border: none;
}
button.selected {
background-color: lightblue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul>
<li><button data-bind="click: gotoPage.bind($data, 1)">First</button></li>
<!-- ko foreach: visiblePages -->
<li>
<button data-bind="text: $data,
click: $parent.gotoPage,
css: { selected: $parent.currentPage() === $data }"></button>
</li>
<!-- /ko -->
<li><button data-bind="click: gotoPage.bind($data, pageCount())">Last</button></li>
</ul>
Related
I'm busy developing a wordpress plugin to look for numbers and hide them by formating the number and replacing it with 0000..., Example:
<a href="tel:0000000000">
<span>
<span>0000 000 000</span>
</span>
</a>
I have javascript that queries the <a href=""> tag. I then get the children of the a tag. However, my issue is that because I don't know what or how many children ill be working with i can't assume it will be 1 or 2 thus I have to predict and look for it.
Javascript code:
// REMOVE SPACES IN STRING
let replaceStr = function (self) {
let value = self.replace(/[- )(]/g, '')
return value
};
// REMOVE LETTERS FROM STRING
let rmLetters = function (self) {
// let value = self.replace( /^\D+/g, '')
let value = self.replace(/\D+%?/g, "");
return value
}
let a = document.querySelectorAll("a[href^='tel:'], a[href^='Tel:'], a[href^='callto:']");
for (var i = 0; i < a.length; i++) {
let hrefSlice = a[i].href.slice(4);
let countChildren = a[i].childElementCount
if (a[i].hasChildNodes()) {
let a_childNodes = a[i].children;
if (a_childNodes.length > 1) {
for (let l = 0; l < a_childNodes.length; l++) {
if (replaceStr(a_childNodes[l].textContent) === hrefSlice) {
a_childNodes[l].textContent = replaceStr(a_childNodes[l].textContent).slice(0, 4) +
"...Click Here";
} else if (replaceStr(rmLetters(a_childNodes[l].textContent)) === hrefSlice) {
a_childNodes[l].textContent = replaceStr(rmLetters(a_childNodes[l].textContent)).slice(
0, 4) + "...Click Here";
}
}
}
}
}
}
Not sure if I got you right but I'd do it like this:
document.querySelector('#hideButton').addEventListener('click', () => {
const phoneAnchors = document.querySelectorAll('a[href^="tel:"], a[href^="Tel:"], a[href^="callto:"]');
phoneAnchors.forEach((phoneAnchor) => {
const phoneNumber = phoneAnchor.href.split(':')[1] || '';
phoneAnchor.href = phoneAnchor.href.replace(/[0-9]/g, '0');
phoneAnchor.querySelectorAll('*').forEach(childNode => {
if (childNode.textContent.replace(/[ ]/g, '') === phoneNumber) {
childNode.textContent = childNode.textContent.replace(/[0-9]/g, '0');
}
});
});
});
a {
display: block;
}
<a href="tel:1234567890">
<span>
<span>1234 567 890</span>
</span>
</a>
<a href="tel:0987654321">
<span>
<span>0987 654 321</span>
</span>
</a>
<a href="tel:1122334455">
<span>
<span>1122334455</span>
</span>
</a>
<hr>
<button id="hideButton">Hide Phone Numbers</button>
Using HTML and javascript I have some basic search result pagination which, although works, if there are hundreds of results, I can end up with pagination looking like this:
I would like to limit this to show only 1 - 10, then 11 - 10 etc, can anyone direct me to an example of how to do this?
<div class="row igs-learning-paths-pagination-row">
<div class="col-12 d-flex justify-content-center" >
<ul class="pagination pagination-info">
<li class="page-item">
<a href="javascript:void(0)" onclick="displayPreviousPage()" class="igs-learning-paths-pagingation-text igs-text-uppercase">
#Umbraco.GetDictionaryValue("Common Prev", "Prev..").ToUpper()
</a>
</li>
#for (int i = 0; i <= #Model.Results.Count() / numberPerPage; i++)
{
<li class="#(i == 0 ? "active" : null) page-item non-generate-page-item" id="page-list-item-#(i)" style="border-radius:16px;">
<a class="page-link" id="page-number-#(i)" href="javascript:void(0)" onclick="displayPages(#(i))">#(i + 1)</a>
</li>
}
<li class="page-item" alt="Go forward a page" title="Go forward a page">
<a href="javascript:void(0)" alt="Go forward a page" title="Go forward a page"
onclick="displayNextPage()"
class="igs-learning-paths-pagingation-text igs-text-uppercase search-result-margin">
#Current.UmbracoHelper.GetDictionaryValue("Common Next", "Next..").ToUpper()
</a>
</li>
</ul>
</div>
</div>
<script>
var numberPerPage = #numberPerPage;
var totalResults = #Model.Results.Count();
var maxPage = Math.floor(totalResults / numberPerPage);
var currentPage = 0;
var previousPage = 0;
function displayNextPage() {
if (currentPage == maxPage) return;
currentPage = currentPage + 1
displayPages(currentPage);
}
function displayPreviousPage() {
if (currentPage == 0) return;
currentPage = currentPage - 1
displayPages(currentPage);
}
function displayPages(pageToDisplay) {
$(".page-search-hider").hide();
currentPage = pageToDisplay;
if (!pageToDisplay) {
pageToDisplay = 0
}
else {
var skip = pageToDisplay * numberPerPage;
pageToDisplay = skip+1;
}
var newNumberPerPage = numberPerPage;
document.getElementById("page-list-item-" + previousPage).classList.remove("active")
previousPage = currentPage;
document.getElementById("page-list-item-" + currentPage).classList.add("active")
for (var i = 0; i <= newNumberPerPage; i++) {
var result = pageToDisplay + i;
$("#pageId-" + result).show();
$("#pageId-" + #(pageIdCount)).hide();
}
}
const pageNumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const maxPageLimit = 5;
let upperPageIndex = maxPageLimit;
let currentPageIndex = 0;
let displayPages = pageNumbers.slice(currentPageIndex , upperPageIndex); // slice(startIndex, endIndex);
console.log(displayPages)
// if currentPageIndex = 2 then show 2 more as you go through
upperPageIndex += 2; // You can make 2 a constant if needed
currentPageIndex += 2;
displayPages = pageNumbers.slice(currentPageIndex , upperPageIndex);
console.log(displayPages)
REF: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
var totalItems = $("#resultsList").children().length;
var itemsPerPage = 10;
var pages = Math.ceil(totalItems / itemsPerPage);
function createPagination(pages, page) {
var str = '<ul class="paginatorList">';
var active;
var pageCutLow = page - 1;
var pageCutHigh = page + 1;
var prevButton = '<li class=\'page-item previous\'><a onclick=\'createPagination(pages, ' + (page - 1) + ');\'><</a></li>';
var nextButton = '<li class="page-item next"><a onclick="createPagination(pages, ' + (page + 1) + ');">></a></li>';
var firstPage = '<li class="page-item page-item"><a onclick="createPagination(pages, 1);">1</a></li>';
var ellipseL = '<li class="page-item out-of-range"><a onclick="createPagination(pages, ' + (page - 2) + ');">...</a></li>';
var ellipseR = '<li class="page-item out-of-range"><a onclick="createPagination(pages, ' + (page + 2) + ');">...</a></li>';
// Show the Previous button only if you are on a page other than the first
if (page > 1) { str += prevButton; }
// Show all the pagination elements if there are less than 6 pages total
if (pages < 6) {
for (let p = 1; p <= pages; p++) {
active = page == p ? " active" : '';
var activePage = "<li class=\"page-item" + active + "\"><a onclick=\"createPagination(pages, " + p + ")\">" + p + "</a></li>";
str += activePage;
}
}
// Use "..." to collapse pages outside of 3 pages range
else {
// Show the very first page followed by a "..." at the beginning of the pagination section (after the Previous button)
if (page > 2) {
str += firstPage;
if (page > 3) {
str += ellipseL;
}
}
// Determine how many pages to show after the current page index
(page === 1) ? pageCutHigh += 2 : (page === 2) ? pageCutHigh += 1 : null;
// Determine how many pages to show before the current page index
(page === pages) ? pageCutLow -= 2 : (page === pages-1) ? pageCutLow -= 1 : null;
// Output the indexes for pages that fall inside the range of pageCutLow and pageCutHigh
for (let p = pageCutLow; p <= pageCutHigh; p++) {
if (p === 0) { p += 1; }
if (p > pages) { continue }
active = (page == p) ? " active" : '';
var activePage = "<li class=\"page-item" + active + "\"><a onclick=\"createPagination(pages, " + p + ")\">" + p + "</a></li>";
str += activePage;
}
// Show the very last page preceded by a "..." at the end of the pagination section (before the Next button)
if (page < pages-1) {
if (page < pages-2) {
str += ellipseR;
}
str += '<li class="page-item no"><a onclick="createPagination(pages, pages)">'+pages+'</a></li>';
}
}
// Show the Next button only if you are on a page other than the last
if (page < pages) { str += nextButton; }
str += '</ul>';
// Return the pagination string to be outputted
$("div[id^=pager]").html(str);
listFilter();
return str;
}
function listFilter() {
window.scrollTo(0,0);
function _toConsumableArray(arr) {
if (Array.isArray(arr)) {
for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) {
arr2[i] = arr[i];
}
return arr2;
} else {
return Array.from(arr);
}
}
var list = document.querySelectorAll('.resultsItem');
var items = [].concat(_toConsumableArray(list));
var currentPgVal = parseInt(document.querySelector('.page-item.active a').textContent);
var rangeMax = (currentPgVal) * Number(itemsPerPage) - 1;
var rangeMin = (rangeMax - Number(itemsPerPage)) + 1;
for (var item in items) {
if (item <= rangeMax && item >= rangeMin) {
var sortDir = $('#filterBtn').find('i[class$=active]').data('sortdir');
var sortOption = $('#filterOptions').find(':selected').val();
tinysort('.resultsItem', { order: '' + sortDir, selector: '.' + sortOption });
items[item].classList.remove("hidden_apa");
}
}
}
// Sort when Icon clicked
$('#filterBtn i').on('click', function () {
var currentPgVal = parseInt(document.querySelector('.page-item.active a').textContent);
var rangeMax = (currentPgVal) * Number(itemsPerPage) - 1;
var rangeMin = (rangeMax - Number(itemsPerPage)) + 1;
var sortDirection = $('#filterBtn').find('i[class$=active]').data('sortdir');
var sortOption = $('.filterOptions').find(':selected').val();
// toggle displaying the two icons, A-Z or Z-A
$('i[class*=fa-sort-alpha]').toggleClass('hidden active');
// Hide all the items
$('.resultsItem').removeClass('active').addClass('hidden');
// Get the total list
var results = document.querySelectorAll('.resultsItem');
results.forEach(
function(val, i, listObj) {
console.log(i, val, this);
if(i >= rangeMin && i <= rangeMax) {
tinysort('.resultsItem', { order: '' + sortDirection, selector: '.' + sortOption });
// console.log(i, val);
$(this).removeClass('hidden');
}
});
});
$(document).ready(function () {
$("div[id^=pager]").html(createPagination(pages, 1));
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinysort/2.3.6/tinysort.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="filterOptions">
<select name="filterMenu" class="searchFieldMenu">
<option value="firstName" data-sortby="first">First Name</option>
<option value="lastName" data-sortby="last">Last Name</option>
<option value="cityName" data-sortby="city">City</option>
<option value="stateName" data-sortby="state">State</option>
</select>
<div id="filterBtn">
<i class="fa fa-sort-alpha-asc active" data-sortdir="asc" aria-hidden="true"></i>
<i class="fa fa-sort-alpha-desc hidden_apa" data-sortdir="desc" aria-hidden="false"></i>
</div>
</div>
<ul id="resultsList">
<li class="resultsItem">
<div class="infoBox">
<h2 class="firstName">Zack</h2>
<h2 class="lastName">Last Name A</h2>
<div class="title">Manager</div>
<div class="company">Walmart</div>
<div class="location">
<span class="cityName">Juno</span>,
<span class="stateName">AK</span>
<br>United States</div>
</div>
</li>
</ul>
<div class="pagination" id="pager">
<ul class="paginatorList">
<li class="page-item active">
<a onclick="createPagination(pages, 1)">1</a>
</li>
<li class="page-item">
<a onclick="createPagination(pages, 2)">2</a>
</li>
</ul>
</div>
POST UPDATE
I've included all the relevant JS to allow the snippet to work. There will have to be many results added to the ul but I thought that was TOO Much code to have displayed.
I'm trying to sort a list of results after an icon is clicked. The expected result should be to hide all the elements in the UL and then unhide the results which meet the condition.
The condition is based on pagination and I'm using Tinysort to sort the list.
So for example, Say you are on page 1 of 3 and each page will only display 10 results. When you click on the Icon to sort the entire list A-Z then the results to be displayed are items whose index is within range of that page. So page 1's range is 1 to 10.
I'm close and I'm not able to remove the hidden class on the items that meet the criteria.
I hope I explained this well. I'll add my code so far.
the REAL problem I believe is in the NodeList or the .forEach Loop.
I had to slightly modify your code to make it work (before you posted the update).
Then I've made it to sort:
var itemsPerPage = 2;
// Sort when Icon clicked
$('#filterBtn i').on('click', function() {
var currentPgVal = parseInt(document.querySelector('.page-item.active a').textContent);
var rangeMax = currentPgVal * Number(itemsPerPage) - 1;
var rangeMin = (rangeMax - Number(itemsPerPage)) + 1;
var sortDirection = $('#filterBtn').find('i[class$=active]').data('sortdir');
var sortOption = $('.filterOptions').find(':selected').val();
// toggle displaying the two icons, A-Z or Z-A
$('i[class*="fa-sort-alpha"]').toggleClass('hidden_apa active');
var results = document.querySelectorAll('.resultsItem');
// sort
tinysort(results, {
order: sortDirection
})
// Hide out-of-range elements
.forEach(function(val, i) {
console.log(i, val.innerText);
$(val)[(
i < rangeMin || i > rangeMax ?
'add' : 'remove'
) + 'Class']('hidden_apa');
});
});
.hidden_apa {
display: none !important;
}
.active {
display: inline-block;
color: red
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinysort/2.3.6/tinysort.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="filterBtn">
<i class="fa fa-sort-alpha-asc hidden_apa" data-sortdir="asc" aria-hidden="true"></i>
<i class="fa fa-sort-alpha-desc active" data-sortdir="desc" aria-hidden="false"></i>
</div>
<ul id="resultsList">
<li class="resultsItem">Item 1</li>
<li class="resultsItem">Item 2</li>
<li class="resultsItem hidden_apa">Item 3</li>
<li class="resultsItem hidden_apa">Item 4</li>
<li class="resultsItem hidden_apa">Item 5</li>
<li class="resultsItem hidden_apa">Item 6</li>
</ul>
<div class="pagination" id="pager">
<span class="page-item active"><a>1</a></span>
<span class="page-item"><a>2</a></span>
<span class="page-item"><a>3</a></span>
</div>
I'm trying to sort a list of physicians on a page by name. There is a dropdown and the user can select ascending or descending. Neither one is working. The UI is not updating at all.
EDIT: I've changed the sort code to follow the example on the KO website.
When i step into the code i get an error when I hover over left and it says "'left' is not defined"
function SortDownloadPhysicians(){
var sortDirection = getSortDirection()
var sortByParam = getSortByParam()
if(sortByParam === "name"){
if(sortDirection === "ascending"){
self.physicians().sort(function (left, right) { return left.name == right.name ? 0 : (left.name < right.name ? -1 : 1) });
}else{
self.physicians().sort(function (left, right) { return left.name == right.name ? 0 : (left.name > right.name ? -1 : 1) });
}
}
else{ //date of birth
if(sortDirection === "ascending"){
self.physicians().sort(function (left, right) { return left.dateOfBirth == right.dateOfBirth ? 0 : (left.dateOfBirth < right.dateOfBirth ? -1 : 1) });
}else{
self.physicians().sort(function (left, right) { return left.dateOfBirth == right.dateOfBirth ? 0 : (left.dateOfBirth > right.dateOfBirth ? -1 : 1) });
}
}
Here's my sort function
function sortDownloadPage() {
var sortDirection = getSortDirection();
var sortBy = getSortBy();
// sort by name
if (sortDirection === "ascending") {
self.physicians().sort(function (a, b) {
if (a.name().toLowerCase() > b.name().toLowerCase()) {
return 1;
}
if (a.name().toLowerCase() < b.name().toLowerCase()) {
return -1;
}
// a must be equal to b
return 0;
});
} else {
self.physicians().sort(function (a, b) {
if (a.name().toLowerCase() < b.name().toLowerCase()) {
return 1;
}
if (a.name().toLowerCase() > b.name().toLowerCase()) {
return -1;
}
// a must be equal to b
return 0;
});
}
};
and here's part of the view model
var ViewModels = ViewModels || {};
(function (ViewModels) {
var DownloadVM = function (options) {
options = $.extend({
physicians: ko.utils.unwrapObservable(options.physicians || [])
}, options);
//********************************************************************************
// Private properties
var self = this,
_physicians = ko.observableArray([]),
_page = 1,
_pageSize = 2;
//********************************************************************************
// Public properties
self.physicians = ko.computed(function () {
return ko.utils.unwrapObservable(_physicians);
});
UI code
<div class="grid-list-group" data-bind="template: { name: 'physicianDownloadTemplate', foreach: physicians }">
<script type="text/html" id="physicianDownloadTemplate">
<div class="group-item clearfix" data-bind="fxVisibility: visible">
<div class="item-col selector">
<input type="checkbox" data-bind="checked: checked">
</div>
<div class="item-col photo hidden-sm hidden-xs" data-bind="click: $root.toggleOpenState">
<img class="rounded" title="Profile Picture" src="#Url.Content("~/Content/Images/profile-default.png")">
</div>
<div class="item-col field name" onclick="$(this).parent().children('.group-item-drawer').slideToggle();">
<div class="caption">Physician Name</div>
<div class="value" data-bind="text: name">{NAME}</div>
</div>
<div style="float: right;">
<div class="item-col field date-of-birth">
<div class="caption">Date of Birth</div>
<div class="value" data-bind="text: dateOfBirth">{DATE OF BIRTH}</div>
</div>
</div>
<div class="group-item-drawer clearfix" style="display: none; clear: both;" data-bind="template: { name: 'downloadItemTemplate', foreach: files }"></div>
</div>
</script>
I typically solve this problem using a computed function. The computed can subscribe to the "sorting" variable, so when it changes, it will recompute based on the new variable. From there, it is simply a matter of returning the appropriate sorting.
function vm() {
var physicians = [
'smith',
'grant',
'foreman'
];
this.sorting = ko.observable();
this.sortingOptions = ['A-Z', 'Z-A'];
this.physicians = ko.computed(function() {
var sorting = this.sorting(),
sorted = physicians.slice().sort();
if (sorting == 'Z-A')
sorted.reverse();
return sorted;
});
}
ko.applyBindings(new vm());
and in the view
<select data-bind="options: sortingOptions, value: sorting"></select>
<select data-bind="options: physicians"></select>
I am implementing a search using razor in Umbraco. I am trying to implement paging on the search results - I have used MVCPagedList but it is not working. I have also tried some javascript code but this does not work either.
My code:
#using Examine.LuceneEngine.SearchCriteria
#inherits Umbraco.Web.Macros.PartialViewMacroPage
#using PagedList
#using PagedList.Mvc;
#{
int? page = 10;
//PerformSearch performSearch = new PerformSearch();
var pageNumber = page ?? 1;
var searchTerm = Request.QueryString["s"];
if (String.IsNullOrWhiteSpace(searchTerm))
{
searchTerm = "";
}
var searcher = ExamineManager.Instance.SearchProviderCollection["ExternalSearcher"];
var searchCriteria = searcher.CreateSearchCriteria(UmbracoExamine.IndexTypes.Content);
Examine.SearchCriteria.IBooleanOperation filter = null;
var searchKeywords = searchTerm.Split(' ');
int i = 0;
for (i = 0; i < searchKeywords.Length; i++)
{
if (filter == null)
{
filter = searchCriteria.GroupedOr(new string[] { "nodeName", "bodyText", "browserTitle", "tags", "mediaTags" }, searchKeywords[i]);
}
else
{
filter = filter.Or().GroupedOr(new string[] { "nodeName", "bodyText", "browserTitle", "tags", "mediaTags" }, searchKeywords[i]);
}
}
var searchResults = searcher.Search(searchCriteria).Where(r => r["__IndexType"] == "content").ToList();
//performSearch.searcher = searchResults.ToString();
var paging = searchResults.ToPagedList(pageNumber,1);
if (searchResults.Any())
{
<ul>
#foreach (var result in searchResults)
{
var node = Umbraco.TypedContent(result.Id);
var pathIds = result["__Path"].Split(',');
var path = Umbraco.TypedContent(pathIds).Where(p => p != null).Select(p=> new {p.Name}).ToList();
<li style="line-height: 15px; list-style: none;">
<section>
<a href="#node.Url">
<p class="search-heading custom ">#node.Name</p>
<p class="search-link">www.addingvalue.webdevstaging.co.uk<strong>#node.Url</strong></p>
</a>
#if (result.Fields.ContainsKey("title"))
{
<p class="results-title"><strong>#result["title"]</strong></p>
}
#if (result.Fields.ContainsKey("bodyText"))
{
<p>#result["bodyText"].Truncate(250)</p>
}
</section>
</li>
}
</ul>
}
else
{
<p>
There are no results matching your search criteria:
#if (!String.IsNullOrWhiteSpace(searchTerm))
{
<text>'#searchTerm'</text>
}
</p>
}
Html.PagedListPager((IPagedList)paging, paged => Url.Action("List", new { page }));
}
One way of creating a paged list, using searchResults as the list of search results:
#{
//number of results
var resultsCount = searchResults.Count();
//required results per page
var pageSize = 5;
//retrieve current page from query string
int currentPage = 1;
if (!String.IsNullOrEmpty(Request.QueryString["page"]))
{
int.TryParse(Request.QueryString["page"], out currentPage);
}
//number of pages
int pageCount = 1;
if (resultsCount > pageSize)
{
pageCount = (int)Math.Ceiling((double)resultsCount / pageSize);
}
}
<div>
Page #currentPage of #pageCount pages
</div>
<ul>
#*Iterate through the search results skipping the results showing on any previous pages, and taking enough results to fill the page*#
#foreach (var result in searchResults.Skip((currentPage - 1) * pageSize).Take(pageSize))
{
<li>
#result.Name
</li>
}
</ul>
<ul>
#for (int i = 1; i <= pageCount; i++)
{
<li>
Page #i
</li>
}
</ul>