いいねボタン・改をjQuery使わないようにしてみた

do様で配布しているこちらのプログラム、レンタルサーバーに置こうと思ったときに「jQueryを使って非同期通信してるのをFetchAPIにしてもいいか…?」となったのでざっくりと書きました。

(ただの趣味です)(いけるかなと思っただけで、バックエンドのphpファイルは触ってないし処理内容が変わるわけではないので書き換える必要自体まずないです)(そもそもプログラミング素人です)

個人的にお礼メッセージが必要なかったのでそこは書いてないです。

MITライセンスで改変自由ということから有難くごりごりと使わせて頂いております。ただの備忘録としてコード残しておきます。エラーハンドリング部分はかなりchatGPTに質問しながら書いてます難しいですね。

サンプルコード

iine.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>Hello, world!</title>
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <link rel="stylesheet" href="./newiine/newiine.css">
</head>
<body>
  <h1>Hello, world!</h1>

<!-- いいねボタン改ここから -->
  <button type="submit" class="newiine_btn" data-iinename="like-0" data-iinecountlimit="99999">
    <span class="material-icons-round">favorite</span>
    <span class="newiine_count"></span>いいね
  </button>
<!-- いいねボタン改ここまで -->

<!-- いいねボタン改ここから -->
  <button type="submit" class="newiine_btn" data-iinename="like-1" data-iinecountlimit="99999">
    <span class="material-icons-round">favorite</span>
    <span class="newiine_count"></span>いいね
  </button>
<!-- いいねボタン改ここまで -->

<!-- 読み込み -->
  <script src="./newiine/new2iine.js" type="module" data-newiine-js></script>

</body>
</html>

htmlファイルに書いておく<script>要素

<script src="./newiine/new2iine.js" type="module" data-newiine-js></script>
  • データ属性(data-newiine-js)を付与したスクリプト自身のURLを取得するようにしている

  • moduleとして読み込んでいる

以上の相違点があります。
内部的には、どうやって自身のURLを取得するか?はURLコンストラクタを利用して組み立てるようにしました。後はjqXHRではなくfetchAPIを利用してます。

new2iine.js, likeButtonHandler.js, _ajax.php は同じ階層に置いておきます。(同階層にあることを前提としてコードされているからです)

(20240319追記)data-iinename="like-1"のようにボタンに名前をつける際には半角英数字、アンダーバー、ハイフン推奨(=全角文字は非推奨)。いいねボタンがカウントされた回数などの情報をcsvファイルに保存するプログラムなのですが、このcsvファイルを生成するときファイル名は設定したdata-iinename="like-1"のline-1部分が使われるみたいなので、サーバーに置くファイル名に2バイト文字やスラッシュ、ピリオド、コロン等の一部の記号とか含めない方がいいよっていうのかなと思います。それはそう。

new2iine.js

import { setupLikeButtons } from './likeButtonHandler.js';

setupLikeButtons();

likeButtonHandler.js

// baseUrl 取得関数
const getBaseUrl = () => {
  const scriptElement = document.querySelector('script[data-newiine-js]');
  if (!scriptElement) {
    throw new Error('newiine.js script tag with data-newiine-js attribute not found.');
  }

  try {
    let url = new URL(scriptElement.src, window.location.href);
    url.pathname = url.pathname.replace(/\/[^\/]+$/, "/");
    return url.href;
  } catch (error) {
    throw new Error(`Error creating base URL: ${error}`);
  }
};

// _ajax.php のURLを取得し、その有効性を確認する
const checkAjaxPath = () => {
  try {
    const base = getBaseUrl();
    const ajaxUrl = new URL("_ajax.php", base).href;
    // ここでajaxUrlの形式や有効性を確認する追加の検証が必要であれば行う
    return ajaxUrl;
  } catch (error) {
    console.error('Error obtaining or validating ajaxPath URL:', error);
    throw error; // エラーを再投げて、呼び出し元で対応できるようにする
  }
};
// 'いいね'の数の表示を更新する関数
function updateLikeCountView(button, newCount) {
  const countElement = button.querySelector('.newiine_count');
  if (countElement) {
    countElement.textContent = newCount;
  }
}

