2013-07-16 10 views
17

Ho un div che è configurato per associare a un observeableArray, ma voglio solo mostrare al massimo 50 elementi da quello observeableArray in qualsiasi momento. Voglio gestirlo con l'impaginazione con un pulsante precedente e successivo insieme agli indici nella pagina per consentire agli utenti di scorrere le pagine degli elementi dalla raccolta.
So che probabilmente potrei farlo con uno computedObservable e un collegamento dati personalizzato ma non sono sicuro di come farlo (sono ancora un neofita di Knockout).
Qualcuno può indicarmi la giusta direzione?Come gestire l'impaginazione con Knockout

Ecco il mio codice (il JS è a macchina):

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

risposta

40

paginazione è abbastanza semplice con Knockout. Io personalmente ottenere in questo modo:

  • Avere un observableArray contenente tutti gli elementi
  • avere un osservabile contenente la pagina corrente (inizializzato a 0)
  • avere una variabile dichiarando il numero di elementi per pagina
  • Avere un calcolo che restituisce il numero di pagine, calcolato grazie al numero di elementi per pagina e al numero totale di elementi.
  • Infine, aggiungi un computed che divide l'array che contiene tutti gli elementi.

Dato che, ora è possibile aggiungere una funzione che incrementa (successivo) o decrementa (precedente) la pagina corrente.

Ecco un rapido esempio:

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

Troverete un esempio semplice e completa qui: http://jsfiddle.net/LAbCv/ (potrebbe essere un po 'buggy, ma l'idea è lì).

+5

Questo è fantastico .. Grazie! L'ho espanso per mostrare ogni numero di pagina e consentire all'utente di fare clic da pagina 5 per dire pagina 1. http://jsfiddle.net/LAbCv/31/ –

+5

Elimina l'ultima metà di una pagina, giusto? 'Math.ceil' dovrebbe essere nel calcolo del numero di pagine piuttosto che in' Math.floor'? modifica: Ahh si aggiunge il resto/modulo indietro, vorrei solo ceiling e fatto. – MrYellow

+0

Stai attento a questo approccio se stai lavorando con qualsiasi cosa possa avere un set di risultati considerevole. Non è una buona idea caricare l'intero elenco di risultati lato client e quindi solo la pagina in javascript. Per set di risultati di grandi dimensioni, è necessario un qualche tipo di soluzione AJAX che carichi solo i dati necessari per la pagina corrente. – Mir

1

Ho creato un post di blog con una spiegazione dettagliata su come creare l'impaginazione con l'aiuto di un piccolo plugin JQuery (here).

Fondamentalmente, ho utilizzato il normale binding dei dati con AJAX e dopo che i dati sono stati recuperati dal server, chiamo il plugin. È possibile trovare il plug-in here. Si chiama Impaginazione semplice.

5

Attualmente sto lavorando a un sito Web, che ha un sacco di tabelle (la maggior parte ha bisogno di paging).
Quindi, in realtà, avevo bisogno di un po 'di reusable-component per il paging per usarlo in tutti i casi in cui ho bisogno di cercapersone.
Inoltre, avevo bisogno di funzionalità più avanzate di quelle fornite nella risposta accettata a questa domanda.

Così ho sviluppato il mio componente per risolvere questo problema, eccolo.

Now on Github

JsFiddle

E per maggiori dettagli, continuare a leggere (Si prega di prendere in considerazione di prendere il codice da GitHub, non da qui, come il codice GitHub è stato aggiornato e migliorato da quando ho metterlo qui)

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> 

