この記事では、カスタムデータ属性の値をもとに要素をソートする簡単なjQueryプラグインの作成方法を順を追って説明していきます。
どのようなプラグインかは、CodePenのデモを参照してください。
注:この記事では、jQueryプラグインの開発方法develop jQuery pluginsとflexboxの基本的な知識を持った読者を想定しています。知識が十分でない場合は、リンク先を参照してください。
アクセシビリティの問題
プラグインを作成するのに、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要素は絶対に必要なわけではありません。記事では、目的の要素がどのようにソートされるかを分かりやすくするために利用しています。
次に、ソートの基準となる属性を決めます。例ではpriceとlength属性に決めます。これらの名前をリスト項目にカスタム属性(data-priceとdata-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つのことが実行されています。
- 選択されたオプションのdata-sort属性の値(たとえば、price:asc)を求める
- sortColumns関数を呼び出す
sortColumns関数は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]);
}
}
中でなにが起こっているのか説明します。
- ソートの基準とする属性によって(たとえば、priceまたはlength)、ターゲット要素のdata-* attributeの値を求め、配列に格納する。さらに、ターゲット要素のソート方法に従って、昇順または降順に配列をソートする
- 配列の中を調べて、関連する要素を見つけ出しorderプロパティの値(正または負の値)をアサインする。その値は、対応するdata-* attributeの値によって決定される。
たとえば、ユーザーがprice:ascオプションを選んだ場合data-price:315である要素はorder:315を受け取る。
一方、ユーザーがprice:descオプションを選んだ場合order:-315を受け取る
最後に$変数を使うほかのライブラリーと矛盾が生じないようにするため、即時関数でコードをラップします。
(function($) {
$.fn.numericFlexboxSorting = function(options) {
// do stuff here
};
})(jQuery);
これでプラグインの用意ができました。Codepenでデモが見られます。
プラグインの制限事項
プラグインに1つ大きな制限があることを思い出してください。アクセシブルでないことです。その証拠に、ドロップダウンリストからオプションを選択し、キーボードを使ってリンクをナビゲートしてください(ペンをクリックしてTabキーを押す)。要素がフォーカスされるのは、CSSの順番ではなく、DOMの順となります。
もう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]