2013-07-16 8 views
17

Mam div, który jest skonfigurowany do wiązania z observeableArray, ale chcę pokazać tylko 50 elementów z tego observeableArray w danym momencie. Chcę to obsłużyć paginacją za pomocą poprzedniego i następnego przycisku wraz z indeksami na stronie, aby umożliwić użytkownikom przeglądanie stron przedmiotów z kolekcji.
Wiem, że prawdopodobnie mógłbym to zrobić z computedObservable i niestandardowym wiązaniem danych, ale nie jestem pewien jak to zrobić (nadal jestem neofitą Knockout).
Czy ktoś może wskazać mi właściwy kierunek?Jak obsługiwać dzielenie stron z Knockout

Oto mój kod (JS jest w maszynopisie):

<div class="container-fluid"> 
    <div class="row-fluid"> 
     <div class="span12"> 
      <%= 
      if params[:q] 
       render 'active_search.html.erb' 
      else 
       render 'passive_search.html.erb' 
      end 
      %> 
      <%= form_tag("/search", method: "get", :class => "form-search form-inline") do %> 
      <%= label_tag(:q, "Search for:") %> 
      <%= text_field_tag(:q, nil, class:"input-medium search-query") %> 
      <%= submit_tag("Search", :class=>"btn") %> 
      <% end %> 

      <div class="media" data-bind="foreach: tweetsArray"> 
       <%= image_tag('twitter-icon.svg', :class=>"tweet_img", :style=>"display:inline;") %> 
       <div class="media-body" style="display:inline;"> 
        <h4 class="media-heading" data-bind="text: user.screen_name" style="display:inline;"></h4> 
        <span data-bind="text:text" style="display:inline;"></span> <br /> 
        <span data-bind="text:'Created at '+created_at"></span> <br /> 
       </div> 
      </div> 

      <div class="pagination pagination-centered"> 
       <ul> 
        <li> 
         <a href="#">Prev</a> 
        </li> 
        <li> 
         <a href="#">1</a> 
        </li> 
        <li> 
         <a href="#">Next</a> 
        </li> 
       </ul> 
      </div> 

     </div> 
    </div> 
</div> 

<script> 
    var viewModel = new twitterResearch.TweetViewModel(); 
    ko.applyBindings(viewModel); 

    //TODO: notes to self, use custom binding for pagination along with a computed observable to determine where at in the list you are 

    //document.onReady callback function 
    $(function() { 
     $.getJSON('twitter', {}, function(data) { 
      viewModel.pushTweet(data); 
      console.log(data.user); 
     }); 
    }); 
</script> 

declare var $: any; 
declare var ko: any; 

module twitterResearch { 
    class Tweet { 
     text: string; 
     created_at: string; 
     coordinates: string; 
     user: string; 
     entities: string; 
     id: number; 
     id_str: string; 

     constructor(_text: string, _created_at: string, _coordinates: any, _user: any, 
        _entities: any, _id_str: string, _id: number){ 

      this.text = _text; 
      this.created_at = _created_at; 
      this.coordinates = _coordinates; 
      this.user = _user; 
      this.entities = _entities; 
      this.id_str = _id_str; 
      this.id = _id; 
     } 
    } 

    export class TweetViewModel{ 

     tweetsArray: any; 
     constructor() 
     { 
      this.tweetsArray = ko.observableArray([]); 
     } 

     //tweet is going to be the JSON tweet we return 
     //from the server 
     pushTweet(tweet) 
     { 
      var _tweet = new Tweet(tweet.text, tweet.created_at, tweet.coordinates, 
            tweet.user, tweet.entities, tweet.id_str, tweet.id); 
      this.tweetsArray.push(_tweet); 
      this.tweetsArray.valueHasMutated(); 
     } 
    } 
} 

Odpowiedz

40

Pagination jest dość proste z Knockout. Chciałbym osobiście osiągnąć to w następujący sposób:

  • mieć observableArray zawierający wszystkie elementy
  • mają widoczny zawierający aktualną stronę (zainicjowany na 0)
  • mają zmienną stwierdzającą liczbę elementów na stronie
  • Wykonaj obliczenia zwracające liczbę stron, obliczoną na podstawie liczby elementów na stronie i łącznej liczby elementów.
  • Na koniec dodaj obliczoną wartość, która dzieli tablicę zawierającą wszystkie elementy.

Biorąc pod uwagę, że można teraz dodać funkcję, która zwiększa (następna) lub dekrementacja (poprzednia) bieżącej strony.

Oto krótki przykład:

