基于Knockout JS实现带搜索筛选与排序的分页表格技术咨询
Absolutely you can combine search/filtering with pagination in your Knockout.js table—this is a super common, straightforward pattern to implement. Let’s walk through how to build out all three features (filtering, sorting, pagination) using your existing PlayersViewModel as a starting point.
The order of operations matters here: we’ll first filter the full dataset based on your search text, then sort the filtered results, and finally paginate the sorted/filtered list. This ensures your pagination always reflects the most up-to-date filtered and sorted data.
Here’s how to extend your existing PlayersViewModel with all required functionality:
function PlayersViewModel() { var self = this; // --- Your existing code --- self.filterText = ko.observable(""); // Search input text self.descending = "fa fa-arrow-down"; self.ascending = "fa fa-arrow-up"; // Assumed completion of your truncated snippet // --- New: Raw player data (replace with your actual data source) --- self.players = ko.observableArray([ { name: "LeBron James", position: "Forward", team: "Lakers" }, { name: "Stephen Curry", position: "Guard", team: "Warriors" }, { name: "Nikola Jokic", position: "Center", team: "Nuggets" }, // Add more player objects here ]); // --- 1. Search/Filtering Logic --- self.filteredPlayers = ko.computed(function() { var filter = self.filterText().toLowerCase().trim(); if (!filter) return self.players(); // Return all players if no filter // Adjust this to check the properties you want to search (name, position, team, etc.) return self.players().filter(function(player) { return player.name.toLowerCase().includes(filter) || player.position.toLowerCase().includes(filter) || player.team.toLowerCase().includes(filter); }); }); // --- 2. Sorting Logic --- self.sortBy = ko.observable("name"); // Default sort column self.sortDirection = ko.observable("asc"); // "asc" for ascending, "desc" for descending self.sortedFilteredPlayers = ko.computed(function() { var data = self.filteredPlayers().slice(); // Clone array to avoid mutating original data // Sort the cloned array data.sort(function(a, b) { var valA = a[self.sortBy()], valB = b[self.sortBy()]; // Handle case-insensitive string sorting if (typeof valA === "string") { valA = valA.toLowerCase(); valB = valB.toLowerCase(); } // Basic comparison logic return valA > valB ? 1 : valA < valB ? -1 : 0; }); // Reverse if sorting descending return self.sortDirection() === "desc" ? data.reverse() : data; }); // Toggle sort direction when a column header is clicked self.toggleSort = function(column) { if (self.sortBy() === column) { // Flip direction if clicking the same column self.sortDirection(self.sortDirection() === "asc" ? "desc" : "asc"); } else { // Set new column and default to ascending self.sortBy(column); self.sortDirection("asc"); } }; // --- 3. Pagination Logic --- self.currentPage = ko.observable(1); self.itemsPerPage = ko.observable(10); // Adjust this to your preferred default // Get the players for the current page self.paginatedPlayers = ko.computed(function() { var startIndex = (self.currentPage() - 1) * self.itemsPerPage(); var endIndex = startIndex + self.itemsPerPage(); return self.sortedFilteredPlayers().slice(startIndex, endIndex); }); // Calculate total number of pages self.totalPages = ko.computed(function() { return Math.ceil(self.sortedFilteredPlayers().length / self.itemsPerPage()); }); // Page navigation methods self.goToPage = function(page) { if (page >= 1 && page <= self.totalPages()) { self.currentPage(page); } }; self.nextPage = function() { if (self.currentPage() < self.totalPages()) { self.currentPage(self.currentPage() + 1); } }; self.previousPage = function() { if (self.currentPage() > 1) { self.currentPage(self.currentPage() - 1); } }; } // Initialize the ViewModel ko.applyBindings(new PlayersViewModel());
Here’s how to bind this ViewModel to your HTML table and controls:
<!-- Search Filter Input --> <div class="search-bar"> <input type="text" data-bind="value: filterText, valueUpdate: 'input'" placeholder="Search players by name, position, or team..." > </div> <!-- Player Table --> <table class="player-table"> <thead> <tr> <!-- Name Column (Sortable) --> <th data-bind=" click: function() { toggleSort('name') }, css: { 'sorted-asc': sortBy() === 'name' && sortDirection() === 'asc', 'sorted-desc': sortBy() === 'name' && sortDirection() === 'desc' } " > Name <span data-bind=" css: sortBy() === 'name' ? (sortDirection() === 'asc' ? ascending : descending) : '' " ></span> </th> <!-- Position Column (Sortable) --> <th data-bind=" click: function() { toggleSort('position') }, css: { 'sorted-asc': sortBy() === 'position' && sortDirection() === 'asc', 'sorted-desc': sortBy() === 'position' && sortDirection() === 'desc' } " > Position <span data-bind=" css: sortBy() === 'position' ? (sortDirection() === 'asc' ? ascending : descending) : '' " ></span> </th> <!-- Team Column (Sortable) --> <th data-bind=" click: function() { toggleSort('team') }, css: { 'sorted-asc': sortBy() === 'team' && sortDirection() === 'asc', 'sorted-desc': sortBy() === 'team' && sortDirection() === 'desc' } " > Team <span data-bind=" css: sortBy() === 'team' ? (sortDirection() === 'asc' ? ascending : descending) : '' " ></span> </th> </tr> </thead> <tbody data-bind="foreach: paginatedPlayers"> <tr> <td data-bind="text: name"></td> <td data-bind="text: position"></td> <td data-bind="text: team"></td> </tr> </tbody> </table> <!-- Pagination Controls --> <div class="pagination-controls"> <button data-bind="click: previousPage, disable: currentPage() === 1" > Previous </button> <span data-bind="text: 'Page ' + currentPage() + ' of ' + totalPages()"></span> <button data-bind="click: nextPage, disable: currentPage() === totalPages()" > Next </button> <!-- Optional: Clickable Page Numbers --> <div class="page-numbers"> <!-- ko foreach: Array.from({length: totalPages()}, (_, i) => i + 1) --> <button data-bind=" click: $parent.goToPage, css: { 'active': $data === $parent.currentPage() } " > <span data-bind="text: $data"></span> </button> <!-- /ko --> </div> </div>
- Adjust Searchable Properties: Modify the
filterfunction infilteredPlayersto match the properties of your actual player objects. - Async Data: If your player data loads asynchronously, make sure to update the
self.playersobservable array once the data is fetched—all computed properties will automatically update. - Styling: Add CSS for the
sorted-asc,sorted-desc, andactiveclasses to highlight the current sort direction and active page. - Custom Items Per Page: You can add a dropdown to let users select
itemsPerPageby binding it to theself.itemsPerPageobservable.
内容的提问来源于stack exchange,提问作者Partha Sarathy

