【もりけん塾】JavaScript課題20 -Vanilla JSでテーブルを実装①-

JavaScript

もりけん塾のJavaScript課題20で、Vanilla JSを使用して、JSONデータをもとにテーブルを実装する課題に挑戦しました。

こんにちは。Webコーダーのはるです。

現在、所属している「もりけん塾」でハンズオン課題 に取り組み、レビューをいただいています。

今日は、課題20 についてアウトプットをしていきます。
(前回までのアウトプットは、こちらです。)

実装する過程で学んだことを、学習ノートとして記録していきます。

認識の間違えている箇所がありましたら、お問い合わせからご指摘いただけるとうれしいです。

Vanilla JSでテーブルを実装①

今回、取り組んだ課題はこちらです。

(もりけん先生のJavaScriptハンズオン課題より)

作成したJSONデータ

こちらにまとめています。

テーブルの構造を作る

テーブル構造を作る関数を作成しました。

まず、JSONデータからテーブルのカラム名を静的に取り出し、使用したい名前に変更するオブジェクトを作成しました。このオブジェクトを使って、テーブルのデータを埋めていきます。

const tableTitlesData = {
 "userId": "ID",
 "name": "名前",
 "gender": "性別",
 "age": "年齢"
}

以下で、<table><thead><tbody>を作成します。

<thead><tbody>の作成は、それぞれ別関数で切り出しました。

const table = createElementWithClassName("table", "table");
const titleKeys = Object.keys(tableTitlesData);
const titleNames = Object.values(tableTitlesData);
const tableHead = createTableHead(titleNames);
const tableBody = createTableBody(data, titleKeys);

※引数のdataには、JSONから取得したデータ全体が渡されます。

引数にオブジェクトのキー・値を渡したかったので、それぞれの配列を作成しました。

ここで初めて使用したのが、Object.keys(obj)とObject.values(obj)です。

指定されたオブジェクトが持つプロパティの 名前の配列を、通常のループで取得するのと同じ順序で返します。

(Object.keysのMDNより)

以下のコードで配列を変数にしました。

const titleKeys = Object.keys(tableTitlesData);
const titleNames = Object.values(tableTitlesData);

それぞれconsole.logすると、以下のようになります。

最後に、<table><thead><tbody>をrenderします。

parent.appendChild(table).appendChild(tableHead).after(tableBody);

<thead>の作成

const createTableHead = (titlesData) => {
 const tableHead = document.createElement("thead");
 const tr = createElementWithClassName("tr", "table__row");
 const fragment = document.createDocumentFragment();
 for(let i = 0; i < titlesData.length; i++){
  const th = createElementWithClassName("th", "table__title");
  th.textContent = titlesData[i];
  fragment.appendChild(th);
 }
 tableHead.appendChild(tr).appendChild(fragment);

 return tableHead;
};

Object.values(obj)で作成した値の配列を引数に渡してあります。

<thead>と<tr>を作成して、値の数だけ<th>を作ります。

ループ内で値を一つずつtextContentしてカラム名を作りました。

<tbody>の作成

const createTableBody = (data, keys) => {
 const tableBody = document.createElement("tbody");
 const fragment = document.createDocumentFragment();
 for(let i = 0; i < data.length; i++){
  const tr = createElementWithClassName("tr", "table__row");
  const td = createTableContents(data[i], keys);
  fragment.appendChild(tr).appendChild(td);
 }
 tableBody.appendChild(fragment);

 return tableBody;
};

JSONデータから取得したデータ全体と、Object.keys(obj)で作成した値の配列を引数に渡してあります。

<tbody>を作成して、データの数だけ<tr>作成 & <td>を作成する関数を呼び出しました。

<td>を作成する関数は別に切り出したので、次にまとめます。

tableのコンテンツを作成

const createTableContents = (data, keys) => {
 const fragment = document.createDocumentFragment();
 for (let i = 0; i < keys.length; i++) {
  const td = createElementWithClassName("td", "table__contents");
  td.textContent = data[keys[i]];
  fragment.appendChild(td);
 }

 return fragment;
};

引数で渡したObject.keys(obj)の数だけ<td>を作ります。

<td>の中身は、データからkeyに一致するvalueを取り出してtextContentしました。


td.textContent = data[keys[i]];

このdataには、createTableBody()関数からdata[ i ]が渡ってきます。

  • createTableBody()関数でデータの数(今回は5つ)だけ createTableContents()を呼び出す。
  • createTableContents()が呼び出されたら、Object.keys(obj)の数だけ<td>が作られます。

この構造を考えるのにとても時間がかかりました・・・

学び

JSONから取得したデータを、どこで引数に渡すのか?

最初は以下のように、renderTable()内でデータ取得の関数を実行していました。

const renderTable = async() => {
 const data = await getUserData(); //here!

 const tableTitlesData = {
  "userId": "ID",
  "name": "名前",
  "gender": "性別",
  "age": "年齢"
 }

 const table = createElementWithClassName("table", "table");
 const titleKeys = Object.keys(tableTitlesData);
 const titleNames = Object.values(tableTitlesData);
 const tableHead = createTableHead(titleNames);
 const tableBody = createTableBody(data, titleKeys); //ここでdataを渡したいため!

 parent.appendChild(table).appendChild(tableHead).after(tableBody);
};

renderTable内でgetUserData()を実行するのは不自然で、データは引数として渡すべきだと学びました。

そこでgetUserData()内でdataを渡す方法を考えました (NG版)

const getUserData = async() => {
 addLoading();
 try {
  const json = await fetchUserData();
  if (!json) return;

  const data = json.data;

  renderTable(data); //here!

  if (data.length === 0) {
   parent.appendChild(createErrorMessage("まだデータがありません"));
   console.log("まだデータがありません");
   return;
  }
  return data;
 } catch(e) {
  console.error(e);
  parent.appendChild(createErrorMessage(e));
 } finally {
 removeLoading();
 } 
};

これだと、そもそもtryの最後でreturnしている意味がなかったのと(凡ミス)、tryの中で他関数を実行してしまうとエラーの起源がわからなくなってしまうことを学びました。

(promiseのエラーなのか、 renderTable()に関するエラーなのか)

最終的に以下のようにinit()関数を新たに作成して、データを引数に渡しました。

const getUserData = async() => {
 addLoading();
 try {
  const json = await fetchUserData();
  if (!json) return;

  const data = json.data;

  if (data.length === 0) {
   parent.appendChild(createErrorMessage("まだデータがありません"));
   console.log("まだデータがありません");
   return;
  }
 return data;

 } catch(e) {
  console.error(e);
  parent.appendChild(createErrorMessage(e));
 } finally {
  removeLoading();
 }
};

//追加
const init = async() => {
 const data = await getUserData();
 data && renderTable(data);
};

今回のコード

//
学習に使用している本は、もりけん先生推奨の”JavaScript本格入門”です。

あとがき

ちひろさん(@chihiro7029)、まいさん(mai2022web)、senさん(websen5)、にゃっつさん(nyattsu72)レビューいただきありがとうございました!

こんなにたくさんの方にレビューをいただいたのが初めてで嬉しかった・・・

tryで他の関数を実行すると、発生したエラーがどの起源かがわからなくなると教えていただき、特にtry,catchの理解が進んだ!

お時間を割いていただきありがとうございました!

今日は以上です。

//

【もりけん塾で勉強しています】

もりけん先生(@terrace_tec)のHPはこちら