もりけん塾 JavaScript課題16 タブ切り替えの実装

もりけん塾 JavaScript課題16 タブ切り替えの実装

今回は、jsonデータを作って、タブ切り替えを実装する課題です。jsonデータの構成に少し悩みましたが、先に取り組んだ方々のコードやコメントを参考に、進めました。

お題

これはyahooのトップページを模したUIですが、これをデータとしてどのようなものがサーバーがから返ってくれば実現できそうか、データ構造から考えて、静的なデータ(前回までのようなresolveされると返されるベタ書きのデータ)を作って、画面表示させてみてください。

仕様は

・それぞれのカテゴリタブを開くことができてそれぞれのジャンルに応じた記事が4つ表示できる。(記事のタイトル名は適当)
・それぞれのカテゴリにはそれぞれ固有の画像が入る(右側四角。画像は適当)
・記事が3日以内のものであれば、newという新着かどうかのラベルがつく(どこの記事かは適当でいいです)
・記事にはそれぞれコメントがあり、0件なら表示しない、1以上ならアイコンと共に数字が表示される
・カテゴリタブは切り替えられる。面倒なら2つのカテゴリだけでよいです。その場合ニュースと経済だけにします
・どのカテゴリタブを初期表示時に選んでいるかはデータとして持っている
・htmlはulだけ作ってあとはcreateElementで作る
・try-catchでエラー時はulの中に「ただいまサーバー側で通信がぶっ壊れています」みたいなテキストを画面内に表示すること
・CSSはなしで良い。上記機能要件だけ満たしていればいい

です

jsonデータを作る

どんなデータを作れば、タブ切り替えに必要な情報が揃うか考えるために、いつも使っているWordpressの記事のデータを取得してみました。

WordPressの場合はREST APIが使えるので、簡単にデータを取得可能です。このブログの場合はhttps://mai.kosodante.com/wp-json/wp/v2/postsと入れると、jsonデータが取得できます。

少しずつデータのイメージを作ります。記事データには、id,date,title,cagtegoriesなどのデータがありました。ここにcommentなどをプラスしていけば良さそうです。

下記のようなjsonデータを作りました。

