Vue.jsで作成したテーブルの列情報でソート(並び替え)を行いたいと思ったことはありませんか?Vue.js+JavaScriptのsortメソッドを利用するとテーブルの列のソート処理機能も簡単に実装することができます。入門書の人にもわかりやすいようにできるだけ丁寧に説明を行っています。本文書を読み終えるとJavaScriptのsortメソッドについても理解を深めることができます。

テーブルの準備

テーブルに表示されるデータを手動で作成することもできますが、通常はテーブルを作成する際はバックエンドのサーバから取得することが基本になります。そのため本文書ではテーブルのデータを外部のサービスであるJSONPlaceHolder(https://jsonplaceholder.typicode.com/)から取得します。

JSONPlaceHolderからのデータ取得はaxiosライブラリを利用して行います。またVue.jsとaxiosはcdnから利用します。任意の場所にindex.htmlファイルを作成して動作確認を進めていきます。axiosは必須ではないのでfetch関数を利用することもできます。以下のCDNのURLはVueのバージョン2です。


<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

Vue.jsのデータプロパティにusersを追加し、ライフサイクルフッックのmountedでJSONPlaceHolderからユーザデータを取得しています。


<script>
new Vue({
  el: '#app',
  data: {
    message: 'Sort Column In Table',
    users:[],
  },
  mounted(){
    axios.get('https://jsonplaceholder.typicode.com/users')
          .then(response => this.users = response.data)
      // fetch関数を利用した場合
   //   fetch('https://jsonplaceholder.typicode.com/users')
   //     .then((response) => response.json())
   //     .then((data) => (this.users = data));
  }
})
</script> 
jsonplaceholderからuser情報を取得するとname, email, website以外にも様々なデータが取得できますが、ここではこの3つのデータのみ利用します。すべてのデータはブラウザでhttps://jsonplaceholder.typicode.com/usersにアクセスすると確認することができます。
fukidashi

テーブルもHTML内にstyleタグを追加してCSSでスタイルの設定を行っています。CSSの設定で悩まないようにシンプルなものしか利用していません。

取得したusersデータをテーブルで表示するまでのコードは下記の通りです。


<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>テーブル列でソートを行う</title>
</head>
<style>
    table{
        border-collapse: collapse;
        width:100%
    }
    td, th {
        border: 1px solid #dddddd;
        text-align: left;
        padding: 8px;
    }
    th {
        color:white;
        background-color: #1E90FF;
    }
</style>
<body>
  <div id="app">
    <h1>{{ message }}</h1>
<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Website</th>
        </tr>
    </thead>
    <tbody>
        <tr v-for="user in users" :key="user.id">
            <td>{{ user.name }}</td>
            <td>{{ user.email }}</td>
            <td>{{ user.website }}</td>
        </tr>
    </tbody>
</table>
  </div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
new Vue({
  el: '#app',
  data: {
    message: 'Sort Column In Table',
    users:[],
  },
  mounted(){
    axios.get('https://jsonplaceholder.typicode.com/users')
          .then(response => this.users = response.data)
  }
})
</script> 
</body>
</html>

ブラウザで確認するとJSONPlaceHolderから取得した10名分のユーザ情報がテーブルとして表示されます。

テーブルを表示
テーブルを表示

ソート機能の追加

テーブルにソート機能を追加していきますが、テーブルの列名をクリックするとソートによる並び替えが行えるように設定していきます。

ソートキーの設定と動作確認

どの列をクリックしてソートが行われているかをsort_keyに保存するためVue.jsのデータプロパティに追加します。ページを開いた直後はどの列でもソートが行われていないため初期値はブランクとします。


//略
new Vue({
  el: '#app',
  data: {
    message: 'Sort Column In Table',
    users: [],
    sort_key: '',
  },
//略

ソートがどこ列で行うかをsort_keyに保存する必要があるのでsortByメソッドを利用して行います。


//略
data: {
  message: 'Sort Column In Table',
  users: [],
  sort_key: '',
},
methods: {
  sortBy(key) {
    this.sort_key = key;
  },
},
//略

sortByの引数の値は列名に設定するクリックイベントから取得します。

列にはクリックイベントでsortByメソッドを設定し、その引数には各列の列名を設定します。列名の名前は各環境のテーブルの構成情報によって異なります。


<tr>
  <th @click="sortBy('name')">Name</th>
  <th @click="sortBy('email')">Email</th>
  <th @click="sortBy('website')">Website</th>
</tr>

ここまでの設定で列名をクリックするとsorByメソッドが実行され、sort_keyプロパティにクリックを行った列名の名前が設定されます。

sort_keyを表示させて列名をクリックするとsort_keyが更新されるのか確認しましょう。sort_keyはテーブルの上に表示させます。


<div id="app">
  <h1>{{ message }}</h1>
  <h2>{{ sort_key }}</h2>
  <table>
    <thead>

Emailの列名をクリックするとテーブルの上部にemailが表示されたらここまでの設定は正常に行われています。他の列もクリックし、表示されるsort_keyの値が更新されることを確認してください。

ソートのキーが表示されることを確認
ソートのキーが表示されることを確認

JavaScriptのsortメソッドの確認

ソートを実装した経験のない人の場合、どのように並び替えを行うのだろうかと疑問を持つかと思います。並び替えは、JavaScriptのsortメソッドを利用して行います。

そのためJavaScriptのsortメソッドの理解をしておく必要があります。まず数字だけ値を持つ配列を使ってsortメソッドによる並び替えを確認しておきましょう。


let array = [5,3,8,1,9]
console.log(array.sort())

// Result
[ 1, 3, 5, 8, 9 ]

sortメソッドにより配列の中の数字が昇順になっていることがわかりました。降順にしたい場合はどのように行うのかという疑問が新たに湧いたと思います。その疑問を解消するため次にsortメソッドの記述方法を確認していきます。

数字の配列ではsortメソッドのみで昇順に並び替えることができますが、下記のようにコールバック関数を利用して書き換えることができます。


const array = [5, 3, 8, 1, 9];
array.sort(function (a, b) {
  if (a < b) return -1;
  if (a > b) return 1;
  return 0;
});

console.log(array);

// Result
[ 1, 3, 5, 8, 9 ]

sortメソッドのみを使用した場合と実行結果は同じです。

次のルールにしたがってa,bの2つを比較することで並び替えを行っています。sortでは配列の2つの数字を比較して戻り値が正の場合は引数aが引数bの後ろに並び替えられ、戻り値が負の場合は引数aば引数bの前に並び替えられます。戻り値が0の場合はaとbが同じ値なので並び替えは起こりません。a,bの引数を比較することで並び替えを行います。

降順の場合は戻り値を下記のように逆にします。sortメソッドを使って昇順だけではなく降順にできることがわかりました。


const array = [5, 3, 8, 1, 9];
array.sort(function (a, b) {
  if (a < b) return 1;
  if (a > b) return -1;
  return 0;
});

console.log(array);
// Result
[ 9, 8, 5, 3, 1 ]

最後の疑問は数字の配列ではなくオブジェクトの場合はどうようにソートするのでしょう。オブジェクトの場合も配列の場合と基本同じ方法です。

まずオブジェクトの値が数字の場合を確認します。


const items = [
  { name: 'Edward', number: 21 },
  { name: 'Sharpe', number: 37 },
  { name: 'And', number: 45 },
  { name: 'The', number: -12 },
  { name: 'Magnetic', number: 13 },
  { name: 'Zeros', number: 37 },
];

items.sort(function (a, b) {
  return a.number - b.number;
});

console.log(items);

//Rusult
[
  { name: 'The', number: -12 },
  { name: 'Magnetic', number: 13 },
  { name: 'Edward', number: 21 },
  { name: 'Sharpe', number: 37 },
  { name: 'Zeros', number: 37 },
  { name: 'And', number: 45 }
]

文字列の場合は以下のように記述します。toUpperCaseメソッドで小文字を大文字に変換し、大文字のみのアルファベットを利用して比較を行っています。nameAとnameBが同じ文字列の場合は戻り値を0としています。結果アルファベット順に並んでいることがわかります。


const items = [
  { name: 'Edward', number: 21 },
  { name: 'Sharpe', number: 37 },
  { name: 'And', number: 45 },
  { name: 'The', number: -12 },
  { name: 'Magnetic', number: 13 },
  { name: 'Zeros', number: 37 },
];

items.sort(function (a, b) {
  var nameA = a.name.toUpperCase();
  var nameB = b.name.toUpperCase();
  if (nameA < nameB) return -1;
  if (nameA > nameB) return 1;
  return 0;
});

console.log(items);

//Result
[
  { name: 'And', value: 45 },
  { name: 'Edward', value: 21 },
  { name: 'Magnetic', value: 13 },
  { name: 'Sharpe', value: 37 },
  { name: 'The', value: -12 },
  { name: 'Zeros', value: 37 }
]

ソート機能の設定と動作確認

computedプロパティを利用してソート機能を実行します。sort_keyがブランクの場合は並び替えは行いません。ページへのアクセス直後はsort_keyはブランクなので並び替えが行われていない状態です。

先ほど説明したsortメソッドを使って並び替えのコードを記述します。ほとんど内容はオブジェクトの並び替えの時と同じです。


//略
methods: {
  sortBy(key) {
    this.sort_key = key;
  },
},
computed: {
  sort_users() {
    if (this.sort_key != '') {
      this.users.sort((a, b) => {
        if (a[this.sort_key] < b[this.sort_key]) return -1;
        if (a[this.sort_key] > b[this.sort_key]) return 1;
        return 0;
      });
      return this.users;
    } else {
      return this.users;
    }
  },
},
//略

v-forでこれまではusersを利用していましたが、computedプロパティのsort_usersに変更を行います。


<tbody>
    <tr v-for="user in sort_users" :key="user.id">
    <td>{{ user.name }}</td>
    <td>{{ user.email }}</td>
    <td>{{ user.website }}</td>
    </tr>
</tbody>

列名をクリックするとソートが行われるのか確認を行います。Emailの列名をクリックするとメールアドレスが昇順に並ぶことが確認できます。

emailでソートを行う
emailでソートを行う

他の列をクリックしてもソートが行われることを確認してください。

昇順、降順の切替

ここまでの設定ではソートを行うことができましたが、昇順、降順の制御は行うことができず、すべて昇順になっていました。

昇順、降順の機能を実装ではどのように昇順と降順を切り替えるかがポイントとなります。本文書では列名を1度クリックすると昇順、続けて同じ列名をクリックすると降順に切り替わることとします。また別の列名をクリックすると最初は昇順にソートになることとします。

新たにデータプロパティsort_acsを追加し値をtrueとします。


data: {
  message: "Sort Column In Table",
  users: [],
  sort_key: "",
  sort_asc: true,
},

列名をクリックした場合に昇順か降順かを切り替えるので、sortByメソッドにコードを追加します。同じ列名をクリックしたい場合のみsort_ascの値が切り替わります。同じ列を連続クリックする場合は1回目は昇順、2回目は降順、3回目は昇順という風に切り替わります。


methods: {
  sortBy(key) {
    this.sort_key === key
      ? (this.sort_asc = !this.sort_asc)
      : (this.sort_asc = true);
    this.sort_key = key;
  },
},

JavaScriptのsortメソッドの説明箇所で昇順と降順は戻り値を逆にすると説明下通りsetという変数を使って戻り値の値を変更しています。


computed: {
  sort_users() {
    if (this.sort_key != "") {
      let set = 1;
      this.sort_asc ? (set = 1) : (set = -1);
      this.users.sort((a, b) => {
        if (a[this.sort_key] < b[this.sort_key]) return -1 * set;
        if (a[this.sort_key] > b[this.sort_key]) return 1 * set;
        return 0;
      });
      return this.users;
    } else {
      return this.users;
    }
  },
},

昇順か降順か現在の値がわかるように以下のようにHTMLに追加を行います。


<div id="app">
  <h1>{{ message }}</h1>
  <h2>{{ sort_key }}: {{ sort_asc ? '昇順' : '降順'}}</h2>

Emailの列を1度クリックすると昇順、次にクリックすると以下のように降順に並び替えが行われることを確認してください。

昇順、降順をブラウザ上に表示
昇順、降順をブラウザ上に表示

昇順、降順の矢印を追加

どの列で昇順または降順で表示されているか目で見てわかるように矢印の追加を行います。

矢印の付与はCSSで行い疑似要素タグを利用します。CSSは以下の2つを設定します。


.asc::after {
  content: "↓";
}
.desc::after {
  content: "↑";
}

列のthタグにdescクラスを適用します。


<th @click="sortBy('name')" class="desc">Name</th>

ブラウザで見ると列名の横(Name)に矢印が表示されます。

疑似要素afterを使って矢印を表示
疑似要素afterを使って矢印を表示

classを直接設定していますが、クリックを押した列のみ動的に矢印を上向き(昇順の場合), 下向き(降順の場合)に表示されるようにコードを追加していきます。

classを動的に設定するためにclassにv-bindを設定します。sortByと同様にclassバインドで設定するメソッドには引数に列名を使用します。

thタグにv-bindのclassを追加しメソッドaddClassを追加します。addClassメソッドは後ほどvueに追加します。


<tr>
  <th @click="sortBy('name')" :class="addClass('name')">Name</th>
  <th @click="sortBy('email')" :class="addClass('email')">Email</th>
  <th @click="sortBy('website')" :class="addClass('website')">
    Website
  </th>
</tr>

vueにaddClassメソッドを追加します。sort_keyとkeyが同じ値つまり列名をクリックされ、sort_ascがtrueの場合はthタグにascクラスを適用し、sort_keyとkeyが同じ値でsort_ascがfalseの時にdescクラスを追加します。


addClass(key) {
  return {
    asc: this.sort_key === key && this.sort_asc,
    desc: this.sort_key === key && !this.sort_asc,
  };
},

コード追加後ブラウザで確認します。Emailの列名を2回連続でクリックすると上向きの矢印がEmailの列名の横に表示されることが確認できます。他の列名をクリックして矢印が正しく表示されるかを確認してください。

emailに上向きの矢印表示
emailに上向きの矢印表示

テーブルへのソート機能と昇順、降順機能の実装を行うことができました。

Vue 2での最終的なコード

最終的なコードは下記の通りです。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>テーブル列でソートを行う</title>
  </head>
  <style>
    table {
      border-collapse: collapse;
      width: 100%;
    }
    td,
    th {
      border: 1px solid #dddddd;
      text-align: left;
      padding: 8px;
    }
    th {
      color: white;
      background-color: #1e90ff;
    }
    .asc::after {
      content: '↓';
    }
    .desc::after {
      content: '↑';
    }
  </style>
  <body>
    <div id="app">
      <h1>{{ message }}</h1>
      <h2>{{ sort_key }}: {{ sort_asc ? '昇順' : '降順'}}</h2>
      <table>
        <thead>
          <tr>
            <th @click="sortBy('name')" :class="addClass('name')">Name</th>
            <th @click="sortBy('email')" :class="addClass('email')">Email</th>
            <th @click="sortBy('website')" :class="addClass('website')">
              Website
            </th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="user in sort_users" :key="user.id">
            <td>{{ user.name }}</td>
            <td>{{ user.email }}</td>
            <td>{{ user.website }}</td>
          </tr>
        </tbody>
      </table>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
      new Vue({
        el: '#app',
        data: {
          message: 'Sort Column In Table',
          users: [],
          sort_key: '',
          sort_asc: true,
        },
        methods: {
          sortBy(key) {
            this.sort_key === key
              ? (this.sort_asc = !this.sort_asc)
              : (this.sort_asc = true);
            this.sort_key = key;
          },
          addClass(key) {
            return {
              asc: this.sort_key === key && this.sort_asc,
              desc: this.sort_key === key && !this.sort_asc,
            };
          },
        },
        computed: {
          sort_users() {
            if (this.sort_key != '') {
              let set = 1;
              this.sort_asc ? (set = 1) : (set = -1);
              this.users.sort((a, b) => {
                if (a[this.sort_key] < b[this.sort_key]) return -1 * set;
                if (a[this.sort_key] > b[this.sort_key]) return 1 * set;
                return 0;
              });
              return this.users;
            } else {
              return this.users;
            }
          },
        },
        mounted() {
          axios
            .get('https://jsonplaceholder.typicode.com/users')
            .then((response) => (this.users = response.data));
        },
      });
    </script>
  </body>
</html>