How to implement paging search in Umbraco site using razor - javascript

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>

Related

Javascript get text from child within child

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>

HTML and Javascript search result pagination - limit the number of result pages showing

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

Limiting Pagination using Javascript

I have a web app that uses OMDB API which fetches all the related results that matches the title of a Movie searched by the user.
So for example the user searched for "Star Wars" the API will then return 483 results, I managed to make a pagination for it but it shows all the pages from 1-48 and what I'm trying to figure out is how can I only show pages [1,2,3,4,5,6,7...49(ending page)], then change that pagination to [2,3,4,5,6,7,8...49] and so on.
Heres the code for it:
<input type="text" class="form-control" id="title" name="title" placeholder="Movie Title...">
<button onclick="callOMDB(document.getElementById('title').value)" class="btn btn-primary">Search</button>
<div id="page" class="page">
<nav aria-label="Page navigation example">
<ul class="pagination" id="pagination">
</ul>
</nav>
</div>
<div id="info">
</div>
function callOMDB(x){
var poster = document.getElementById("info");
var page = document.getElementById("pagination");
var search = x;
var searchLink = 'https://www.omdbapi.com/?i=tt3896198&apikey=123456&s='+encodeURI(x);
$.getJSON(searchLink).then(function(response){
poster.innerHTML = '';
page.innerHTML = '';
var length = response.Search.length;
for(var i = 0; i < length; i++){
var yr = response.Search[i].Year;
var title = response.Search[i].Title;
poster.innerHTML += "<p>Year: "+yr+"</p><p>Title: "+title+"</p>";
}
var pageNo = response.totalResults/10;
var i = 0;
for(i = 1; i < pageNo; i++){
page.innerHTML += '<li class="page-item"><a onclick="nextPage('+i+',\''+search+'\')" class="page-link" href="#">'+i+'</a></li>';
}
});
}
You can use slice to get slice of the entire result and display that. For the purpose of demo I just shown you only. and results are just numbers. The array can be anything. Important things is slice to get parts of results you want and display them.
After as you click through you move the start and end positions
const resultsList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // This could be results / or page numbers
const maxResults = 9;
let upperPageIndex = maxResults;
let currentPageIndex = 0;
let resultsToDisplay = resultsList.slice(currentPageIndex , upperPageIndex); // slice(startIndex, endIndex);
console.log(resultsToDisplay)
// if currentPageIndex = 2 then show 2 more as you go through
upperPageIndex += 1; // You can make 2 a constant if needed
currentPageIndex += 1;
resultsToDisplay = resultsList.slice(currentPageIndex , upperPageIndex);
console.log(resultsToDisplay)
I used the paramter page in the omdbapi and that is my code
JS
window.addEventListener('DOMContentLoaded', function () {
const posterContainer = document.getElementById("info");
const pageContainer = document.getElementById("pagination");
const titleInput = document.getElementById('title');
const searchBtn = document.getElementById('searchBtn');
const searchLink = 'https://www.omdbapi.com/?i=tt3896198&apikey=56fbcd03';
searchBtn.addEventListener('click', () => {
getData(titleInput.value);
});
// event delegation
$('body').on('click', '.page-link', function (e) {
e.preventDefault();
getData(titleInput.value, this.id);
});
function getData(keyword, page = 1) {
$.getJSON(`${searchLink}&page=${page}&s=${keyword}`).then(function (response) {
posterContainer.innerHTML = '';
pageContainer.innerHTML = '';
var length = response.Search.length;
for (var i = 0; i < length; i++) {
var yr = response.Search[i].Year;
var title = response.Search[i].Title;
posterContainer.innerHTML += "<p>Year: " + yr + "</p><p>Title: " + title + "</p>";
}
var pageNo = response.totalResults / 10;
var i = 0;
for (i = 1; i < pageNo; i++) {
pageContainer.innerHTML += `<li class="page-item" ><a class="page-link" id="${i}" href="#">${i}</a></li>`;
}
});
}
});
HTML
<input type="text" class="form-control" id="title" name="title" placeholder="Movie Title...">
<button id="searchBtn" class="btn btn-primary">Search</button>
<div id="page" class="page">
<nav aria-label="Page navigation example">
<ul class="pagination" id="pagination">
</ul>
</nav>
</div>
<div id="info">
</div>

Javascript creating json object from two strings

