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>
Related
I have some html elements like this:
<div class="items">
<div class="item" data-opt1="val1" data-opt2="val2,val3">Item 1</div>
<div class="item" data-opt1="val4" data-opt2="val2,val5">Item 2</div>
<div class="item" data-opt1="val1" data-opt2="val3,val6">Item 3</div>
<div class="item" data-opt1="val7" data-opt2="val3,val5">Item 4</div>
</div>
and 2 variables to be used as filters, one array of options and one search string like this:
Example 1
var srcString = "val";
var filters = [
'opt1' : ['val1'],
'opt2' : ['val2','val6']
];
In this example item1 and item3 should be visible, item2 and item4 not visible.
Example 2
var srcString = "value";
var filters = [
'opt1' : ['val1'],
'opt2' : ['val2','val6']
];
All items shouldn't be visible, because var srcString contain a word that are not present in any of the data attributes.
Example 3
var srcString = "val6";
var filters = [];
Only item3 should be visible.
Example 4
var srcString = "";
var filters = [
'opt1' : ['val1','val7'],
'opt2' : ['val5']
];
Only item4 should be visible, because item1 and item3 (even if have opt1=val1) not have val5 in opt2.
Example 5
var srcString = "";
var filters = [
'opt1' : ['val1','val7']
];
items: 1,3,4 should be visible.
I was able to make all of these filters work one by one, but problems comes when I try to combinate all of them.
Code for search:
$(".item").each(function(){
var item = $(this);
if (item.data('opt1').toLowerCase().indexOf(srcVal) >= 0
|| item.data('opt2').toLowerCase().indexOf(srcVal) >= 0){
item.removeClass('d-none');
}else{
item.addClass('d-none');
}
});
Code for single filter:
var selectedOptions = filters['opt2'];
$(".item").each(function(){
var item = $(this);
let _options = item.data('opt2') + '';
_options = _options.split(",");
let found = _options.some(r=> selectedOptions.includes(r));
if(found==true){
item.removeClass('d-none');
}else{
item.addClass('d-none');
}
})
Any help is appreciate
Adapting your existing code, you can move the 2nd check inside the pass of the 1st check.
There's a few extra checks needed such as checking that .opt2 exists and has values, but these are simple checks.
$(".item").each(function() {
var item = $(this);
if (item.data('opt1').toLowerCase().indexOf(srcString) >= 0 ||
item.data('opt2').toLowerCase().indexOf(srcString) >= 0) {
item.removeClass('d-none');
var selectedOptions = filters['opt2'];
if (selectedOptions != null) {
let _options = item.data('opt2') + '';
_options = _options.split(",");
if (_options.length > 0) {
let found = _options.some(r => selectedOptions.includes(r));
if (found == true) {
item.removeClass('d-none');
} else {
item.addClass('d-none');
}
}
}
} else {
item.addClass('d-none');
}
});
Fiddle: https://jsfiddle.net/7t26n3pj/
note: this uses OPs code as provided so does not match their examples exactly as missing check for opt1
You can remove the if (_available_) continue to reduce code indentation by moving the check into a separate function and return true/false for pass/not pass - this will also allow you to add new filters going forward.
function applyFilter(filter) {
$(".item").each(function() {
var item = $(this);
if (passesFilter(item, filter))
item.removeClass('d-none');
else
item.addClass('d-none');
})
}
function passesFilter(item, filter) {
if (item.data('opt1').toLowerCase().indexOf(filter.srcString) < 0 &&
item.data('opt2').toLowerCase().indexOf(filter.srcString) < 0)
return false;
var selectedOptions = filter.filters['opt2'];
if (selectedOptions == null) return true;
let _options = item.data('opt2') + '';
_options = _options.split(",");
if (_options.length === 0) return true;
let found = _options.some(r => selectedOptions.includes(r));
return found;
}
// Examples
$("button").click(function() {
var filter = $(this).data("filter");
console.log(filter);
applyFilter(filter);
});
.d-none { display:none; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="items">
<div class="item" data-opt1="val1" data-opt2="val2,val3">Item 1</div>
<div class="item" data-opt1="val4" data-opt2="val2,val5">Item 2</div>
<div class="item" data-opt1="val1" data-opt2="val3,val6">Item 3</div>
<div class="item" data-opt1="val7" data-opt2="val3,val5">Item 4</div>
</div>
<button type='button' data-filter='{"srcString":"val","filters":{"opt1":["val1"],"opt2":["val2","val6"]}}'>
test 1
</button>
<button type='button' data-filter='{"srcString":"value","filters":{"opt1":["val1"],"opt2":["val2","val6"]}}'>
test 2
</button>
<button type='button' data-filter='{"srcString":"val6","filters":{}'>
test 3
</button>
<button type='button' data-filter='{"srcString":"","filters":{"opt1":["val1", "val7"],"opt2":["val5"]}}'>
test 4
</button>
<button type='button' data-filter='{"srcString":"","filters":{"opt1":["val1", "val7"]}}'>
test 5
</button>
I am developing an application using Angular 2.. In my application I am using barcode scanner to scan in the text field and storing those items in the array.
When I scan the item get added to array, but when I scan another item the old item it replace the old value in array.
Below is the piece of code which I am using. Please help me if you see any fix for the weird issue.
import { Component,ViewChild,Input, Output,OnInit,ChangeDetectorRef } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { HeaderComponent } from '../common/header.component';
//import { SaleCart } from '../model/SaleCart';
//import * as $ from "jquery";
declare var jQuery: any
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./posapp.component.css']
})
export class TestComponent implements OnInit{
title = 'Treewalker POS';
public cartItems = [];
public query;
public filteredList = [];
public products = [{"id":"3","name":"Pears Soap Original 75gm","sku":"89675432189","price":"32.00","special_price":"32.00","qty":null,"barcode":"89675432189","tax":"5","discount":"0"},{"id":"1","name":"Rin","sku":"1111111111111","price":"11.00","special_price":"11.00","qty":"10.000","barcode":"1111111111111","tax":"5","discount":"0"},{"id":"2","name":"Test 1","sku":"23456","price":"10.00","special_price":"10.00","qty":"10.000","barcode":"23456","tax":"5","discount":"0"}];
constructor() {
}
ngOnInit() {
}
add(item) {
/* check the items in the json data */
let flag = false;
var foodItem = {};
for (let product of this.products) {
if(product.barcode == item) {
flag = true;
foodItem['ctr'] = 1;
foodItem['item'] = product;
break;
}
}
let localCart = [];
if(sessionStorage.getItem("cart")){
localCart = JSON.parse(sessionStorage.getItem("cart"));
//console.log(JSON.stringify(localCart));
}
//console.log("food "+JSON.stringify(this.cart));
if(flag && localCart.length) {
let exist = 0;
for(let i=0; i < localCart.length; i++) {
if(localCart[i].item.barcode == item) {
localCart[i].ctr = parseInt(localCart[i].ctr) + 1;
//console.log("#### "+this.cart[i].ctr+" --- "+item);
exist = 1;
}
}
if(!exist){
localCart.push(foodItem);
}
sessionStorage.setItem("cart",JSON.stringify(localCart));
//this.barcode = "";
}else if(flag){
localCart.push(foodItem);
sessionStorage.setItem("cart",JSON.stringify(localCart));
}
//this.cart = JSON.parse(sessionStorage.getItem("cart"));
//this.itemsCnt = localCart.length;
//console.log("--- "+this.itemsCnt);
console.log(JSON.parse(sessionStorage.getItem('cart')));
//this.onScanProduct.emit(localCart);
}
filter(e) {
//e.preventDefault();
if (this.query !== ""){
this.filteredList = this.products.filter(function(el){
if(el.barcode.toLowerCase() == this.query.toLowerCase()) {
return el.barcode.toLowerCase() == this.query.toLowerCase();
}else{
return el.barcode.toLowerCase().indexOf(this.query.toLowerCase()) > -1;
}
}.bind(this));
/* scanned item will be added to the cart */
console.log(this.filteredList.length);
if(this.filteredList.length > 0 && e.which == 13){
//console.log(JSON.stringify(this.filteredList));
for (let product of this.filteredList) {
//console.log(filter.barcode+"=="+this.query);
if(product.barcode == this.query) {
this.add(product.barcode);
jQuery('#barcode').val("");jQuery('#barcode').focus();
this.filteredList = [];
}
}
}
}else{
this.filteredList = [];
}
}
}
Below is the html template
<div class="content-wrapper">
<section class="content">
<form>
<div class="row">
<!-- sales item add window -->
<!-- end -->
<div class="col-sm-4">
<div class="box box-primary">
<div class="box-body">
<div class="form-group">
<div class="row">
<div class="col-md-9">
<!--<input type="text" class="form-control" id="barcode" name="barcode" [(ngModel)]="barcode" (ngModelChange)="add($event)"
placeholder="Enter item code or scan the barcode" autocomplete="off" />-->
<input id="barcode" type="text" class="form-control validate filter-input" name="query" [(ngModel)]="query" (keyup)="filter($event)" placeholder="Enter item code or scan the barcode" autocomplete="off" [ngModelOptions]="{standalone: true}">
</div>
<div class="suggestions" *ngIf="filteredList.length > 0">
<ul>
<li *ngFor="let item of filteredList" >
<a (click)="select(item)" href="javascript:;">{{item.barcode}} {{item.name}}</a>
</li>
</ul>
</div>
<div class="col-md-3">
<button type="button" class="btn btn-primary" (click)="createnewproduct(newproduct)">New Product</button>
</div>
</div>
</div>
</div> <!-- end of box body -->
</div>
</div>
</div><!-- end of row -->
</form>
</section>
</div>
Below is the input field which is being used to scan the barcode
<input id="barcode" type="text" class="form-control validate filter-input" [(ngModel)]="query" (keyup)="filter()" placeholder="Enter item code or scan the barcode" autocomplete="off">
I am assuming you are using only the function add. I tried to implement in a javascript like in the following code but I am pretty sure you are referencing that object somewhere else and you are changing it. That's my conclusion but I might be wrong.
var factoryP = (function(){
function P() {
this.cart = [];
this.products = [{'barcode': 1, 'name': 'a'}, {'barcode': 1, 'name': 'b'}]
}
function add(item) {
/* check the items in the json data */
//console.log("cart length "+JSON.stringify(this.cart));
let flag = false;
var foodItem = {};
for (let product of this.products) {
if(product.barcode == item) {
//console.log("check "+item);
flag = true;
foodItem['ctr'] = 1;
foodItem['item'] = product;
break;
}
}
if(flag && this.cart.length) {
let exist = 0;
for(let i=0; i < this.cart.length; i++) {
if(this.cart[i].item.barcode == item) {
//console.log("Same product");
this.cart[i].ctr = parseInt(this.cart[i].ctr) + 1;
exist = 1;
}
}
if(!exist){
console.log(foodItem);
this.cart.push(foodItem);
}
}else if(flag){
console.log("step 4 "+item);
this.cart.push(foodItem);
}
}
P.prototype.add = add;
return new P();
});
instanceP = factoryP();
instanceP.add(1);
instanceP.add(1);
instanceP.add(1);
instanceP.add(2);
console.log(instanceP.cart[0].ctr)
//output 3
instanceP.cart[1].ctr
//output 1
Check your code here. Every time you are initializing the foodItem array with empty array. So whenever code will call add method, it will first empty your foodItem array.
Please check my comment in your code below:
add(item) {
let flag = false;
//Akshay: You need to make your changes here. Initialize your foodItem array out of this scope
var foodItem = {};
for (let product of this.products) {
if(product.barcode == item) {
//console.log("check "+item);
flag = true;
foodItem['ctr'] = 1;
foodItem['item'] = product;
break;
}
}
I have an observable array which contains numbers. I want to sort with a button on price. This is my viewmodel:
var ViewModel = function(model) {
self.Numbers = ko.observableArray(model);
self.SortArray = function() {
InstanceViewModel.Numbers.sort(function (left, right) {
return left.id == right.id ? 0 : (left.id < right.id ? -1 : 1) })
}
}
var InstanceViewModel = new ViewModel([{"id":"1"},{"id":"2"},{"id":"3"},{"id":"4"}]);
ko.applyBindings(InstanceViewModel);
This is my html:
<input type="button" value="Sort" data-bind="click: SortArrayNum">
<div data-bind="foreach: Numbers">
<h1 data-bind="text: id"></h1>
</div>
Its not sorting properly how is this possible? It says also: "Uncaught TypeError: Cannot read property 'sort' of undefined". I got this to work a time ago and it was sorting randomly strange. What am i doing wrong guys?
by reading http://knockoutjs.com/documentation/observableArrays.html the correct way is
myObservableArray.sort(function (left, right) { return left.lastName == right.lastName ? 0 : (left.lastName < right.lastName ? -1 : 1) })
therefore, shouldn't it be
self.SortArray = function () {
return self.Numbers.sort(function (left, right) { return left.id == right.id ? 0 : (left.id < right.id ? -1 : 1) })
}
instead of
self.SortArray = function() {
InstanceViewModel.Numbers.sort(function (left, right) {
return left.id == right.id ? 0 : (left.id < right.id ? -1 : 1) })
}
<input type="button" value="Sort" data-bind="click: SortArray">
<div data-bind="foreach: Numbers">
<h1 data-bind="text: id"></h1>
</div>
var ViewModel = function(model) {
self.Numbers = ko.observableArray(model);
self.SortArray = function() {
self.Numbers.sort(function (left, right) {
return left.id == right.id ? 0 : (left.id < right.id ? -1 : 1) })
}
}
var InstanceViewModel = new ViewModel([{"id":"2"},{"id":"1"},{"id":"6"},{"id":"4"}]);
ko.applyBindings(InstanceViewModel);
http://jsfiddle.net/93Z8N/239/
You are getting exception because your function is called SortArray not SortArrayNum
how to count the number of selecte/unselected checkbox items using angularjs?
my html
<label class="col-xs-5 pull-left" style="font-weight: bold; margin-left: 4px; margin-top: -17px;" >You have choose <font size="3" color="green">{{checkedResult}}</font> Customer(s)</label>
<tr ng-repeat="item in $data " >
<td width="30" style="text-align: left" header="\'smsChkbx\'">
<label>
<input type="checkbox" class="ace" name="someList[]" value="{{item.somename}}" ng-model="checkboxes.items[item.somename]" />
checkbox function
$scope.$watch('checkboxes.items', function(values) {
if (!$scope.mydata) {
return;
}
var checked = 0,
unchecked = 0,
total = $scope.mydata.length;
angular.forEach($scope.mydata, function(item) {
checked += ($scope.checkboxesSms.items[item.somename]) || 0;
unchecked += (!$scope.checkboxesSms.items[item.somename]) || 0;
});
if ((unchecked == 0) || (checked == 0)) {
$scope.checkboxes.checked = (checked == total);
}
**if(checked != 0 && unchecked != 0){
$scope.checkedResult++;
}**
$scope.tableParamsSms.reload();
console.log($scope.checkedResult);
console.log((checked != 0 && unchecked != 0));
angular.element(document.getElementById("select_Sms")).prop("indeterminate", (checked != 0 && unchecked != 0));
}, true);
counts properly when i check for first time, the issue is it wlll also count when i uncheck the checked one
also want to count when its checked by multiple check option
You should make an ng-click in the checkbox and fire an event.
e.g:
ng-click="selectOrDeselect(item)"
Then in that function do something like this to add or remove it from the list.
$scope.selectOrDeselect = function(item) {
var index = $scope.selectedItems.indexOf(item);
if (index === -1) {
$scope.selectedItems.push(item);
} else {
$scope.selectedItems.splice(index, 1);
}
};
Then have a var count = $scope.selectedItems.length
I could not modify your code but you can use something like this:
Html
<div ng-app>
<h2>Sample</h2>
<div ng-controller="MyCtrl">
<div ng-repeat="item in Items">
{{item.name}} <input type="checkbox" ng-model="item.selected" ng-change="Checked(item)" />
</div>
</div>
</div>
AngularJS
function MyCtrl($scope) {
$scope.SelectedItems = [];
$scope.Items = [
{
id: 1,
name: "ABC",
selected: false
},
{
id: 2,
name: "DEF",
selected: false
},
{
id: 3,
name: "GHI",
selected: false
}
]
$scope.Checked = function(item) {
if (item.selected) {
$scope.SelectedItems.push(item);
}
else {
var index = $scope.SelectedItems.indexOf(item);
if (index > -1) {
$scope.SelectedItems.splice(index, 1);
}
}
console.log($scope.SelectedItems) //array of selected items
}
}
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>