[{
      "id": "624ea99d26476db2ff6fc08c",
      "isActive": false,
      "picture": "img/cat-news.png",
      "categories": "News",
      "articles": [
        {
          "id": "624ea99d0d87ab7e03d3ca38",
          "date": "2022-04-20 5:24:58",
          "title": "News Article01",
          "comment": [
          ]
        },
        {
          "id": "624ea99d9ff85a94542f3fe2",
          "date": "2022-04-17 7:12:08",
          "title": "News Article02",
          "comment": [
            {
              "id": "624ea99dd263e4df6c40de3b",
              "name": "Wendy",
              "date": "2022-04-11 10:41:01",
              "text": "Excelent!"
            },
            {
              "id": "624ea99dd263e4df6c40de3c",
              "name": "Joy",
              "date": "2022-04-12 10:41:01",
              "text": "Excelent!"
            },
            {
              "id": "624ea99dd263e4df6c40de3g",
              "name": "Wendy",
              "date": "2022-04-13 10:41:01",
              "text": "Excelent!"
            },
            {
              "id": "624ea99dd263e4df6c40de3s",
              "name": "Wendy",
              "date": "2022-04-14 10:41:01",
              "text": "Excelent!"
            }
          ]
        },
        {
          "id": "624ea99dac3c56428fde5279",
          "date": "2022-04-20 1:00:26",
          "title": "News Article03",
          "comment": [
            {
              "id": "624ea99dca6c379647288b24",
              "name": "Tom",
              "date": "2022-04-10 12:04:47",
              "text": "Excelent!"
            },
            {
              "id": "624ea99d3f73d331f5e0bd5e",
              "name": "Kei",
              "date": "2022-04-11 01:23:00",
              "text": "Nice!"
            }
          ]
        },
        {
          "id": "624ea99d791df75257b0b012",
          "date": "2022-04-07 9:06:45",
          "title": "News Article04",
          "comment": [
            {
              "id": "624ea99d7dc825911b5cfd81",
              "name": "Kei",
              "date": "2022-04-08 1:16:51",
              "text": "WaaaaO!"
            }
          ]
        }
      ]
    },
    {
      "id": "624ea99dbfeab35e9c3e8281",
      "isActive": true,
      "picture": "img/cat-economic.png",
      "categories": "Economic",
      "articles": [
        {
          "id": "624ea99db415a2dd23763da0",
          "date": "2022-04-20 05:19:41",
          "title": "Economic Article01",
          "comment": [
            {
              "id": "624ea99d261e4618d752d5c5",
              "name": "shelly",
              "date": "2022-04-20 5:40:51",
              "text": "WaaaaO!"
            }
          ]
        },
        {
          "id": "624ea99d3368cc2942430010",
          "date": "2022-04-13 11:27:29",
          "title": "Economic Article02",
          "comment": [
            {
              "id": "624ea99ddc8a0c22ab61e1c7",
              "name": "Tom",
              "date": "2022-04-13 11:35:49",
              "text": "Hello"
            },
            {
              "id": "624ea99ddc8a0c22ab61e1c7",
              "name": "Tommy",
              "date": "2022-04-14 11:35:49",
              "text": "Hello"
            }
          ]
        },
        {
          "id": "624ea99d58558c732ca7bf7b",
          "date": "2022-04-20 10:33:59",
          "title": "Economic Article03",
          "comment": [
          ]
        },
        {
          "id": "624ea99d0954973f06913129",
          "date": "2022-04-17 10:37:19",
          "title": "Economic Article04",
          "comment": [
          ]
        }
      ]
    },
    {
      "id": "624ea99d861f4da2da2ba788",
      "isActive": false,
      "picture": "img/cat-sports.png",
      "categories": "Sports",
      "articles": [
        {
          "id": "624ea99d63923d841eab2333",
          "date": "2022-04-20 12:02:11",
          "title": "Sports Article01",
          "comment": [
            {
              "id": "624ea99dcbd0e8e92c4676d1",
              "name": "Kei",
              "date": "2022-04-14 09:06:31",
              "text": "Nice!"
            },
            {
              "id": "624ea99dcbd0e8e92c4676a1",
              "name": "Kei",
              "date": "2022-04-08 09:06:31",
              "text": "Nice!"
            },
            {
              "id": "624ea99dcbd0e8e92c4676c1",
              "name": "Kei",
              "date": "2022-04-14 09:06:31",
              "text": "Nice!"
            }
          ]
        },
        {
          "id": "624ea99dcfd2989d70472ae5",
          "date": "2022-04-14 04:42:48 -09:00",
          "title": "Sports Article02",
          "comment": [
            {
              "id": "624ea99da9c4813a53949557",
              "name": "Mark",
              "date": "2022-04-06 09:03:20",
              "text": "Nice!"
            },
            {
              "id": "624ea99dcbd0e8e92c4676d5",
              "name": "Kei",
              "date": "2022-04-08 09:06:31",
              "text": "Nice!"
            },
            {
              "id": "624ea99dcbd0e8e92c4676d4",
              "name": "Kei",
              "date": "2022-04-14 09:06:31",
              "text": "Nice!"
            },
            {
              "id": "624ea99dcbd0e8e92c4676d2",
              "name": "Kei",
              "date": "2022-04-08 09:06:31",
              "text": "Nice!"
            }
          ]
        },
        {
          "id": "624ea99d042401a2873a600b",
          "date": "2022-04-10 12:00:40",
          "title": "Sports Article03",
          "comment": [
          ]
        },
        {
          "id": "624ea99d379c38b4e70772bd",
          "date": "2022-04-06 09:55:32",
          "title": "Sports Article04",
          "comment": [
            {
              "id": "624ea99df14a8c6e36e44154",
              "name": "Kei",
              "date": "2022-04-07 01:19:50",
              "text": "Amazing!"
            },
            {
              "id": "624ea99df14a8c6e36e44154",
              "name": "Kei",
              "date": "2022-04-07 01:18:50",
              "text": "Amazing!"
            }
          ]
        }
      ]
    },
    {
      "id": "624ea99ece110530de4cf44a",
      "isActive": false,
      "picture": "img/cat-entertaiment.png",
      "categories": "Domestic",
      "articles": [
        {
          "id": "624ea99ea02b8b0e33b576cc",
          "date": "2022-04-17 04:42:17",
          "title": "Domestic Ariticle01",
          "comment": [
            {
              "id": "624ea99ea8f7ad5f65993428",
              "name": "Mark",
              "date": "2022-04-13 10:26:40",
              "text": "Nice!"
            },
            {
              "id": "624ea99ea8f7ad5f65993428",
              "name": "Mark",
              "date": "2022-04-13 10:26:40",
              "text": "Nice!"
            },
            {
              "id": "624ea99ea8f7ad5f65993428",
              "name": "Mark",
              "date": "2022-04-13 10:26:40",
              "text": "Nice!"
            }
          ]
        },
        {
          "id": "624ea99e791722b3c21cef93",
          "date": "2022-04-20 07:20:57",
          "title": "Domestic Ariticle02",
          "comment": [
            {
              "id": "624ea99e228306aa1ca88d53",
              "name": "Wendy",
              "date": "2022-04-05 12:30:07",
              "text": "Hello"
            }
          ]
        },
        {
          "id": "624ea99e6d888126993eaeb6",
          "date": "2022-04-07 03:26:28",
          "title": "Domestic Ariticle03",
          "comment": [
          ]
        },
        {
          "id": "624ea99ec8003419a090a66c",
          "date": "2022-04-14 01:31:49",
          "title": "Domestic Ariticle04",
          "comment": [
            {
              "id": "624ea99edcc140ce9b596e5a",
              "name": "shelly",
              "date": "2022-04-14 05:28:42",
              "text": "WaaaaO!"
            },
            {
              "id": "624ea99edcc140ce9b596e5a",
              "name": "May",
              "date": "2022-04-14 05:28:42",
              "text": "WaaaaO!"
            }
          ]
        }
      ]
    }
 ]
  