var Model = function() { 
    var self = this; 
    this.all = ko.observableArray([]); 
    this.pageNumber = ko.observable(0); 
    this.nbPerPage = 25; 
    this.totalPages = ko.computed(function() { 
     var div = Math.floor(self.all().length/self.nbPerPage); 
     div += self.all().length % self.nbPerPage > 0 ? 1 : 0; 
     return div - 1; 
    }); 

    this.paginated = ko.computed(function() { 
     var first = self.pageNumber() * self.nbPerPage; 
     return self.all.slice(first, first + self.nbPerPage); 
    }); 

    this.hasPrevious = ko.computed(function() { 
     return self.pageNumber() !== 0; 
    }); 

    this.hasNext = ko.computed(function() { 
     return self.pageNumber() !== self.totalPages(); 
    }); 

    this.next = function() { 
     if(self.pageNumber() < self.totalPages()) { 
      self.pageNumber(self.pageNumber() + 1); 
     } 
    } 

    this.previous = function() { 
     if(self.pageNumber() != 0) { 
      self.pageNumber(self.pageNumber() - 1); 
     } 
    } 
} 

Musisz znaleźć prosty i kompletny przykład tutaj: http://jsfiddle.net/LAbCv/ (może być nieco buggy, ale idea jest tam).

+5

To jest świetne .. Dziękuję! Rozbudowałem go tak, aby wyświetlał każdy numer strony i pozwalał użytkownikowi na klikanie od strony 5, aby wypowiadać się na stronie 1. http://jsfiddle.net/LAbCv/31/ –

+5

Zrzuca ostatnią połowę strony w prawo? 'Math.ceil' powinien znajdować się na stronie calc zamiast" Math.floor "? edytuj: Ahh dodajesz resztę/modulus z powrotem, po prostu skończę to i skończę. – MrYellow

+0

Uważaj na to podejście, jeśli pracujesz z wszystkim, co może mieć spory wynik. Nie jest dobrym pomysłem załadowanie całej listy wyników po stronie klienta, a następnie po prostu strona w javascript. W przypadku dużych zestawów wyników potrzebujesz jakiegoś rozwiązania AJAX, które ładuje tylko dane wymagane dla bieżącej strony. – Mir

1

Utworzyłem stronę z dokładnym wyjaśnieniem, w jaki sposób tworzyć paginacje za pomocą małej wtyczki JQuery (here).

Zasadniczo użyłem zwykłego wiązania danych knockout z AJAX i po pobraniu danych z serwera wywołuję wtyczkę. Możesz znaleźć wtyczkę here. Nazywa się to Prostą paginacją.

5

Właściwie pracuję nad stroną internetową, która ma wiele tabel (większość z nich wymaga stronicowania).
Tak naprawdę potrzebowałem jakiegoś reusable-component do stronicowania, aby użyć go we wszystkich przypadkach, w których potrzebuję stronicowania.
Potrzebowałem także bardziej zaawansowanych funkcji niż te, które znalazły się w zaakceptowanej odpowiedzi na to pytanie.

Opracowałem więc własny komponent do rozwiązania tego problemu, oto on.

Now on Github

JsFiddle

i więcej szczegółów, kontynuować czytanie (Proszę zwrócić uwagę, aby wziąć kodu z GitHub, nie stąd, jak kod GitHub zostało zaktualizowane i rozszerzone, odkąd wstaw to tutaj)

JavaScript