So I will start with my needs. I have a task to create json output using nightwatch.js from the ul list where inside lists are few div elements with classes like name, surname... But really I can't think of any of solutions. Here is my html
<html>
<meta charset="utf-8">
<body>
<ul class="random">
<li class="list">
<div class="name">John</div>
<div class="surname">Lewis</div>
</li>
<li class="list odd">
<div class="name">Nick</div>
<div class="surname">Kyrgios</div>
</li>
</ul>
</body>
</html>
And here is my nightwatch.js script
'Test' : function(browser) {
function iterate(elements) {
elements.value.forEach(function(el) {
browser.elementIdText(el.ELEMENT, function(r) {
browser.elementIdAttribute(el.ELEMENT, 'class', function(att){
// output for json i guess
console.log(att.value + ' => ' + r.value)
})
});
});
}
browser
.url('http://url.com/nightwatch.php')
.waitForElementVisible('body', 8000)
.elements('css selector', 'ul li div', iterate)
.end();
}
Basically this will execute the following:
name => John
surname => Lewis
name => Nick
surname => Kyrgios
Output is a string for both...
And how can I make it like
[{name: "John", surname: "Lewis"}, {name: "Nick", surname: "Kyrgios"}]
This should work. You just need to keep track of the object and place it inside the array after list.
function iterate(elements) {
var objArr = [];
var obj = {};
elements.value.forEach(function(el, idx) {
browser.elementIdText(el.ELEMENT, function(r) {
browser.elementIdAttribute(el.ELEMENT, 'class', function(att){
if (obj.hasOwnProperty(att.value)) {
objArr.push(obj);
obj = {};
}
obj[att.value] = r.value;
});
});
if (idx === (elements.value.length-1)) {
objArr.push(obj);
console.log(objArr);
}
});
}
As with Will's solution, I used straight JavaScript. It does not appear that the nightwatch.js code for this provides any significant benefit. In addition, your question does not specify that only nightwatch.js should be used.
As opposed to Will, I have assumed that the class on your inner <div> elements could be arbitrary and that the arbitrary class should be used as the key/property on the object for that entry. Choosing to use this method vs. restricting it only to a name or surname property will depend on what your HTML really is, and how you want to handle classes which are not those two strings.
var theList = [];
var listItems = document.querySelectorAll('li');
for (var itemIndex=0,itemLength=listItems.length; itemIndex < itemLength; itemIndex++) {
var entry = {};
divs = listItems[itemIndex].querySelectorAll('div');
for (var divsIndex=0, divsLength=divs.length; divsIndex < divsLength; divsIndex++) {
entry[divs[divsIndex].className] = divs[divsIndex].textContent;
}
theList.push(entry);
}
outputJson = JSON.stringify(theList);
console.log(outputJson);
<html>
<meta charset="utf-8">
<body>
<ul class="random">
<li class="list">
<div class="name">John</div>
<div class="surname">Lewis</div>
</li>
<li class="list odd">
<div class="name">Nick</div>
<div class="surname">Kyrgios</div>
</li>
</ul>
</body>
</html>
What about something like this?
function iterate(elements) {
var jsonArray = [];
var jsonBuffer = "";
elements.value.forEach(function(el) {
browser.elementIdText(el.ELEMENT, function(r) {
browser.elementIdAttribute(el.ELEMENT, 'class', function(att){
// output for json i guess
if (att.value == 'name') {
jsonBuffer += "{" + att.value + ":" + "" + r.value + "" + ",";
}
else {
jsonBuffer += att.value + ":" + "" + r.value + "" + "}";
jsonArray.push(jsonBuffer);
jsonBuffer = "";
}
})
});
});
var jsonOutput = "[";
var i = 0;
jsonArray.forEach(function(el) {
if (i < jsonArray.length) {
jsonOutput += el + ",";
} else {
jsonOutput += el + "]";
}
i++;
}
}
I'm not familiar with Nightwatch, but you essentially loop through the elements and push them on to an array.
var results = [];
var entries = document.querySelectorAll('li');
for (var ix = 0; ix < entries.length; ix++) {
var name = entries[ix].querySelector('.name').innerText;
var surname = entries[ix].querySelector('.surname').innerText;
results.push({
name: name,
surname: surname
});
}
console.log(results);
<ul class="random">
<li class="list">
<div class="name">John</div>
<div class="surname">Lewis</div>
</li>
<li class="list odd">
<div class="name">Nick</div>
<div class="surname">Kyrgios</div>
</li>
</ul>

KnockoutJS or Javascript not keeping proper track of displayed array

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>

Categories