タブ切り替え実装

タブ切り替えの実装は、クリックしたらis-activeクラスをaddして、他のタブをクリックしたら現在のタブからis-activeをremoveする、というJQueryでもよくある方法を考えました。

タブ切り替えは、こんな感じの動きになりました。

最初のコード

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <link rel="stylesheet" href="reset.css" type="text/css" />
    <link rel="stylesheet" href="style.css" type="text/css" />
    <script src="script.js" defer></script>
    <title>JavaScript課題16</title>
  </head>
  <body>
    <ul id="js-topics-list" class="topics-list"></ul>
  </body>
</html>

const showLoading = () => {
    const loadingArea     = document.createElement('div');
    const loadingImg      = document.createElement('img');
    loadingArea.id        = "js-loading";
    loadingArea.className = "loading-area";
    loadingImg.src        = "img/loading-circle.gif";
    loadingImg.alt        = "ローディング画像";

    document.body.insertAdjacentElement('afterbegin',loadingArea).appendChild(loadingImg);
}

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

const createErrorMessage = (error) => {
    const errMsg       = document.createElement('li');
    errMsg.className   = "error-msg";
    errMsg.textContent = error;
    document.getElementById('js-topics-list').appendChild(errMsg);
    return errMsg;
}

const fetchData = async () => {
    const requestURL = "https://api.json-generator.com/templates/kuSXIyw7OJla/data?access_token=hu4bc7qh9znx2m8f53mn4mz2hryvdntkavwbw8j0";
    try{
        const response   = await fetch(requestURL);
        if(!response.ok){
            console.error(`${response.status}:${response.statusText}`);
            createErrorMessage('現在、サーバーの通信が壊れています');
        }
        const json = await response.json();
        return json;
    }catch(error){
        console.error(error);
    }
}

const init = async () => {
    showLoading();
    try{
        const result = await fetchData();
        if(!result){
            return;
        }
        showContentsArea();
        renderTopics(result);
        renderArticles(result);
        renderImages(result);
        switchTabTopics(result);
    }catch(error){
        console.error(error);
    }finally{
        removeLoading();
    }
}
const showContentsArea = () =>{
    const contentsArea      = document.createElement('section');
    contentsArea.id         = "js-contents-area";
    contentsArea.className  = "contents-area";

    document.getElementById('js-topics-list')
    .insertAdjacentElement('afterend',contentsArea);
}

const renderTopics = (result) => {
    const topicsArea     = document.getElementById('js-topics-list');
    const fragmentTopics = document.createDocumentFragment();
    for (const topics of result){
        const category     = document.createElement('li');
        const categroyLink = document.createElement('a');
        category.classList.add('topics-list__item');
        categroyLink.textContent = topics.categories;
        fragmentTopics.appendChild(category).appendChild(categroyLink);
    }
    topicsArea.appendChild(fragmentTopics);
}