function PagingVM(options) { 
    var self = this; 

    self.PageSize = ko.observable(options.pageSize); 
    self.CurrentPage = ko.observable(1); 
    self.TotalCount = ko.observable(options.totalCount); 

    self.PageCount = ko.pureComputed(function() { 
     return Math.ceil(self.TotalCount()/self.PageSize()); 
    }); 

    self.SetCurrentPage = function (page) { 
     if (page < self.FirstPage) 
      page = self.FirstPage; 

     if (page > self.LastPage()) 
      page = self.LastPage(); 

     self.CurrentPage(page); 
    }; 

    self.FirstPage = 1; 
    self.LastPage = ko.pureComputed(function() { 
     return self.PageCount(); 
    }); 

    self.NextPage = ko.pureComputed(function() { 
     var next = self.CurrentPage() + 1; 
     if (next > self.LastPage()) 
      return null; 
     return next; 
    }); 

    self.PreviousPage = ko.pureComputed(function() { 
     var previous = self.CurrentPage() - 1; 
     if (previous < self.FirstPage) 
      return null; 
     return previous; 
    }); 

    self.NeedPaging = ko.pureComputed(function() { 
     return self.PageCount() > 1; 
    }); 

    self.NextPageActive = ko.pureComputed(function() { 
     return self.NextPage() != null; 
    }); 

    self.PreviousPageActive = ko.pureComputed(function() { 
     return self.PreviousPage() != null; 
    }); 

    self.LastPageActive = ko.pureComputed(function() { 
     return (self.LastPage() != self.CurrentPage()); 
    }); 

    self.FirstPageActive = ko.pureComputed(function() { 
     return (self.FirstPage != self.CurrentPage()); 
    }); 

    // this should be odd number always 
    var maxPageCount = 7; 

    self.generateAllPages = function() { 
     var pages = []; 
     for (var i = self.FirstPage; i <= self.LastPage() ; i++) 
      pages.push(i); 

     return pages; 
    }; 

    self.generateMaxPage = function() { 
     var current = self.CurrentPage(); 
     var pageCount = self.PageCount(); 
     var first = self.FirstPage; 

     var upperLimit = current + parseInt((maxPageCount - 1)/2); 
     var downLimit = current - parseInt((maxPageCount - 1)/2); 

     while (upperLimit > pageCount) { 
      upperLimit--; 
      if (downLimit > first) 
       downLimit--; 
     } 

     while (downLimit < first) { 
      downLimit++; 
      if (upperLimit < pageCount) 
       upperLimit++; 
     } 

     var pages = []; 
     for (var i = downLimit; i <= upperLimit; i++) { 
      pages.push(i); 
     } 
     return pages; 
    }; 

    self.GetPages = ko.pureComputed(function() { 
     self.CurrentPage(); 
     self.TotalCount(); 

     if (self.PageCount() <= maxPageCount) { 
      return ko.observableArray(self.generateAllPages()); 
     } else { 
      return ko.observableArray(self.generateMaxPage()); 
     } 
    }); 

    self.Update = function (e) { 
     self.TotalCount(e.TotalCount); 
     self.PageSize(e.PageSize); 
     self.SetCurrentPage(e.CurrentPage); 
    }; 

    self.GoToPage = function (page) { 
     if (page >= self.FirstPage && page <= self.LastPage()) 
      self.SetCurrentPage(page); 
    } 

    self.GoToFirst = function() { 
     self.SetCurrentPage(self.FirstPage); 
    }; 

    self.GoToPrevious = function() { 
     var previous = self.PreviousPage(); 
     if (previous != null) 
      self.SetCurrentPage(previous); 
    }; 

    self.GoToNext = function() { 
     var next = self.NextPage(); 
     if (next != null) 
      self.SetCurrentPage(next); 
    }; 

    self.GoToLast = function() { 
     self.SetCurrentPage(self.LastPage()); 
    }; 
} 

HTML

<ul data-bind="visible: NeedPaging" class="pagination pagination-sm"> 
    <li data-bind="css: { disabled: !FirstPageActive() }"> 
     <a data-bind="click: GoToFirst">First</a> 
    </li> 
    <li data-bind="css: { disabled: !PreviousPageActive() }"> 
     <a data-bind="click: GoToPrevious">Previous</a> 
    </li> 

    <!-- ko foreach: GetPages() --> 
    <li data-bind="css: { active: $parent.CurrentPage() === $data }"> 
     <a data-bind="click: $parent.GoToPage, text: $data"></a> 
    </li> 
    <!-- /ko --> 

    <li data-bind="css: { disabled: !NextPageActive() }"> 
     <a data-bind="click: GoToNext">Next</a> 
    </li> 
    <li data-bind="css: { disabled: !LastPageActive() }"> 
     <a data-bind="click: GoToLast">Last</a> 
    </li> 
</ul> 

