Sae/note

【もりけん塾 @JS課題21・22】JSONデータからtableを作成する sort機能追加 編

目次

    管理人

    管理人

    こんにちは、さえと申します👩‍💻
    現在 フロントエンドエンジニアになるために 日々勉強をしています
    このブログは その勉強の記録と アウトプットのために運営をしています✨

    現在、もりけん塾で
    マークアップエンジニアの方がフロントエンドエンジニアになる為の課題に取り組んでいます。
    今回は課題21・22の実装で学んだことをブログへまとめます

    課題21・22

    前回の課題で作成したtableへ機能追加を行いました

    前回の課題

    https://itosae.com/js_lesson20/

    課題の仕様

    • id・年齢に sort機能を追加する
    • 初期状態は サーバーからのレスポンス順
    • 上下の矢印両方が共にクリッカブル領域になっていて 押すと以下の順で変化する
      初期状態 ▶︎ 昇順 ▶︎ 降順 ▶︎ 初期状態 ...

    制作物

    Image from Gyazo

    https://codesandbox.io/s/lesson22-9b83p

    sortボタンの作成・追加

    sortボタンの状態を data属性で管理する仕様にしました
    状態は全部で3種類で、初期状態(default)、昇順(asc)、降順(desc) です

    const createSortButton = () => {
      const sortButton = createElementWithClassName(
        "button",
        "sort-button js-sort-button"
      );
      // サーバーからからのレスポンス順を初期設定とする
      sortButton.dataset.sortStatus = "default";
      return sortButton;
    };
    

    ※ createElementWithClassName(type,class)はclass名を付与したHTMLタグを生成する関数

    また、矢印画像は CSSで実装しました

    .sort-button[data-sort-status="default"] {
      background-image: url(./img/standard.svg);
    }
    
    .sort-button[data-sort-status="asc"] {
      background-image: url(./img/asc.svg);
    }
    
    .sort-button[data-sort-status="desc"] {
      background-image: url(./img/desc.svg);
    }
    

    イベント定義

    作成したsortボタンに クリックイベントを定義します

    定義するイベントは...
    - sortボタンの状態(status)を切り替える
    - クリックが起きたセル(id or 年齢)を取得し、状態に合わせてsortさせる
    - 順番を書き換える
    - クリックされたセル以外の項目のsortボタンはstatusをdefaultにする
    (例 : "ID"がクリックされた場合、"年齢"のsortボタンの状態は defaultにする)

    const setClickInSortButton = () => {
    
    // sort前の初期状態のrowsを配列に
      const defaultRows = [...document.querySelectorAll(".js-tr-inTbody")];
    // sortボタンを全て取得し配列に
      const sortButtons = [...document.querySelectorAll(".js-sort-button")];
    
      sortButtons.forEach((sortButton) => {
        sortButton.addEventListener("click", (e) => {
          resetSortButtonsExceptTarget(sortButtons, e.target);
          const nextStatus = switchSortStatus(e.target.dataset.sortStatus);
          e.target.dataset.sortStatus = nextStatus;
          const sortedRows = getSortedRows(nextStatus, defaultRows, e.target);
          const tbody = document.querySelector("tbody");
          sortedRows.forEach((row) => {
            tbody.appendChild(row);
          });
        });
      });
    };
    

    関数をもう少し細かく見ていきます

    状態(status)の切り替え

    sortボタンに設定している data属性の切り替えを行います
    引数のstatusには sortボタンの現在の状態(status)が渡ってきます
    渡ってきた 状態(status)を元に switch文を使用し 切り替えを行います
    また、caseのどれにも当てはまらない場合は defaultの状態にする様にしました

    const switchSortStatus = (status) => {
      switch (status) {
        case "default":
          return "asc";
    
        case "desc":
          return "default";
    
        case "asc":
          return "desc";
    
        default:
          return "default";
      }
    };
    

    sort

    sortする方法を整理します

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

    以下の様な関数を定義しました
    引数へは(現在のstatus、配列に格納された初期状態の列達(row)、イベントが起きたbutton要素)が渡ってきます

    まず最初に sortボタンのstatusがdefaultなのか それ以外なのか で分岐します
    - statusがdefault ..... 早期リターンし defaultRows(初期状態の列群)を返す
    - statusがdefault以外 ...... statusによってさらに条件分岐

    一つめの関門でstatusがdefault以外だった場合、asc(昇順) or desc(降順) で更に条件分岐します
    また、ascでもdescでもなかった場合はエラーをポイと投げます

    const getSortedRows = (status, defaultRows, target) => {
      if (status === "default") return defaultRows;
      const index = findClickedCellIndex(target);
      switch (status) {
        case "asc":
          return [...defaultRows].sort(
            (a, b) => a.children[index].textContent - b.children[index].textContent
          );
        case "desc":
          return [...defaultRows].sort(
            (a, b) => b.children[index].textContent - a.children[index].textContent
          );
        default:
          throw new Error(`${status} is not provided.`);
      }
    };
    

    変数findClickedCellIndexへは、クリックが起きたセルのindexが格納されます
    このindexを元にsortを行いました

    const findClickedCellIndex = (target) =>[...document.querySelectorAll(".js-th")].indexOf(target.parentElement);
    

    sort部分は以下の様なコードで実装しました
    sortは破壊的メソッドで、元の配列に影響を与えてしまうので、
    [...defaultRows]とし spred構文を使用しています。
    こうすることで defaultRowsのcloneに対してsortさせてるので 元の配列は壊れません

    case "asc":
     return [...defaultRows].sort((a, b) => a.children[index].textContent - b.children[index].textContent);
    case "desc":
     return [...defaultRows].sort((a, b) => b.children[index].textContent - a.children[index].textContent);
    

    先程のindexを使用し、rowの中でクリックされたセルに対する項目をsortさせました

    reset(statusをdefaultに戻す)

    sortボタンの中で、今クリックされたボタン(target)で なければ、状態(status)をdefaultにする

    const resetSortButtonsExceptTarget = (sortButtons, target) => {
      sortButtons.filter((button) => button !== target).forEach((value) => {
        value.dataset.sortStatus = "default";
      });
    }
    

    まとめ

    レビューで switch文を使用した条件分岐の書き方をアドバイス頂きました
    普段、問答無用で if~else文で書いていた条件分岐ですが、caseによって使い分けることが重要だと学びました。

    参考記事

    https://qiita.com/taiju_suzuki/items/e2bf11fcf1645623235f

    http://dqn.sakusakutto.jp/2012/08/if-else.html

    今回の課題でレビューしてくれた方々ありがとうございました
    Thanks to もりけん先生もなかさんちひろさん


    もりけん塾でJavaScriptを学習をしています!
    もりけん先生のTwitter:https://twitter.com/terrace_tech

    https://kenjimorita.jp/