最新のグリッドUIコンポーネントがどのように見えるか、およびどの機能を提供する必要があるかについては、オンラインで多くの情報があります。 一般的な要件の1つは、グリッドコンポーネントを簡単に視覚化し、情報に基づいて操作できるようにすることです。

これはVue.jsを使用して簡単に実現できます。この記事では、次のユーザーエクスペリエンスで構成されるグリッドコンポーネントを構築します。

  • 固定ヘッダーとスクロール可能なテーブル本体。
  • テーブルページ間のページネーション。
  • ページあたりの行数を変更する機能。

Vue.jsがインストールされていると仮定します。

以下のコードは本番環境に対応しておらず、教育目的でここに示されていることに注意してください。

コード

以下のコードは、公式のVue.jsドキュメントから抜粋した修正例です。

次のコードを使用して、新しい単一ファイルコンポーネントGrid.vueを作成します。

Grid.vue
<div class="wrapper">
  <form id="search">
    Search <input name="query" v-model="searchQuery">
  </form>
  <div id="grid-template">
    <div class="table-header-wrapper">
      <table class="table-header">
        <thead>
          <th v-for="key in columns"
            @click="sortBy(key)"
            :class="{ active: sortKey == key }"
          >
            {{ key | capitalize }}
            <span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'"></span>
          </th>
        </thead>
      </table>
    </div>
    <div class="table-body-wrapper">
      <table class="table-body">
        <tbody>
          <tr v-for="entry in filteredData">
            <td v-for="key in columns"> {{entry[key]}}</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</div>
export default {
  name: "grid",
  props: {
    data: Array,
    columns: Array,
  },
  data(){
    return {
      searchQuery: '',
      sortKey: '',
      sortOrders: {},
    }
  },
  computed: {
    filteredData: function () {
      let sortKey = this.sortKey;
      let filterKey = this.searchQuery && this.searchQuery.toLowerCase();
      let order = this.sortOrders[sortKey] || 1;
      let data = this.data;

      if (filterKey) {
        data = data.filter(function (row) {
          return Object.keys(row).some(function (key) {
            return String(row[key]).toLowerCase().indexOf(filterKey) > -1;
          })
        })
      }
      if (sortKey) {
        data = data.slice().sort(function (a, b) {
          a = a[sortKey];
          b = b[sortKey];
          return (a === b ? 0 : a > b ? 1 : -1) * order;
        })
      }
    return data;
    },
  },
  filters: {
    capitalize: function (str) {
      return str.charAt(0).toUpperCase() + str.slice(1);
    }
  },
  methods: {
    sortBy: function (key) {
      this.sortKey = key;
      this.sortOrders[key] = this.sortOrders[key] * -1
    },
  },
  created(){
    let sortOrders = {};
    this.columns.forEach(function (key) {
      sortOrders[key] = 1;
    })
    this.sortOrders = sortOrders;
  }
}
body{
  font-family: Helvetica Neue, Arial, sans-serif;
  font-size: 14px;
  color: #555;
}

table {
  border-spacing: 0;
  width: 100%;

}

th {
  background-color: #008f68;
  color: rgba(255,255,255,0.66);
  cursor: pointer;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

td {
  border-bottom: 1px #008f68 solid;
}

th, td {
  min-width: 150px;
  padding: 10px 20px;
}

th.active {
  color: #fff;
}

th.active .arrow {
  opacity: 1;
}

.arrow {
  display: inline-block;
  vertical-align: middle;
  width: 0;
  height: 0;
  margin-left: 5px;
  opacity: 0.66;
}

.arrow.asc {
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-bottom: 4px solid #FAE042;
}

.arrow.dsc {
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 4px solid #FAE042;
}

#grid-template {
  display: flex;
  display: -webkit-flex;
  flex-direction: column;
  -webkit-flex-direction: column;
  width: 600px;
}

次に、このコンポーネントをにインポートします App.vue ファイルを作成し、データを追加します。

App.vue
import Grid from './Grid'
export default {
  name: 'app',
    data() {
      return {
        gridData: [
          { name: 'American alligator', location: 'Southeast United States' },
          { name: 'Chinese alligator', location: 'Eastern China' },
          { name: 'Spectacled caiman', location: 'Central & South America' },
          { name: 'Broad-snouted caiman', location: 'South America' },
          { name: 'Jacaré caiman', location: 'South America' },
          { name: 'Black caiman', location: 'South America' },
        ],
        gridColumns: ['name', 'location'],        
      }
  },
  components: {
    Grid
  }
}

対応するデータを渡してコンポーネントを使用します。

<grid :data="gridData" :columns="gridColumns"></grid>

次に、このコンポーネントを、導入部で説明した追加機能で拡張してみましょう。

固定ヘッダー

記事TheHoly Grail:Pure CSS Scrolling Tables with Fixed Headers の手法を使用しており、マークアップはこの記事に従ってすでに設定されています。 テーブルの高さを定義し、テーブル本体をスクロール可能にします。

.wrapper{
  height: 300px;
}

#grid-template > .table-body-wrapper {
  width: 100%;
  overflow-y: scroll;
}

#grid-template {
  height: 100%;
}

#grid-template > .table-body-wrapper {
  flex: 1;
}

ページ付け

まず、ページあたりの行数と開始位置を定義しましょう。 このため、2つの新しいプロパティが追加されます。

startRow: 0,
rowsPerPage: 10

現在のページ番号を表示するナビゲーションのマークアップを追加しましょう。

<div id="page-navigation">
  <button>Back</button>
  <p>{{startRow / rowsPerPage + 1}} out of {{Math.ceil(filteredData.length / rowsPerPage)}}</p>
  <button>Next</button>
</div>

ページ間で変更するには、ボタンのクリックイベントハンドラーを追加します。 @click=movePages(-1) 戻るボタンと @click=movePages(1) 次へボタンの場合。

このメソッドは、ページの最初の行数をカウントします。

movePages: function(amount) {
  let newStartRow = this.startRow + (amount * this.rowsPerPage);
  if (newStartRow >= 0 && newStartRow < this.filteredData.length) {
    this.startRow = newStartRow;
  }
}              

次に、現在のページの行をフィルタリングして返す計算プロパティを追加します。

dataPerPage: function() {
  return this.filteredData.filter((item, index) => index >= this.startRow && index < (this.startRow + this.rowsPerPage))
}

ページ間を移動できるため、更新されたデータを表示する必要があります。 変化する .table-body filteredDataからdataPerPageへ:

<tr v-for="entry in dataPerPage">
  <td v-for="key in columns"> {{entry[key]}}</td>
</tr>

長さの選択

行数を設定できるようにするには、v-modelrowsPerPageプロパティと等しい単純な選択を追加します。

オプションについては、pageSizeMenuプロパティを数値の配列に設定します。

pageSizeMenu: [10, 20, 50, 100]
<select v-model="rowsPerPage">
  <option v-for="pageSize in pageSizeMenu" :value="pageSize">{{pageSize}}</option>
</select>

そして、これはあなたがページごとの行数を変更する能力を持つために必要なすべてです。 双方向のデータバインディングのおかげで、Vueが残りの作業を行います。