KnockoutJS o Javascript no mantienen un seguimiento adecuado de la matriz mostrada

Estoy escribiendo una tabla paginada con un selector de página en la parte inferior que muestra los diferentes números de página

introduzca la descripción de la imagen aquí

Estoy usando nocaut. Los números provienen de una matriz asignada por ko.com ( self.pages ) que calcula cuántas páginas hay en función del número de resultados / resultados por página. El problema que estoy encontrando es que si la matriz de datos es muy larga y los resultados por página están algo bajos, obtengo algo como esto:

introduzca la descripción de la imagen aquí

Lo que quiero hacer es limitar el número de elementos del menú a tres, por lo que si se selecciona la página 4, solo se verán los elementos 3,4,5. Actualmente estoy implementando un segundo ko.com que primero recupera el valor de self.pages , luego obtiene el valor del número de página actual ( self.pageNumber ), y corta la matriz de modo que solo se devuelvan 3 elementos:

 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); } }); 

Ahora, todo esto parece estar funcionando bien, pero hay un error que no he podido eliminar. Al utilizar el enlace de datos css knockout, le digo que asigne una clase de ‘ seleccionado ‘ a cualquier elemento que tenga el mismo valor que self.pageNumber (consulte el código a continuación).

Si el elemento seleccionado no requiere que las self.availablePages cambien (es decir, seleccionar 2 cuando 1 era la selección anterior), no hay problemas; 2 se selecciona y 1 se deselecciona.

Sin embargo, si la selección requiere self.availablePages para cambiar (es decir, 1,2,3 visibles, la selección 3 cambiará visible a 2,3,4), se muestran los números correctos, pero en lugar de seleccionar 3, se selecciona 4. Supongo que esto se debe a que el índice de la matriz que 3 solía estar ubicado en (el último) ahora está ocupado por 4.

Aquí está el menú:

  

La matriz que se estaba iterando originalmente era solo una matriz de números, pero al intentar solucionar este error lo cambié para que fuera una matriz del siguiente objeto:

 available.MenuModel = function(iterator) { var self = this; self.displayValue = iterator + 1; self.iterator = iterator; self.isSelected = ko.observable(false); } 

Una cosa que traté de hacer fue agregar el self.isSelected observable a todos los elementos en el menú, y luego, cuando self.availablePages se vuelve a calcular, la función comprueba qué es el número de página y luego encuentra qué elemento en la matriz coincide con eso y establece self.isSelected(true) , y luego intentó teclear el enlace css a eso.

Desafortunadamente esto no funcionó; Todavía tiene el mismo error exacto. He estado depurando el script como loco y no parece haber un problema; todo parece saber que se debe seleccionar 3, pero lo que realmente se selecciona es 4.

Supongo que los nocauts no son lo suficientemente inteligentes como para mantenerse al día con esto. ¿Hay algo que pueda hacer o algún patrón que ayude a realizar un seguimiento de qué elemento debe seleccionarse? Incluso intenté eliminarlo por completo, y tenía una función en el script eliminar / agregar manualmente la clase ‘ seleccionada ‘ cada self.pageNumber se cambiaba self.pageNumber y / o cuando se cambiaban las self.availablePages pero todavía tengo el mismo problema, así que tal vez esto no es un problema de nocaut sino algo con javascript.

He intentado todo lo demás que puedo pensar; Suscribirme a varios observables, promesas, pero como he dicho, todo ya sabe lo que debería seleccionarse, por lo que los controles adicionales y las devoluciones de llamada no alteran nada ni eliminan el error.

Espero que alguien conozca la causa / solución del error o una forma más inteligente de realizar la tarea. Esta es la self.pages que self.availablePages teclas self.availablePages off, en caso de que sea útil:

  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; }); 

Este es el modelo javascript completo (usando 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; }); 

Aquí está la tabla entera:

 
Part Number
Serial Number
Type
Equipment Group
Operational
Valid
Show entries per page

Seguí adelante y construí un paginador. En lugar de usar una matriz como lo hizo, usé solo el número de páginas disponibles, pageCount .

Probablemente, lo único que vale la pena analizar con más detalle es el cálculo de las páginas que se mostrarán:

 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); 

Vayamos a través de esta pieza por pieza. Queremos que nuestra página actual esté en el centro de todos los botones de paginación mostrados, con algunos a su izquierda y otros a su derecha. Pero cuantas

Si usamos un número impar como tres, eso es simple: el número menos 1 (el seleccionado) dividido por dos. (3 - 1) / 2 = 1 , o uno a cada lado.

Con un número par de botones de paginación para mostrar, eso no funciona, por lo que calculamos cada lado individualmente y redondeamos un resultado hacia arriba y otro hacia abajo:

 var previousHalf = Math.floor( (this.visiblePageCount() - 1) / 2 ), nextHalf = Math.ceil( (this.visiblePageCount() - 1) / 2 ), 

Hay tres resultados posibles:

  1. nuestra selección encaja
  2. estamos demasiado cerca del principio
  3. estamos demasiado cerca del final

Si estamos demasiado cerca del principio:

 if ( this.currentPage() - previousHalf < 1 ) { firstPage = 1; lastPage = this.visiblePageCount(); if ( lastPage > this.pageCount() ) { lastPage = this.pageCount(); } } 

comenzamos con 1 e intentamos mostrar las páginas 1 hasta visiblePageCount . Si eso tampoco funciona, porque no tenemos suficientes páginas, simplemente mostramos todo lo que tenemos.

Si estamos demasiado cerca del final:

  } else if ( this.currentPage() + nextHalf > this.pageCount() ) { lastPage = this.pageCount(); firstPage = this.pageCount() - this.visiblePageCount() + 1; if (firstPage < 1) { firstPage = 1; } } 

Terminamos con la última página e intentamos mostrar tantas como necesitamos a la izquierda. Si eso no funciona, porque no tenemos suficientes páginas, simplemente mostramos todo lo que tenemos.

Aquí está el ejemplo completo:

 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; }