現在、もりけん塾にてJavaScriptを勉強させていただいております。その記録。
今回は課題21についての備忘録です。

考え方が間違っている箇所もあるかと思いますが、お気づきの際はご指摘いただければと思います。

課題21について

仕様

  1. こちらのようなテーブルを画面遷移してから3秒後に解決されるPromiseが返すオブジェクトを元に作り、 idがソートできる機能を作ってください
  2. ソートは通常時はidが適当でもよく
  3. ソートが昇順の場合は上矢印がアクティブ、下矢印がdisabled、1,2,3,4,5の順番で表示され、降順の場合はその逆、

通常時の矢印クリック(クリッカブル領域は2つの矢印です。上下別々のクリッカブル領域でではなく)を押すと画像のように変化します

制作物

コードはこちらから(codesandbox)

※codesandboxではエラーが出る場合がありますが、リロードすれば動きます。

実装内容

課題20で作成したテーブル要素にソート機能を追加しました。

ソートボタンの作成

まずはソートボタンを作成。ボタンの素材画像は用意されていたので、button要素を作成し、ボタンの切り替えはCSSでするのが良いのだろうと漠然と考える。data属性(status)を付与し、とりあえず初期値はdefaultという値をあてました。

const createSortButton = () => {
  const sortButton = createElementWithClassName("button", "sortButton");
  sortButton.dataset.status = "default";
  sortButton.id = "js-sortbtn";
  return sortButton;
}

ボタンのクリックイベント実装

先ほど作成したボタンに対してクリックイベントを付与する。クリックがされたら下記が実行される。

①switchSortStatus()の実行(ボタンのdata属性を次のstatusへ更新する関数)
②sortFunc()の実行(要素をソートする関数)
③ソートされたtr要素をtbodyへappendChild

詳しくは下記コメントをご参考。

const sortButtonClickEvent = () => {
  const tbody = document.querySelector('tbody');
  const sortButton = document.getElementById('js-sortbtn');
  const trElement = [...document.querySelectorAll("tbody > tr")];
  sortButton.addEventListener('click', (e) => {
    //クリックされたボタンのセルインデックスを取得
    const clickedCellIndex = e.target.parentElement.cellIndex;
  //switchStatus()にて次のstatusを取得し、更新
    const nextStatus = switchSortStatus(e.target);
    sortButton.dataset.status = nextStatus;
  //ソート結果を代入
    const sortedRows = sortFunc(e.target,clickedCellIndex,trElement);
    //tbody以下を削除して
    while (tbody.firstChild) {
      tbody.removeChild(tbody.firstChild);
  }
    //ソート結果の配列からtr要素をappendChild
    sortedRows.forEach((row) => {
      tbody.appendChild(row);
    });
  });
}

レビューPOINT

クリックされたインデックスは直接[0]を指定していましたが、ここではどこの列のソートなのか確実性を持たせるために、動的に取得した方が良いとレビューいただきました。今回のようなIDのみのソートだったらインデックス箇所に間違いはないはずですが、ソート箇所が増えることも考え、その方がいいと思い修正しました。

未だに先のことを考えきれていないなと反省。

ボタンのdata属性の更新関数

ボタンがクリックされた際にdata属性を更新する。(default→asc→desc→元に戻るの順番)
先ほどのクリックイベントから現在のstatusを取得、ケースごとにswitch文で切り替える。

const Sort = {
  Default: "default",
  Asc: "asc",
  Desc: "desc"
};

const switchSortStatus = (target) => {
  switch (target.dataset.status) {
    case Sort.Default:
      return Sort.Asc;
    case Sort.Asc:
      return Sort.Desc;
    case Sort.Desc:
      return Sort.Default; 
    default:
      return Sort.Default;
  }
}

レビューPOINT

当初のPRでは、switch文のみで直接data属性の更新を実行していたのですが、固定値の指定の際は、関数の外にオブジェクトを作り指定する方が良いとアイディアをいただきました。そうすることで意図しないバグの発生を防ぐことができるのではということでした。そういう視点がいつも足りてないと気付かされます、今後に生かすぞ。

ソートの実装

今回の肝である、ソート機能です。IDの値が、昇順→降順→元の並びで切り替わる仕様です。
現在のボタンのdata属性を取得し、statusの値によって返すソート結果を変える。ここでもstatus値の指定は先ほどのオブジェクトから取得します。

今回は数値の比較なので、単純にa-b(昇順)、b-a(降順)で返す値が0より大きいか小さいかでソートできます。

const sortFunc = (target,clickedCellIndex,defaultRows) => {
 //スプレッド構文で配列の複製
  const defaultTrElement = [...defaultRows];
 //現在のdata属性のstatusの取得
  const currentStatus = target.dataset.status;
  if (currentStatus === Sort.Default) {
      return defaultRows;
  }
  if (currentStatus === Sort.Asc){
      return defaultTrElement.sort((a,b) => a.children[clickedCellIndex].textContent - b.children[clickedCellIndex].textContent);
  } 
  if (currentStatus === Sort.Desc){
      return defaultTrElement.sort((a,b) => b.children[clickedCellIndex].textContent - a.children[clickedCellIndex].textContent);
  } 
}

sort()メソッドは破壊的メソッド

ソートで気をつけるべきところは、sort()メソッドは破壊的メソッドであり、元の配列を操作してしまうという特徴があります。
なので、配列で渡ってきたdefaultRow(元のtr要素配列)をスプレッド構文で複製し、それをソートし結果をそれぞれ返すというやり方となります。

当初、渡ってきたdefaultRowをそのままソートにかけていたのですが、なぜか初期値の表示がうまくいかず、色々調べる中でその事実を知り、元の配列を操作してしまっていたから結果が変わってしまったのだとわかりました。こことても勉強になった。

【余談】比較関数のソートアルゴリズム

比較関数では2つの引数を取るのだが、引数として渡されるのは配列の値の中で、ランダムに選ばれた値だそう。ソートアルゴリズムはランダムに選んだ値を何回も繰り返し比較して少しずつソートを行なっているらしいです。(参考:独習JavaScriptより)

どんな順番で比較を行なっているんだろう、と中身がいまいちイメージできなかったので、なるほどーとなりました。これ面白い。

その他レビューポイント①

アロー関数は、1行の場合は{ }を省略することができるとレビューいただきました。早速省略。

const removeLoadImg = () => document.getElementById('js-loading').remove();

// const removeLoadImg = () => {
//   document.getElementById('js-loading').remove();
// } 

その他レビューポイント②

定数名や関数名でtypoやもっと理解しやすい命名がいいのではといくつかご指摘いただきました。言われてみれば、、となることが多く、今後はもっと細密に考えなければと思いました。typoについては単純に確認不足が露呈、お手数おかけしました。

後記

思った以上に難しかったソート機能の実装。記事を読み、動画学習をし、わからないことが言語化できなくて、本当に一歩一歩進んだ感じでしたが、JavaScriptでのTable操作やソート機能ってこういう考え方だったんだと知ることができ、大変勉強になりました。正直、ソートの全てがわかったとは言い難く、言語化するのは未だに難しいと感じます。引き続き勉強します。

c.sakyou

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA