【もりけん塾】JavaScript課題16 -Vanilla JSでタブコンテンツを作る②-

JavaScript

もりけん塾のJavaScript課題16 で、Vanilla JSを使用したタブコンテンツ作りにチャレンジしています。今回は第二弾として、追加の仕様をまとめます。

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

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

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

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

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

【もりけん塾】JavaScript課題16 -Vanilla JSでタブコンテンツを作る②

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

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

前回は最低限のタブ機能を実装しました。

【今回は、追加の仕様を実装しました】

  • それぞれのカテゴリにはそれぞれ固有の画像が入る(右側四角。画像は適当)
  • 記事にはnewという新着かどうかのラベルがつく(どこの記事にそれが入るかは適当でいいです)
  • 記事にはそれぞれコメントがあり、0件なら表示しない、1以上ならアイコンと共に数字が表示される
  • どのカテゴリタブを初期表示時に選んでいるかはデータとして持っている
  • try-catchでエラー時はulの中に「ただいまサーバー側で通信がぶっ壊れています」みたいなテキストを画面内に表示すること

カテゴリタブを初期表示するデータをJSONデータから取得

タブと、タブコンテンツの初期表示をするデータを取得して、それを反映させる実装を追加しました。

タブ、タブコンテンツを表示させる関数に追加したコード

//JSONデータでdisplayがtrueのカテゴリの場合はis-activeを付与
values[i].display && button.classList.add("is-active");

//JSONデータでdisplayがtrueのカテゴリの場合はis-activeを付与
data[i].display && tabContents.classList.add("is-active"); //追加

&&を使用したコードは、さえさんに教えていただきました!ありがとうございます。

&&演算子は、短絡評価が行われます。

MDNによると、

(falseの式) && expr

この場合は、後の’expr‘の式は実行されません。

エラー時はtry-catchでulにエラーテキストを表示

try-catch節で、エラー時の対応を追加しました。

async function fetchData(api) {
 try{
  const response = await fetch(api);
  const json = await response.json();
  return json.data;
 } catch(e) {
  throw new Error("サーバーエラーです");
 }
}

async function fetchArrayData() {
 try {
  const data = await fetchData("https://myjson.dit.upm.es/api/bins/amqx");
  if (data.length === 0) {
   tabNav.textContent = "データが空です";
   console.log("データが空です");
  }
  return data;
 } catch(e) {
  createErrorMessage(e);
 }
}

これまでの課題で勉強した部分です。

先生からアドバイスいただき、引数にapiを入れて

async function fetchData(api)

fetchArrayData()関数内で、APIのURLを入れる実装に修正しました。