const renderImages = (result) => {
    const contentsArea  = document.getElementById('js-contents-area');
    const imageArea     = document.createElement('div');
    imageArea.className = "contents-item topics-img";

    const fragmentTopicImg = document.createDocumentFragment();
    for (const topics of result){
        const topicsImg = document.createElement('img');
        topicsImg.id    = `${topics.categories + '-img' }`;
        topicsImg.src   = topics.picture;
        topicsImg.classList.add('topics-img__item');
        fragmentTopicImg.appendChild(topicsImg);
    }
    contentsArea.appendChild(imageArea).appendChild(fragmentTopicImg);
}

const showNewIcon = () => {
    const newIcon       = document.createElement('span');
    newIcon.className   = "new-icon";
    newIcon.textContent = "New";
    return newIcon;
}

const showCommentIcon = () => {
    const commentIcon     = document.createElement('img');
    commentIcon.src       = "img/comment-dots-solid.svg";
    commentIcon.className = "comment-icon";
    return commentIcon;
}

const showCommentNumber = (commentLength) => {
    const commentNumber       = document.createElement('span');
    commentNumber.textContent = commentLength;
    commentNumber.className   = "comment-number";
    return commentNumber;
}

const renderArticles = (result) => {
    const contentsArea    = document.getElementById('js-contents-area');
    const articleArea     = document.createElement('div');
    articleArea.className = "contents-item articles";

    const fragmentAritcle = document.createDocumentFragment(); 
    for (const topics of result){
        const articleList = document.createElement('ul');
        articleList.id    = `${topics.categories + "-articles"}`;
        articleList.classList.add('article-list');

        const articles = topics.articles;
        for( article of articles ){
            const articleItem   = document.createElement('li');
            const articleLink   = document.createElement('a'); 
            articleItem.classList.add('article-list__item');
            articleLink.textContent  = article.title;

            const commentLength = article.comment.length; 
            const articleDateMs = new Date(article.date).getTime();
            const periodDayMs   = new Date().getTime() - (7*24*60*60*1000);


            if(periodDayMs < articleDateMs){
                articleLink.appendChild(showNewIcon());
            }

            if(commentLength > 0){
                articleLink.appendChild(showCommentIcon());
                articleLink.appendChild(showCommentNumber(commentLength));
            }

            fragmentAritcle.appendChild(articleList).appendChild(articleItem).appendChild(articleLink);
        };
    }
    contentsArea.appendChild(articleArea).appendChild(fragmentAritcle);    
}

const switchTabTopics = (result) => {
    const tabTopics   = document.getElementsByClassName('topics-list__item');
    const topicsImg   = document.getElementsByClassName('topics-img__item');
    const articleList = document.getElementsByClassName('article-list');

    /*初期表示*/
    const index = result.findIndex(value => value.isActive == true);
    if(index){
        tabTopics[index].classList.add('is-active');
        topicsImg[index].classList.add('is-active');
        articleList[index].classList.add('is-active');
    }

    for (topic of tabTopics ){
        topic.addEventListener('click',function (){
        const tabTopicsIsShow = document.getElementsByClassName('topics-list__item is-active');
        const topicsImgIsShow = document.getElementsByClassName('topics-img__item is-active');
        const articleIsShow   = document.getElementsByClassName('article-list is-active');

        const arrayTabs = Array.prototype.slice.call(tabTopics);
        const index = arrayTabs.indexOf(this);

        tabTopicsIsShow[0].classList.remove('is-active');
        this.classList.add('is-active');

        topicsImgIsShow[0].classList.remove('is-active');
        topicsImg[index].classList.add('is-active');

        articleIsShow[0].classList.remove('is-active');
        articleList[index].classList.add('is-active');
        });
    }
}

init();

レビュー

ちひろさん、やすさんにレビューして頂きました!ありがとうございます。

最初に、resultという変数名、引数名を、jsonの中身を表すような’newsData’ か ‘articleData’などにするのはどうか、とアドバイスを頂きました。データの中身を、わかりやすくするのが大事だと学びました!

また、この部分は、特にsliceする必要がないので、

const arrayTabs = Array.prototype.slice.call(tabTopics);

下記のように書いても同じ動きをすることを教えて頂きました。

const arrayTabs = Array.from(tabTopics);