Cechy

  1. Pokaż na potrzeby
    gdy nie ma potrzeby przywoływania w ogóle (na przykład elementów, które trzeba wyświetlić mniej niż rozmiar strony), wtedy komponent HTML wyłączy się zjawić się.
    Zostanie to ustalone na podstawie oświadczenia data-bind="visible: NeedPaging".

  2. Wyłącz na potrzeby
    na przykład, jeśli jesteś już wybrany ostatnią stronę, dlaczego last page lub przycisk Next powinny być dostępne dla prasy?
    ja operowania tym iw tym przypadku jestem wyłączając te przyciski stosując następujące wiązaniadata-bind="css: { disabled: !PreviousPageActive() }"

  3. rozróżnić wybranej strony
    specjalnej klasy (w tym przypadku o nazwie active klasa) jest stosowana na wybranej stronie, aby użytkownik wiedział, na której stronie jest teraz.
    ten jest ustalany przez wiązania data-bind="css: { active: $parent.CurrentPage() === $data }"

  4. Ostatni & Pierwszy
    przechodząc do pierwszej i ostatniej stronie jest również dostępna za pomocą prostych przycisków dedykowanych do tego.

  5. Ograniczenia wyświetlanych przycisków
    Załóżmy, że masz dużo stron, na przykład, 1000 stron, to co się stanie? czy wyświetlałbyś je wszystkie dla użytkownika? Absolutnie nie musisz wyświetlić tylko kilka z nich zgodnie z aktualną stroną. na przykład, pokazując 3 strony przed i inne 3 strony po wybranej stronie.
    Ta sprawa została obsłużona tutaj <!-- ko foreach: GetPages() -->
    Funkcja stosująca prosty algorytm do określenia, czy musimy wyświetlić wszystkie strony (liczba stron jest poniżej progu, co można łatwo określić), lub pokazać tylko niektóre z nich przyciski.
    można określić wartość progową, zmieniając wartość zmiennej maxPageCount W tej chwili przypisałem ją do następującej var maxPageCount = 7;, co oznacza, że ​​nie można wyświetlić więcej niż 7 przycisków dla użytkownika (3 przed SelectedPage i 3 po Wybrana strona) i sama wybrana strona.

    Możesz się zastanawiać, co by było, gdyby nie było wystarczającej ilości stron po lub przed wyświetleniem bieżącej strony? nie martw się ja obchodzenia tego w algorytmie
    na przykład, jeśli masz 11 pages i masz maxPageCount = 7 i aktualną selected page is 10, wówczas Następujące strony zostaną pokazane

    5,6,7,8,9,10(selected page),11

    więc zawsze stratyfikacji maxPageCount, w poprzednim przykładzie pokazującym 5 stron przed wybraną stroną i tylko stronę 1 po wybranej stronie.

  6. wybrana strona Validation
    Wszystko zestaw operacji dla CurrentPage obserwowalnym które określają wybraną stronę przez użytkownika, przeżywa funkcji SetCurrentPage. Tylko w tej funkcji ustawiamy to obserwowalne i jak widać z kodu, przed ustawieniem wartości wykonujemy operacje sprawdzania poprawności, aby upewnić się, że nie przekroczymy dostępnej strony stron.

  7. Już czystej
    używam tylko pureComputed nie computed właściwości, co oznacza, że ​​nie trzeba niepokoić się z oczyszczania i usuwania tych właściwości. Chociaż, jak widać na poniższym przykładzie, trzeba dysponować niektórych innych zapisów, które są poza składnika sam

UWAGA 1
Można zauważyć, że używam niektóre bootstrap klasy w tym komponencie, To jest dla mnie odpowiednie, ale oczywiście , oczywiście, możesz używać własnych klas zamiast klas ładowania początkowego.
Klasy bootstrap które stosowane są tu pagination, pagination-sm, active i disabled
Zapraszam je zmienić, jak trzeba.

UWAGA 2
Więc wprowadzono komponent dla ciebie, to jest czas, aby zobaczyć, jak to może działać.
Zintegrujesz ten komponent z głównym ViewModel w ten sposób.

function MainVM() { 
    var self = this; 

    self.PagingComponent = ko.observable(new Paging({ 
     pageSize: 10,  // how many items you would show in one page 
     totalCount: 100, // how many ALL the items do you have. 
    })); 

    self.currentPageSubscription = self.PagingComponent().CurrentPage.subscribe(function (newPage) { 
     // here is the code which will be executed when the user changes the page. 
     // you can handle this in the way you need. 
     // for example, in my case, I am requesting the data from the server again by making an ajax request 
     // and then updating the component 

     var data = /*bring data from server , for example*/ 
     self.PagingComponent().Update({ 

      // we need to set this again, why? because we could apply some other search criteria in the bringing data from the server, 
      // so the total count of all the items could change, and this will affect the paging 
      TotalCount: data.TotalCount, 

      // in most cases we will not change the PageSize after we bring data from the server 
      // but the component allows us to do that. 
      PageSize: self.PagingComponent().PageSize(), 

      // use this statement for now as it is, or you have to made some modifications on the 'Update' function. 
      CurrentPage: self.PagingComponent().CurrentPage(), 
     }); 
    }); 

    self.dispose = function() { 
     // you need to dispose the manual created subscription, you have created before. 
     self.currentPageSubscription.dispose(); 
    } 
} 

Ostatni, ale nie najmniej, Oczywiście nie zapomnij zmienić wiązania w komponencie HTML zgodnie z tym szczególnym ViewModel lub owinąć cały element z with binding like this

<div data-bind="with: PagingComponent()"> 
    <!-- put the component here --> 
</div> 

Cheers

1

To pytanie jest nadal jednym z najlepszych wyszukiwań dla "paginacji knockout", dlatego warto wymienić rozszerzenie nokaut knockout-paging (git).
Zapewnia stronicowanie poprzez rozszerzenie ko.observableArray. Jest dobrze udokumentowany i łatwy w użyciu. Przykładowy przykład to here.