async function fetchArrayData() { 
 try {
   const data = await fetchData("https://myjson.dit.upm.es/api/bins/amqx");

それぞれのカテゴリに固有の画像を入れる

JSONデータにそれぞれのカテゴリの画像ファイル名があるので、それを取得しました。

私の作ったJSONデータ↓

ここから画像のデータを取得して、srcに追加しました。

img.src = `${data.img}`;

画像のfragmentを作成する関数を作りました。

function createImgFragments(data) {
 const fragment = document.createDocumentFragment();
 const imgWrapper = document.createElement("div");
 const img = document.createElement("img");

 imgWrapper.classList.add("tab__img-wrapper");
 img.classList.add("tab__img");
 img.src = `${data.img}`;

 fragment.appendChild(imgWrapper).appendChild(img);

 return fragment;
}

実際にこの関数の実行は、タブコンテンツを作成している関数内に追記します。

function createArticleContents(data) {
 const values = data.map(value => value.articles);
 const tabContainer = document.getElementById("js-tab");

 //記事データの数だけulを作成
 for (let i = 0; i < values.length; i++) {
  const tabContents = document.createElement("div");
  const tabContentsInner = document.createElement("div");
  const ul = document.createElement("ul");

  tabContents.classList.add("tab__contents","js-tabContents");
  tabContentsInner.classList.add("tab__contents-inner");
  ul.classList.add("tab__contents-list");

  //JSONデータでdisplayがtrueのカテゴリの場合はis-activeを付与
  data[i].display && tabContents.classList.add("is-active");

  const articleTitlesFragment = appendArticlesTitleFragment(values[i]);
  const contentsImgFragment = createImgFragments(data[i]); //追加

  tabContainer.appendChild(tabContents).appendChild(tabContentsInner).appendChild(ul).appendChild(articleTitlesFragment);
  tabContentsInner.appendChild(contentsImgFragment); //追加
 }
}

追加したコード

const contentsImgFragment = createImgFragments(data[i]); //追加
tabContentsInner.appendChild(contentsImgFragment); //追加

createArticleContents(data)関数に渡ってきているdataは、JSONから取得した配列です。

data[i]として、カテゴリーごとの情報を入れて
それぞれの画像fragmentを作成してDOMに追加しました。

記事にコメントがあれば、アイコンと数字を表示

コメントの情報を、map()メソッドで新しい配列にして、numberOfComments > 0 の時にcreateCommentInfo関数が呼び出されるようにしました。

const articleComments = values.map(value => value.comments);

for (let i = 0; i < articleTitles.length; i++) {

 const numberOfComments = articleComments[i].length;

 //コメントがあれば件数とアイコンを表示
 if (numberOfComments > 0) {
  const commentInfo = createCommentInfo(articleComments[i]);
  li.appendChild(commentInfo);
  }
 }
}
createCommentInfo(values)関数↓
function createCommentInfo(values) {
 const fragment = document.createDocumentFragment();
 const commentIconWrapper = document.createElement("div");
 const commentIcon = document.createElement("img");
 const commentLength = document.createElement("div");

 commentIcon.src = "./img/icon-comment.svg";
 commentIconWrapper.classList.add("tab__contents-icon");
 commentLength.classList.add("tab__contents-info");
 commentLength.textContent = `${values.length}件`;
 commentIconWrapper.appendChild(commentIcon);

 fragment.appendChild(commentIconWrapper).insertAdjacentElement("afterend", commentLength)
 return fragment;
}

記事にはnewという新着かどうかのラベルがつく

まず、新着を3日以内と定義して、記事が3日以内に投稿されたかをチェックする関数を作成しました。

function isNewArrival(date) {
 const today = format(new Date(), "yyyy,MM,dd");
 const articleDate = format(new Date(date), "yyyy,MM,dd");
 const periodFromSubmission = differenceInCalendarDays(new Date(today), new Date(articleDate));
 const specificPeriod = 3;
 const newArrival = periodFromSubmission <= specificPeriod;
 return newArrival;
}

ここで使用したのがdate-fnsというプラグインです。

date-fnsを使うと、JavaScriptの日付 を簡単に操作することができます。

プラグインを入れて、先頭で各メソッドを読み込むだけで使用できます。

import { format, differenceInCalendarDays } from "date-fns";

今回は

  • format:日付を指定したフォーマットに変換して返す
  • differenceInCalendarDays: 指定された日付間の日数を取得する

の2つを使用しました。

const today = format(new Date(), "yyyy,MM,dd");
const articleDate = format(new Date(date), "yyyy,MM,dd");
const periodFromSubmission = differenceInCalendarDays(new Date(today), new Date(articleDate));

それぞれのメソッドの使い方は、

date-fnsのドキュメントに詳細がまとまっています。

//

先生にレビューいただき、関数名をcheckDayNewArrival()からisNewArrival()に修正しました。

これはreturn newArrival;で返ってくるのはtrue/false(真偽値)のためです。

また、3日以内かを判別する式を以下のように書いていましたが、

const newArrival = periodFromSubmission <= 3;

“3”という値の意味がわかるように名前をつけた方が良いとご指摘いただき、修正しました。

const specificPeriod = 3;
const newArrival = periodFromSubmission <= specificPeriod;

この関数で返ってきたtrue/falseを元に、新着ラベルを表示させます。

const articleDate = values.map(value => value.date);

for (let i = 0; i < articleTitles.length; i++) {
 //3日以内の投稿であればnewアイコンを表示
 isNewArrival(articleDate[i]) && li.insertAdjacentElement("beforeend", createNewIcon());
}
新着ラベルの作成は別の関数にしました。
function createNewIcon() {
 const div = document.createElement("div");
 const img = document.createElement("img");
 div.classList.add("tab__contents-new");
 img.src = "./img/icon-new.svg";

 div.appendChild(img);
 return div;
}

今回のコード

jsonデータ作成に使用していたmyjsonがエラーのため、データを冒頭でベタ書きして仮対応しています。

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

あとがき

今回レビューをしてくださったもりけん先生(@terrace_tec)、さえさん(sae_prog)ありがとうございました!

周りくどい書き方をしてしまうことが多いので、誰がみてもわかりやすい簡潔なコードを書けるようになりたいです。まずは、最初に日本語で詳細にプログラムを組み立てて、そこからコードを書いていくように心掛けます。

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

今日は以上です。

//

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

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