Flexboxが便利すぎる!要素を自在にソートできるjQueryプラグイン作ってみた

2017/03/23

George Martsoukos

77

Articles in this issue reproduced from SitePoint
Copyright © 2017, All rights reserved. SitePoint Pty Ltd. www.sitepoint.com. Translation copyright © 2017, KADOKAWA ASCII Research Laboratories, Inc. Japanese syndication rights arranged with SitePoint Pty Ltd, Collingwood, Victoria,Australia through Tuttle-Mori Agency, Inc., Tokyo

Flexboxを使えば要素の並び替えはとても簡単。そこで、カスタムデータ属性をうまく使って、Flexboxの内容でソートできるjQueryプラグインを考えて作ってみました。

この記事では、カスタムデータ属性の値をもとに要素をソートする簡単なjQueryプラグインの作成方法を順を追って説明していきます。

どのようなプラグインかは、CodePenのデモを参照してください。

:この記事では、jQueryプラグインの開発方法develop jQuery pluginsflexboxの基本的な知識を持った読者を想定しています。知識が十分でない場合は、リンク先を参照してください。

アクセシビリティの問題

プラグインを作成するのに、flexboxを利用します。

デフォルトでは、flexアイテムはソースの順番に配置されていますが、orderプロパティで親要素のflexコンテナ内での順番を変更できます。orderの値が小さいアイテムが最初に表示されます。次の例を参照してください。

order値が同じアイテムが複数ある場合、アイテムの順番はソースの順番で決定されます。

orderプロパティを使えば、簡単に要素の並べ替えができますが、アクセシビリティに制限が生じます。ソースの順番と表示の順番が対応しなくなります。この問題については、こちらの記事を読んでください(特に「Source Order vs. Visual Order(ソースの順番vs表示の順番)」のセクション)。

これからプラグインの作成方法を説明していきますが、アクセシビリティに問題が生じることは覚えておいてください。

マークアップ

最初に、12個のリスト項目からなる番号なしリストを定義します。

<ul class="boxes">
  <li>
    <a href="#">
      Box1
      <div class="details">
        <span class="length">13M</span>
        <span class="price">670€</span>
      </div>
    </a>
  </li>

  <!-- more list items here -->

</ul>

リスト項目に.details要素があることに注意してください。.details要素は、リスト項目に関する情報を示しています。あとで分かりますが、この情報を保持するためにカスタムHTML属性を追加します。

.details要素は絶対に必要なわけではありません。記事では、目的の要素がどのようにソートされるかを分かりやすくするために利用しています。

次に、ソートの基準となる属性を決めます。例ではpricelength属性に決めます。これらの名前をリスト項目にカスタム属性(data-pricedata-length)を適用するのに使用します。属性の値は.details要素の一部である.length.price要素のテキスト値(数値に限ります)に一致させます。

たとえば、1番目のリスト項目の属性は次のようになります。

<li data-length="13" data-price="670">
  <a href="#">
    Box1
    <div class="details">
      <span class="length">13M</span>
      <span class="price">670€</span>
    </div>
  </a>
</li>

ここで、リスト項目をソートするために用いる要素として<select>要素を指定します。

<select class="b-select">
  <option disabled selected>Sort By</option>
  <option data-sort="price:asc">Price Ascending</option>
  <option data-sort="price:desc">Price Descending</option>
  <option data-sort="length:asc">Length Ascending</option>
  <option data-sort="length:desc">Length Descending</option>
</select>

コードから分かるように、すべての<option>要素はdata-sort属性を持ちます(最初の要素以外)。この属性の値を記述するには、次の書式を用います。

<option data-sort="price:asc">

すなわち、ソートに用いる属性を値とし、値のあとにコロンを付け、「asc」または「desc」という識別子をつけます。

CSSスタイル

マークアップの準備ができたので、作成中のページに基本スタイルを加えます。具体的には、順序なしリストをflexコンテナとして定義し、リスト項目の幅をwidth:25%とします。CSSルールは次のようになります。

.boxes {
  display: flex;
  flex-wrap: wrap;
}

.boxes li {
  width: 25%;
}

プラグインの作成

これから作成するプラグインをnumericFlexboxSortingと呼ぶことにします。

プラグインの初期化

プラグイン作成の手順を示す前に、プラグインの目的に応じた初期化の方法を説明します。通常、プラグインは次の方法で初期化します。

$("your-select-tag").numericFlexboxSorting();

今回の場合は次のようにします。

$(".b-select").numericFlexboxSorting();

デフォルトでは.boxes liクラスで要素をソートしますが、elToSort構成プロパティの値を変更するとオーバーライドできます。

$(".b-select").numericFlexboxSorting({
  elToSort: "the-elements-you-want-to-sort"
});

ステップの詳細

いよいよ開発手順について説明します。

最初に、jQueryのプロトタイプ($.fn)オブジェクトをnumericFlexboxSortingに加えて拡張します。

$.fn.numericFlexboxSorting = function() {
  const $select = this;

  // do stuff here

  return $select; 
};

このメソッドでは、キーワードthis<select>要素を参照します。プラグインを作成すると同時に、この要素を返さなければ、メソッドチェーンが切れてしまうからです。

例として、次のコードについて考えてみます。

$(".b-select").numericFlexboxSorting().css("background", "red");

ここで、ターゲット要素を返さなければ、cssメソッドはなにもしないことになります。

