Vue 2 都發行半年多了,直到最近有網友留言這才想起一直沒更新裡面的內容,囧。

延續上回 [VueJS] 在 v-for 列表中透過 filter 完成搜尋與分頁的功能 這篇的說明, 這次我們來看看,自從 VueJS 更新到 V2 拿掉了內建的 filterBy、limitBy 等好用的 filter 後,要如何修改才能做到分頁的功能。

首先看到原本 V1 的範例,是這樣的,這裡透過 limitBy countOfPage pageStart 來完成分頁,如下:

  <tr v-for="r in rows
    | filterBy filter_name in 'name'
    | recordLength 'filteredRowCount'
    | limitBy countOfPage pageStart ">
    <td>{{ (currPage-1) * countOfPage + $index + 1 }}</td>
    <td>{{ r.name }}</td>
    <td>{{ r.age }}</td>
    <td>{{ r.gender }}</td>
  </tr>

因為從 V2 開始,filterBylimitByorderBy 都沒有了,所以我們直接移除掉:

  <tr v-for="r in rows">
    <td>{{ (currPage-1) * countOfPage + $index + 1 }}</td>
    <td>{{ r.name }}</td>
    <td>{{ r.age }}</td>
    <td>{{ r.gender }}</td>
  </tr>

這時應該會出現錯誤,因為 $index 的寫法也更新了,所以改一下 v-for 的內容:

  <tr v-for="(r, index) in rows">
    <td>{{ (currPage-1) * countOfPage + index + 1 }}</td>
    <td>{{ r.name }}</td>
    <td>{{ r.age }}</td>
    <td>{{ r.gender }}</td>
  </tr>

到目前為止,至少可以看到程式正常運作,但相對地分頁與搜尋的功能也沒了,所以接下來先來處理搜尋的部份。


首先,在 computed 屬性,我們新增一個叫做 filteredRows 的屬性,用來處理與 filter_name 比對後的結果:

  filteredRows: function(){
    // 因為 JavaScript 的 filter 有分大小寫,
    // 所以這裡將 filter_name 與 rows[n].name 通通轉小寫方便比對。
    var filter_name = this.filter_name.toLowerCase();

    // 如果 filter_name 有內容,回傳過濾後的資料,否則將原本的 rows 回傳。
    return ( this.filter_name.trim() !== '' ) ?
        this.rows.filter(function(d){ return d.name.toLowerCase().indexOf(filter_name) > -1; }) :
        this.rows;
  },

然後將 html 內的 rows 換成 filteredRows

  <tr v-for="(r, index) in filteredRows">
    <td>{{ (currPage-1) * countOfPage + index + 1 }}</td>
    <td>{{ r.name }}</td>
    <td>{{ r.age }}</td>
    <td>{{ r.gender }}</td>
  </tr>

name 搜尋的部份到這裡就算完成了,接著回頭來處理分頁


處理分頁之前,先看到底下的分頁按鈕, n in range 在 V2 的寫法也有不同,原本的索引會由 0 開始,但從 V2 開始會由 1 開始,所以需要把原本的:

<div class="pagination">
    <ul>
    <li v-bind:class="{'disabled': (currPage === 1)}"
        @click.prevent="setPage(currPage-1)"><a href="#">Prev</a></li>
    <li v-for="n in totalPage"
        v-bind:class="{'active': (currPage === (n+1))}"
        @click.prevent="setPage(n+1)"><a href="#">{{n+1}}</a></li>
    <li v-bind:class="{'disabled': (currPage === totalPage)}"
        @click.prevent="setPage(currPage+1)"><a href="#">Next</a></li>
    </ul>
</div>

改寫成:

<div class="pagination">
    <ul>
    <li v-bind:class="{'disabled': (currPage === 1)}"
        @click.prevent="setPage(currPage-1)"><a href="#">Prev</a></li>
    <li v-for="n in totalPage"
        v-bind:class="{'active': (currPage === (n))}"
        @click.prevent="setPage(n)"><a href="#">{{n}}</a></li>
    <li v-bind:class="{'disabled': (currPage === totalPage)}"
        @click.prevent="setPage(currPage+1)"><a href="#">Next</a></li>
    </ul>
</div>

顯示就會是正常的從 1 開始了。

再來處理顯示筆數時,需要的會有 pageStartcountOfPagetotalPage 這三個部分,其中 totalPage 會跟先前提到的 搜尋 比較有關係,因為總頁數不對,算出來的分頁也會有問題。

原本的 totalPage 長這樣:

  totalPage: function(){
    if( this.filter_name.trim() === '' ) {
        return Math.ceil(this.rows.length / this.countOfPage);
    }
    else{
        return Math.ceil(this.filteredRowCount / this.countOfPage);
    }
  }

因為資料在先前都交給 filteredRows 處理了,所以可以改寫成

  totalPage: function(){
    return Math.ceil(this.filteredRows.length / this.countOfPage);
  }

實際執行會發現最底下的分頁按鈕已經會隨著 Filter 的結果筆數而有所不同:


最後,再回到處理每頁顯示資料量的部分。

在沒有了 limitBy 之後,顯示筆數的部份我們可以透過 JavaScript 的 slice 來處理,當然也可以寫在 computed 屬性。 但這個範例只是單純在顯示作切換,所以可以直接寫在 view 上,像這樣將 filteredRows 改寫成 filteredRows.slice(pageStart, pageStart + countOfPage)

slice 不熟的朋友可參考: MDN: Array.prototype.slice()

  <tr v-for="(r, index) in filteredRows.slice(pageStart, pageStart + countOfPage)">
    <td>{{ (currPage-1) * countOfPage + index + 1 }}</td>
    <td>{{ r.name }}</td>
    <td>{{ r.age }}</td>
    <td>{{ r.gender }}</td>
  </tr>

就完成了從 V1 更新到 V2 的過程了。

完整更新的範例可在此參考:
JS Bin on jsbin.com