Caratteristiche

  1. Mostra sulla necessità
    quando non c'è bisogno di paginazione a tutti (per esempio, gli elementi che devono visualizzare meno della dimensione della pagina), quindi il componente HTML verrà disattivato apparire.
    Questo sarà stabilito dalla dichiarazione data-bind="visible: NeedPaging".

  2. Disabilita sulla necessità
    per esempio, se si è già selezionata l'ultima pagina, perché il last page o il pulsante Next dovrebbe essere disponibile a premere?
    sto manipolazione di questo e in quel caso io sto disabilitando quei bottoni applicando la seguente vincolantedata-bind="css: { disabled: !PreviousPageActive() }"

  3. Distinguere la pagina selezionata
    una classe speciale (in questo caso chiamato active classe) viene applicata sulla pagina selezionata, per far sapere all'utente in quale pagina si trova adesso.
    Questo è stabilito dal legame data-bind="css: { active: $parent.CurrentPage() === $data }"

  4. Ultima & Prima
    andare alla prima e l'ultima pagina è disponibile da semplici pulsanti dedicati a questo anche.

  5. Limiti per pulsanti visualizzati
    Supponiamo di avere un sacco di pagine, per esempio, 1000 pagine, allora che cosa succederà? li mostreresti tutti per l'utente? assolutamente non devi visualizzare solo alcuni di loro in base alla pagina corrente. per esempio, mostrando 3 pagine prima e altre 3 pagine dopo la pagina selezionata. <!-- ko foreach: GetPages() -->
    la funzione GetPages che applica un semplice algoritmo per determinare se è necessario mostrare tutte le pagine (il numero di pagine è sotto la soglia, che può essere determinato facilmente) o per mostrare solo alcune delle i pulsanti.
    è possibile determinare la soglia modificando il valore della variabile maxPageCount
    In questo momento l'ho assegnato come il seguente var maxPageCount = 7; che significa che non possono essere visualizzati più di 7 pulsanti per l'utente (3 prima di SelectedPage e 3 dopo il Pagina selezionata) e la Pagina selezionata stessa.

    Ci si potrebbe chiedere, cosa succederebbe se non ci fossero pagine sufficienti dopo O prima che venga visualizzata la pagina corrente? non ti preoccupare io sono la manipolazione di questo nell'algoritmo di
    per esempio, se si dispone di 11 pages e si ha maxPageCount = 7 e la corrente selected page is 10, quindi verrà visualizzata nelle pagine seguenti

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

    quindi abbiamo sempre stratificazione del maxPageCount, nell'esempio precedente che mostra le pagine 5 prima della pagina selezionata e solo la pagina 1 dopo la pagina selezionata.

  6. Selezionato pagina Convalida
    Tutte le operazioni insieme per la CurrentPage osservabile che determinano la pagina selezionata dall'utente, sta attraversando la funzione SetCurrentPage. Solo in questa funzione impostiamo questa funzione osservabile e, come puoi vedere dal codice, prima di impostare il valore eseguiamo le operazioni di convalida per assicurarci che non andremo oltre la pagina disponibile delle pagine.

  7. già pulito
    Io uso solo pureComputed non computed proprietà, il che significa che non c'è bisogno di preoccuparsi se stessi con la pulizia e lo smaltimento di tali proprietà. Anche se, come si vedrà nel seguente esempio, è necessario disporre di alcuni altri abbonamenti che sono al di fuori del componente stesso

NOTA 1
Si può notare che sto usando un po 'di bootstrap classi in questo componente, Questo è adatto per me, ma , ovviamente, è possibile utilizzare le proprie classi anziché le classi di bootstrap.
Le classi bootstrap che ho usato qui sono pagination, pagination-sm, active e disabled
Sentitevi liberi di cambiarli come avete bisogno.

NOTA 2
Così ho introdotto il componente per voi, è il momento di vedere come potrebbe funzionare.
Integrare questo componente nel ViewModel principale come in questo modo.

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

Ultimo ma non meno importante, Certo non dimenticate di cambiare l'associazione nel componente HTML in base al vostro ViewModel speciale, o avvolgere tutta la componente con il with binding come questo

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

Acclamazioni

1

Questa domanda è ancora una delle principali ricerche per "impaginazione a eliminazione diretta", quindi è da citare l'estensione ad eliminazione diretta knockout-paging (git).
Fornisce l'impaginazione estendendo ko.observableArray. È ben documentato e facile da usare.
L'esempio di utilizzo è here.