すでに述べたように、デフォルトでプラグインは.boxes liクラスで要素をソートしますが、必要があればこの動作をオーバーライドできなければなりません。そのため、jQueryのextendメソッドを利用します。

$.fn.numericFlexboxSorting = function(options) {
  const settings = $.extend({
    elToSort: ".boxes li"
  }, options);

  // do stuff here
};

プラグインは昇順または降順で数字をソートします。それを念頭に、あとで用いることになりますが、これに対応する変数を定義します。

$.fn.numericFlexboxSorting = function(options) {
  const ascOrder = (a, b) => a - b;
  const descOrder = (a, b) => b - a;

  // do stuff here
};

ドロップダウンリストからユーザーがオプションを選んだら(1番目は除く)、値を求め評価します。そのためにはchangeイベントを用います。

$.fn.numericFlexboxSorting = function(options) {
  const $select = this;

  $select.on("change", () => {
    const selectedOption = $select.find("option:selected").attr("data-sort");
    sortColumns(settings.elToSort, selectedOption);
  });

  // do stuff here
};

イベントハンドラーの内部では、2つのことが実行されています。

  1. 選択されたオプションのdata-sort属性の値(たとえば、price:asc)を求める
  2. sortColumns関数を呼び出す

sortColumns関数は2つのパラメータをとります。

  1. ソートしたい要素
  2. 選択されたオプションのdata-sort属性の値

関数の中は次のようになっています。

function sortColumns(el, opt) {
  const attr = "data-" + opt.split(":")[0];
  const sortMethod = (opt.includes("asc")) ? ascOrder : descOrder;
  const sign = (opt.includes("asc")) ? "" : "-";

  // 1
  const sortArray = $(el).map((i, el) => $(el).attr(attr)).sort(sortMethod);

  // 2
  for (let i = 0; i < sortArray.length; i++) {
    $(el).filter(`[${attr}="${sortArray[i]}"]`).css("order", sign + sortArray[i]);
  } 
}

中でなにが起こっているのか説明します。

  1. ソートの基準とする属性によって(たとえば、priceまたはlength)、ターゲット要素のdata-* attributeの値を求め、配列に格納する。さらに、ターゲット要素のソート方法に従って、昇順または降順に配列をソートする
  2. 配列の中を調べて、関連する要素を見つけ出しorderプロパティの値(正または負の値)をアサインする。その値は、対応するdata-* attributeの値によって決定される。
    たとえば、ユーザーがprice:ascオプションを選んだ場合data-price:315である要素はorder:315を受け取る。
    How the plugin works when we sort in ascending order
    一方、ユーザーがprice:descオプションを選んだ場合order:-315を受け取る
    How the plugin works when we sort in descending order

最後に$変数を使うほかのライブラリーと矛盾が生じないようにするため、即時関数でコードをラップします。

(function($) {
  $.fn.numericFlexboxSorting = function(options) {
    // do stuff here
  };
})(jQuery);

これでプラグインの用意ができました。Codepenでデモが見られます。

プラグインの制限事項

プラグインに1つ大きな制限があることを思い出してください。アクセシブルでないことです。その証拠に、ドロップダウンリストからオプションを選択し、キーボードを使ってリンクをナビゲートしてください(ペンをクリックしてTabキーを押す)。要素がフォーカスされるのは、CSSの順番ではなく、DOMの順となります。

The plugin is not accessible because there's a disconnection between the source order and the visual order

もう1つ気をつけなければならないのは、このプラグインでできることはごく基本的なことであり、動作条件が限られていることです。たとえば、属性の文字変数は数値でなければなりません。ターゲット要素として、数値が入ることを前提としているorderを使っているからです。

もちろん、ソートやフィルタの目的では、より信頼性が高く、強力なライブラリーがあります。たとえば、次の2つです。

とくにMixItUpについては、SitePointの記事(日本語訳)で基礎を説明しています。

ブラウザーのサポート

このプラグインはflexboxを使うので、プラグインのサポートはflexboxのブラウザーサポートで決まります。幸い最近では、多くのブラウザーがflexboxをサポートするようになりました。

主なブラウザーでのflexboxのサポート状況はCan I Use flexbox?を参照してください。

次のステップ

このプラグインの機能を拡張する場合、次のようなことが考えられるでしょう。

  • ランダムソーティングの機能を加える
  • ソート制御が<select>要素、<button>要素、あるいはほかのタイプを選べるようにする。この場合、次のようなセッティングを加えても良いかもしれない
    $(".b-select").numericFlexboxSorting({
      elToSort: "the-elements-you-want-to-sort",
      controls: {
        select: true, // fires change event
        button: false // fires click event
      }
    });
    

最後に

この記事では、jQueryプラグインの作成手順について紹介しました。このプラグインでは、カスタムデータ属性に基づいてソートができるというflexboxの機能を利用しました。

(原文:Quick Tip: User Sortable Lists with Flexbox and jQuery

[翻訳:関 宏也/編集:Livit

Copyright © 2017, George Martsoukos All Rights Reserved.

George Martsoukos

George Martsoukos

フリーランスWeb開発者です。SitePointTuts+など、Web構築系の大手マガジンに執筆するWebオタクでもあります。Webに関することなら、なんでもこい!とばかりに、中毒者のごとく、最新テクノロジーについて毎日学び続けています。

Loading...