// ボタンの初期状態を設定する関数
const setupButtonInitialState = async (button, buttonName, ajaxPath) => {
  try {
    const response = await fetch(`${ajaxPath}?buttonname=${encodeURIComponent(buttonName)}`, {
      method: 'GET',
    });
    if (!response.ok) {
      throw new Error('Network response was not ok.');
    }
    const res = await response.text(); //戻り値はstring型

    let data;
    try {
      data = JSON.parse(res);
    } catch (error) {
      throw new Error('Failed to parse initial button state data.');
    }

    updateLikeCountView(button, data[0]);

    if (data[1]) {
      button.classList.add('newiine_clickedtoday');
    }
  } catch (error) {
    console.error('Error fetching initial button state:', error);
    throw error; // エラーを再投げて、呼び出し元で対応できるようにする
  }
};

// ボタンクリックリスナーを追加する関数
const addButtonClickListener = (button, buttonName, ajaxPath) => {
  button.addEventListener('click', async (e) => {
    e.preventDefault();
    try {
      const iineUrl = button.dataset.iineurl || window.location.href;
      // iineCountLimitの値を取得し、数値変換を試みる
      const iineCountLimitValue = parseInt(button.dataset.iinecountlimit, 10);
      // 数値変換が成功し、かつ0より大きい場合はその値を、そうでない場合は"false"をセット
      const iineCountLimit = isNaN(iineCountLimitValue) || iineCountLimitValue <= 0 ? false : iineCountLimitValue;

      // URLSearchParams を使用してリクエストの body を構築
      const params = new URLSearchParams();
      params.append('path', iineUrl);
      params.append('buttonname', buttonName);
      params.append('iineNewCountLimit', iineCountLimit);
      params.append('mode', 'check');

      const response = await fetch(ajaxPath, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: params.toString()
      });
      if (!response.ok) {
        throw new Error('Network response was not ok.');
      }

      const res = await response.text(); //戻り値はstring型

      // Case: response [DanyIP, Upper, else]
      if (['upper', 'denyIP', 'else'].includes(res)) {
        const errorMessages = {
          'upper': 'You have reached the daily click limit.',
          'denyIP': 'This IP address is denied.',
          'else': 'There seems to be another problem.'
        };
        throw new Error(errorMessages[res]);
      }

      let data;
      try {
        data = JSON.parse(res);
      } catch (error) {
        throw new Error('Failed to parse response data.');
      }

      updateLikeCountView(button, data[0]);
      if (data[1]) {
        button.classList.add('newiine_clickedtoday');
      }
    } catch (error) {
      console.error('Error handling button click:', error);
    }
  });
};

// 'いいね'ボタンのセットアップと初期状態の取得
export const setupLikeButtons = async () => {
  try {
    const ajaxPath = checkAjaxPath();
    const likeButtons = document.querySelectorAll('.newiine_btn');
    for (const button of likeButtons) {
      const buttonName = button.dataset.iinename;
      try {
        await setupButtonInitialState(button, buttonName, ajaxPath);
        addButtonClickListener(button, buttonName, ajaxPath);
      } catch (error) {
        console.error('Error setting up like buttons:', error.message);
        // 最初のエラーでループから抜ける
        throw error;
      }
    }
  } catch (error) {
    // ここで全体のエラーハンドリングを行う
    console.error('Error during setup process:', error.message);
    // 必要に応じてユーザーに通知するなどの処理をここに追加
  }
};

fetch()ではなくaxiosやredaxiosも候補だったんで後からしれっと戻したり、ライブラリ使ってるかもしれない。

TODO: setupButtonInitialState()のエラーをrethrowして呼び出し元(setupLikeButton)で対応


この記事が気に入ったらサポートをしてみませんか?