SwitchTabTopics関数の中で、最初のタブをアクティブにする処理を書いていたので、これは切り出した方がいいとアドバイスをもらいました。

    /*初期表示*/
    const index = result.findIndex(value => value.isActive == true);
    if(index){
        tabTopics[index].classList.add('is-active');
        topicsImg[index].classList.add('is-active');
        articleList[index].classList.add('is-active');
    }

分割代入を使う

記事を表示する部分の関数は、ネストが深くなっているため、分割代入をして、切り出すのはどうかとアドバイスをもらいました。分割代入とは何かわかっていなかったので調べました。

分割代入 (Destructuring assignment) 構文は、配列から値を取り出して、あるいはオブジェクトからプロパティを取り出して別個の変数に代入することを可能にする JavaScript の式です。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

わかったようなわからないような。。ともかく、下記の関数を分割代入を使って、切り出していくことにしました。

const renderArticles = (newsDate) => {
    const contentsArea    = document.getElementById('js-contents-area');
    const articleArea     = document.createElement('div');
    articleArea.className = "contents-item articles";

    const fragmentAritcle = document.createDocumentFragment(); 
    for (const topics of newsDate){
        const articleList = document.createElement('ul');
        articleList.id    = `${topics.categories + "-articles"}`;
        articleList.classList.add('article-list');

        const articles = topics.articles;
        for(const article of articles ){
            const articleItem   = document.createElement('li');
            const articleLink   = document.createElement('a'); 
            articleItem.classList.add('article-list__item');
            articleLink.textContent  = article.title;

            const commentLength = article.comment.length; 
            const articleDateMs = new Date(article.date).getTime();
            const periodDayMs   = new Date().getTime() - (3*24*60*60*1000);


            if(periodDayMs < articleDateMs){
                articleLink.appendChild(showNewIcon());
            }

            if(commentLength > 0){
                articleLink.appendChild(showCommentIcon());
                articleLink.appendChild(showCommentNumber(commentLength));
            }

            fragmentAritcle.appendChild(articleList).appendChild(articleItem).appendChild(articleLink);
        };
    }
    contentsArea.appendChild(articleArea).appendChild(fragmentAritcle);    
}

とてもわかりやすくなったと思います!

const renderArticlesSection = (newsDate) => {
    const contentsArea    = document.getElementById('js-contents-area');
    const articleArea     = document.createElement('div');
    articleArea.className = "contents-item articles";
    const fragmentAritcle = document.createDocumentFragment(); 
    for (const topics of newsDate){
        const articleList = document.createElement('ul');
        articleList.id    = `${topics.categories + "-articles"}`;
        articleList.classList.add('article-list');

    fragmentAritcle.appendChild(articleList).appendChild(renderArticlesItem(topics));
    };
    contentsArea.appendChild(articleArea).appendChild(fragmentAritcle);    
}

const renderArticlesItem = ({articles}) => {
    const articleTitleFlagment = document.createDocumentFragment(); 
    for(const article of articles ){
        const articleItem   = document.createElement('li');
        const articleLink   = document.createElement('a'); 
        articleItem.classList.add('article-list__item');
        articleLink.textContent  = article.title;

        const articleDate   = article.date;
        const commentLength = article.comment.length; 
        showSpecificPeriodNewIconToArticles(articleDate,articleLink);
        showCommentIconWithNumber(commentLength,articleLink);

        articleTitleFlagment.appendChild(articleItem).appendChild(articleLink);
    };
    return articleTitleFlagment;
}

date-fnsを使う

3日以内の記事にニューアイコンをつけるために、日付を比較する部分は、(3*24*60*60*1000)の部分が何を表すものなのか、明確にした方がいいので、変数に代入するのはどうかとアドバイスを頂きました。

また、日付の比較は、date-fnsというJavaScriptのライブラリを使いましょう、とご指摘をいただいたので、挑戦することにしました。

     const commentLength = article.comment.length; 
     const articleDateMs = new Date(article.date).getTime();
     const periodDayMs   = new Date().getTime() - (3*24*60*60*1000);
     if(periodDayMs < articleDateMs){
       articleLink.appendChild(showNewIcon());
     }

date-fnsは、CDNで読み込む方法とライブラリをnpmでインストールして読み込む方法などがありますが、CDNで読み込む方法は公式では推奨していないそうです。CDNはCodeSandBoxでエラーになるため、質問したところ、にゃっつさんに教えて頂きました!ありがとうございます。

