🙃

動的にCSSを生成してテーブルをフィルター

2020/12/23に公開

これはJavscript Advent Calendar 2020の23日目の記事です。初zennです!

個人開発でテーブルをフィルターする必要があって、遊び半分でちょいとぶっ飛んだやり方を思いついたので共有したいと思います。決して薦めているわけではないのですが、面白いのでエンターテインメントとして見てください。

JSFiddleでソースとプレビューを確認できます

背景

私はバックエンドな人間なので、できるだけサーバー側に寄せています。フロントエンドでは外部パッケージ・Webpackなどを絶対に使わない主義です[1]
しかし、たまにはフロントエンドでやった方がスムーズな処理があるので、なるべく少ない行数でJSの「ちょい足し」をしています。

HTML

HTMLはSSRでやります。自分はGoのhtml/templateを使っていますが、なんでもOKです。
音楽プレイヤーの曲一覧というイメージです。

<html>
  <head>
    <style id="filter-style"></style>
  </head>
  <form onsubmit="return false;">
    <label for="filter-input">filter:</label> 
    <input id="filter-input" type="text" placeholder="try 'love' or 'beatles'" oninput="return updateFilter(this),false;">
  </form>
  
  <table>
    <thead>
      <th>artist</th><th>album</th><th>title</th>
    </thead>
    <tbody>
      <tr data-artist="The Beatles" data-album="Yellow Submarine" data-title="All You Need is Love">
        <td>The Beatles</td><td>Yellow Submarine</td><td>All You Need Is Love</td>
      </tr>
      <tr data-artist="The Beatles" data-album="Yellow Submarine" data-title="Yellow Submarine">
        <td>The Beatles</td><td>Yellow Submarine</td><td>Yellow Submarine</td>
      </tr>
      <tr data-artist="Haddaway" data-album="The Album" data-title="What Is Love">
        <td>Haddaway</td><td>The Album</td><td>What Is Love</td>
      </tr>
      <!-- ... -->
    </tbody>
  </table>
</html>

ポイントは、それぞれの行にdata-属性を足しています。この属性を使ってフィルターを行います。<input>が変わったら、updateFilter()を呼ぶようにしています。あと、onsubmitを無効にしています。

JS

JSはCSSを動的に変更する役割を果たしています。

const style = document.getElementById("filter-style");

function updateFilter(elem) {
  let input = elem.value;
  if (input.length == 0) {
    // don't filter if empty
    style.innerHTML = "";
    return;
  }
  // escape quotes
  input = input.replace(/\x22/g, '\\\x22');
  input = input.replace(/\x27/g, '\\\x27');
  // generate CSS
  style.innerHTML = "table tbody tr[data-title*='" + input + "' i], table tbody tr[data-album*='" + input + "' i], table tbody tr[data-artist*='" + input + "' i] { display: revert; }\ntable tbody tr { display: none; }";
}

inputが空だったらCSSを空にします。そうでなかったら、入力をエスケープしてCSSを生成します。

たとえば、hogeと入力したらCSSをこうなります

/* 当てはまる */
table tbody tr[data-title*='hoge' i], table tbody tr[data-album*='hoge' i], table tbody tr[data-artist*='hoge' i] {
	display: revert;
}

/* 当てはまらない */
table tbody tr {
	display: none;
}

CSSの属性セレクターを使って当てはまる行だけを表示させています。
例としてカンマ区切りをして複数の属性を見ていますが、用途に合わせて必要な属性に変えるといいでしょう。
また、[attr*=value] は「attr という名前の属性の値が、文字列中に value を1つ以上含む要素」となるが、他にも色々あります。たとえば、タグの仕組みは <tr data-tags="hoge fuga poyo">[data-tags~='hoge'] でhogeタグのある要素をセレクトできます。
最後に i を使って大文字・小文字を区別しないようにしています。

今回はdisplayを変えていますが、background-colorを変えて強調したりすることも簡単です。

ちなみに document.styleSheets でCSSOMをいじれますが、<style>要素のinnerHTMLの上書きより速いかはまだ試してないです。

【おまけ】JSで当てはまる要素を取得

getComputedStyleでフィルターされた要素を取得することができます。

Array.from(document.querySelectorAll("tbody tr")).filter(e => getComputedStyle(e).display != "none");

ただし、ここまでやるとやっぱりJSONとかを使った方がいいでしょうかね。

最後に

「で、使えるの?」という疑問に対しては詳しく分からないですが、使ってみた結果、結構速いです。やっぱりブラウザのCSS周りがかなり最適化されいるので、ひょっとしてDOMをいじるよりは速いかも?
誰かぜひプロファイリングをやってみてください。
一応、個人開発でこれを使っていて、ユーザーから「めっちゃ速い」などと良い評価を頂いています。
仕事では使わないかな🙈

最後にもう一度言いますが、これは遊びだけで、おすすめしているわけじゃないです😇
皆さんもぜひぶっ飛んだWeb開発をやりましょう。楽しいですよ!

もし興味あったら、ツイッターでよく変なコードなどを呟いているので見てみてください~ @guregu

脚注
  1. あくまで個人開発の話です。仕事ではちゃんとしています😉 ↩︎

Discussion