最初は、CDNで読み込みしていましたが、それはやめて、npmでインストールして使う方法にしました。

npmでダウンロードする方法を使う場合、ローカル環境で確認することができなかったので、ローカルの開発環境を作るためにViteをインストールすることにしました。

Viteは、コマンドひとつでインストールができ、すぐに使用することができたので驚きました。date-fnsを使用したコードは下記になりました。

const showSpecificPeriodNewIconToArticles = (articleDate,articleLink) => {
    const newIcon       = document.createElement('span');
    newIcon.className   = "new-icon";
    newIcon.textContent = "New";

    const SpecificPeriod = 3;
    const dateDifferencial  = dateFns.differenceInDays(new Date(), new Date(articleDate));
    const dateDifferencial  = differenceInDays(new Date(), new Date(articleDate));

    if(SpecificPeriod > dateDifferencial){
        articleLink.appendChild(newIcon);
    }
    return dateDifferencial;
}

最終的なコード

細かく切り分けていたアイコンの関数は、関数名を変更して、ひとまとめにしました。

import { differenceInDays } from "/node_modules/date-fns"

const showLoading = () => {
    const loadingArea     = document.createElement('div');
    const loadingImg      = document.createElement('img');
    loadingArea.id        = "js-loading";
    loadingArea.className = "loading-area";
    loadingImg.src        = "img/loading-circle.gif";
    loadingImg.alt        = "ローディング画像";

    document.body.insertAdjacentElement('afterbegin',loadingArea).appendChild(loadingImg);
}

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

const createErrorMessage = (error) => {
    const errMsg       = document.createElement('li');
    errMsg.className   = "error-msg";
    errMsg.textContent = error;
    document.getElementById('js-topics-list').appendChild(errMsg);
    return errMsg;
}

const fetchData = async () => {
    const requestURL = "https://api.json-generator.com/templates/kuSXIyw7OJla/data?access_token=hu4bc7qh9znx2m8f53mn4mz2hryvdntkavwbw8j0";
    try{
        const response   = await fetch(requestURL);
        if(!response.ok){
            console.error(`${response.status}:${response.statusText}`);
            createErrorMessage('現在、サーバーの通信が壊れています');
        }
        const json = await response.json();
        return json;
    }catch(error){
        console.error(error);
    }
}

const init = async () => {
    showLoading();
    try{
        const newsData = await fetchData();
        if(!newsData){
            return;
        }
        showContentsArea();
        renderTopics(newsData);
        renderArticlesSection(newsData);
        renderImages(newsData);

        activateFirstTopic(newsData);
        switchTabTopics();
    }catch(error){
        console.error(error);
    }finally{
        removeLoading();
    }
}

const showContentsArea = () =>{
    const contentsArea      = document.createElement('section');
    contentsArea.id         = "js-contents-area";
    contentsArea.className  = "contents-area";

    document.getElementById('js-topics-list')
    .insertAdjacentElement('afterend',contentsArea);
}

const renderTopics = (newsData) => {
    const topicsArea     = document.getElementById('js-topics-list');
    const fragmentTopics = document.createDocumentFragment();
    for (const topics of newsData){
        const category     = document.createElement('li');
        const categroyLink = document.createElement('a');
        category.classList.add('topics-list__item');
        categroyLink.textContent = topics.categories;
        fragmentTopics.appendChild(category).appendChild(categroyLink);
    }
    topicsArea.appendChild(fragmentTopics);
}

const renderImages = (newsData) => {
    const contentsArea  = document.getElementById('js-contents-area');
    const imageArea     = document.createElement('div');
    imageArea.className = "contents-item topics-img";

    const fragmentTopicImg = document.createDocumentFragment();
    for (const topics of newsData){
        const topicsImg = document.createElement('img');
        topicsImg.id    = `${topics.categories + '-img' }`;
        topicsImg.src   = topics.picture;
        topicsImg.classList.add('topics-img__item');
        fragmentTopicImg.appendChild(topicsImg);
    }
    contentsArea.appendChild(imageArea).appendChild(fragmentTopicImg);
}

const showSpecificPeriodNewIconToArticles = (articleDate,articleLink) => {
    const newIcon       = document.createElement('span');
    newIcon.className   = "new-icon";
    newIcon.textContent = "New";

    const SpecificPeriod = 3;
    const dateDifferencial  = differenceInDays(new Date(), new Date(articleDate));

    if(SpecificPeriod > dateDifferencial){
        articleLink.appendChild(newIcon);
    }
    return dateDifferencial;
}

const showCommentIconWithNumber = (commentLength,articleLink) => {
    const commentIcon     = document.createElement('img');
    commentIcon.src       = "img/comment-dots-solid.svg";
    commentIcon.className = "comment-icon";

    const commentNumber       = document.createElement('span');
    commentNumber.textContent = commentLength;
    commentNumber.className   = "comment-number";

    if(commentLength > 0 ){
        articleLink.appendChild(commentIcon);
        articleLink.appendChild(commentNumber);
    }
    return commentNumber;
}

const renderArticlesSection = (newsData) => {
    const contentsArea    = document.getElementById('js-contents-area');
    const articleArea     = document.createElement('div');
    articleArea.className = "contents-item articles";

    const fragmentAritcle = document.createDocumentFragment(); 
    for (const topics of newsData){
        const articleList = document.createElement('ul');
        articleList.id    = `${topics.categories + "-articles"}`;
        articleList.classList.add('article-list');

        fragmentAritcle.appendChild(articleList).appendChild(createArticleTitleFlagment(topics));
    };
    contentsArea.appendChild(articleArea).appendChild(fragmentAritcle);    
}

const createArticleTitleFlagment = ({articles}) => {
    const articleTitleFlagment = document.createDocumentFragment(); 
    for(const article of articles ){
        const articleItem   = document.createElement('li');
        const articleLink   = document.createElement('a'); 
        articleItem.classList.add('article-list__item');
        articleLink.textContent  = article.title;

        const articleDate   = article.date;
        const commentLength = article.comment.length; 
        showSpecificPeriodNewIconToArticles(articleDate,articleLink);
        showCommentIconWithNumber(commentLength,articleLink);

        articleTitleFlagment.appendChild(articleItem).appendChild(articleLink);
    };
    return articleTitleFlagment;
}

const activateFirstTopic = (newsData) => {
  const tabTopics   = document.getElementsByClassName("topics-list__item");
  const topicsImg   = document.getElementsByClassName("topics-img__item");
  const articleList = document.getElementsByClassName("article-list");

  const index = newsData.findIndex((value) => value.isActive === true);
  if (index) {
    tabTopics[index].classList.add("is-active");
    topicsImg[index].classList.add("is-active");
    articleList[index].classList.add("is-active");
  }
}

const switchTabTopics = () => {
    const tabTopics   = document.getElementsByClassName('topics-list__item');
    const topicsImg   = document.getElementsByClassName('topics-img__item');
    const articleList = document.getElementsByClassName('article-list');

    for (const topic of tabTopics ){
        topic.addEventListener('click',function (){
        const tabTopicsIsActive = document.getElementsByClassName('topics-list__item is-active');
        const topicsImgIsActive = document.getElementsByClassName('topics-img__item is-active');
        const articleIsActive   = document.getElementsByClassName('article-list is-active');

        const arrayTabs = Array.from(tabTopics);
        const index = arrayTabs.indexOf(this);

        tabTopicsIsActive[0].classList.remove('is-active');
        this.classList.add('is-active');

        topicsImgIsActive[0].classList.remove('is-active');
        topicsImg[index].classList.add('is-active');

        articleIsActive[0].classList.remove('is-active');
        articleList[index].classList.add('is-active');
        });
    }
}

init();

だいぶ、時間はかかりましたが、無事に終わることができてほっとしました。Viteについての記事は、また別に書こうと思います。

学んだこと

  • 分割代入のやり方
  • 関数は一つのことをするように書く
  • date-fnsの使い方
  • Viteのインストールの仕方
  • 実引数名は明確にする

次は、スライドショーを実装します。頑張ります!

私が所属しているフロントエンドエンジニアを目指す方のための塾 「もりけん塾」の森田賢二先生のTwitterはこちら!

先生のブログ「武骨日記」はこちら!

https://kenjimorita.jp/

まい

Webサービス制作会社で、Wordpressのテーマ開発や2Dシミュレーターの開発、JavaScriptを使用したフロントエンド周りの実装を担当しています。 JavaScriptが好きです。 最近は、3D Model Configuratorの制作にチャレンジしています。

もりけん塾カテゴリの最新記事