プラグインなしで「お気に入り」機能を追加する方法(クッキー版)

Cocoonカスタマイズ集
この記事は約35分で読めます。
記事内に広告が含まれています。

Ajax(非同期通信)で表示を更新する方法

リロード方式が「ページ全体を再読み込みしてクッキーの状態を反映させる」という標準的な手法であるのに対し、Ajax方式は「ページ遷移を伴わず、特定の要素だけを最新の状態に差し替える」高度な手法です。

通常、ウィジェットの表示設定はAjaxリクエスト時には保持されません。
しかし、本プログラムでは初回のウィジェット描画時にそれらの設定をサーバーのセッションへ一時保存し、Ajax呼び出し時に取り出して再適用する仕組みを構築しています。
これにより、リロードなしでもウィジェット固有のカスタマイズを維持したまま、滑らかな表示更新します。

実装手順

以下の手順で実装します。

  • ラベル
    PHPを追加

    以下のコードをfunctions.phpに追加します。
    お気に入り投稿はライブラリ「jquery.cookie.js」を用い保存します。

    // フロント用JS・CSSを追加
    add_action('wp_enqueue_scripts', function () {
      wp_enqueue_script('jquery-cookie', 'https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js', ['jquery'], '1.4.1', true);
    
      $ajax_url = admin_url('admin-ajax.php');
    
      ob_start();
      ?>
      jQuery(function ($) {
        var KEY = 'my_fav_page_list';
    
        /**
         * お気に入りボタンの状態を更新
         *
         * @return {void}
         */
        function syncUI() {
          // クッキーからデータを取得
          const currentCook = JSON.parse($.cookie(KEY) || '[]');
    
          $('.fav-button').each(function () {
            const id = String($(this).data('id'));
            $(this).toggleClass('active', currentCook.indexOf(id) > -1);
          });
        }
    
        /**
         * .favorite要素(Swiperコンテナ本体)に対してSwiperを初期化
         *
         * @param {jQuery} $container 初期化対象の要素
         * @return {void}
         */
        function initFavoriteSwiper($container) {
          if (typeof Swiper === 'undefined') {
            return;
          }
    
          if (!$container.length || !$container.hasClass('swiper')) {
            return;
          }
    
          // 初期化済みの場合は処理対象外
          if ($container[0].swiper) {
            return;
          }
    
          // Swiperインスタンスの初期化を設定
          new Swiper($container[0], {
            slidesPerView: 'auto',
            spaceBetween: 30,
            navigation: {
              nextEl: $container.find('.swiper-button-next')[0],
              prevEl: $container.find('.swiper-button-prev')[0],
            },
            observer: true,
            observeParents: true,
          });
        }
    
        /**
         * お気に入り一覧をAjaxで更新
         *
         * @return {void}
         */
        function refreshFavWidget() {
          const $favoriteArea = $('.favorite');
    
          if (!$favoriteArea.length) {
            return;
          }
    
          $.post('<?php echo esc_url($ajax_url); ?>', {
            action: 'refresh_fav_list'
          }, function (response) {
            if (!response) {
              return;
            }
    
            // 差し替え前に、既存のSwiperインスタンスを破棄
            $favoriteArea.each(function () {
              if (this.swiper) {
                this.swiper.destroy(true, true);
              }
            });
    
            // DOMを差し替え
            const $newContent = $($.trim(response));
            $favoriteArea.replaceWith($newContent);
            syncUI();
    
            // 新しいコンテナに対してSwiperを再初期化
            initFavoriteSwiper($newContent);
          });
        }
    
        // お気に入りボタンクリック
        $(document).on('click', '.fav-button', function (e) {
          e.preventDefault();
          e.stopPropagation();
    
          // クッキーからデータを取得
          const $btn = $(this);
          const id = String($btn.data('id'));
          let cook = JSON.parse($.cookie(KEY) || '[]');
          const index = cook.indexOf(id);
    
          // お気に入り状態を変更
          if (index > -1) {
            cook.splice(index, 1);
          } else {
            cook.push(id);
          }
    
          // クッキーを更新
          $.cookie(KEY, JSON.stringify(cook), { expires: 365, path: '/' });
    
          // お気に入り一覧の更新を通知
          $(document).trigger('favListUpdated');
        });
    
        // お気に入り一覧更新時に再描画
        $(document).on('favListUpdated', function () {
          syncUI();
          refreshFavWidget();
        });
    
        // クッキーの有効期限を更新
        const initialCook = JSON.parse($.cookie(KEY) || '[]');
    
        if (initialCook.length > 0) {
          $.cookie(KEY, JSON.stringify(initialCook), { expires: 365, path: '/' });
        }
    
        syncUI();
      });
      <?php
      wp_add_inline_script('jquery-cookie', ob_get_clean(), 'after');
    });
    
    
    /**
     * Ajaxでお気に入り一覧を出力
     *
     * @return void
     */
    if (!function_exists('ajax_refresh_fav_list')):
    function ajax_refresh_fav_list() {
      echo do_shortcode('[new_list action="favorite"]');
      wp_die();
    }
    endif;
    
    
    // Ajaxアクションを追加
    add_action('wp_ajax_refresh_fav_list', 'ajax_refresh_fav_list');
    add_action('wp_ajax_nopriv_refresh_fav_list', 'ajax_refresh_fav_list');
    
    
    // 新着記事のクエリを設定
    add_filter('widget_entries_args', function ($args) {
      if (empty($GLOBALS['favorite'])) {
        return $args;
      }
    
      $ids = [];
    
      // クッキーからお気に入りIDを取得
      if (isset($_COOKIE['my_fav_page_list'])) {
        $ids = json_decode(stripslashes($_COOKIE['my_fav_page_list']), true);
      }
    
      // お気に入り記事がない場合は終了
      if (empty($ids) || !is_array($ids)) {
        $args['post__in']       = [0];
        $args['posts_per_page'] = 0;
      } else {
        $ids = array_unique(array_map('intval', (array) $ids));
        $args['post__in'] = array_reverse($ids);
        $args['orderby'] = 'post__in';
      }
    
      $args['ignore_sticky_posts'] = true;
    
      return $args;
    });
    
    // テンプレート(エントリーカード)を変更
    add_filter('cocoon_part__tmp/entry-card', function ($content) {
      ob_start();
      get_fav_button();
      $button = ob_get_clean();
    
      return preg_replace('/(<h2[^>]*>)/', '$1' . $button, $content);
    });
    
    
    /**
     * お気に入りボタンを出力
     *
     * @return void
     */
    if (!function_exists('get_fav_button')):
    function get_fav_button() {
      echo '<span class="fav-button" data-id="' . get_the_ID() . '" title="お気に入り"><i class="fas fa-paperclip"></i></span>';
    }
    endif;
    
    
    // 新着記事の属性を設定
    add_filter('shortcode_atts_generate_widget_entries_tag', function ($out, $pairs, $atts) {
      if (!session_id() && !headers_sent()) {
        session_start();
      }
    
      if (
        empty($GLOBALS['favorite']) &&
        (!isset($out['action']) || $out['action'] !== 'favorite')
      ) {
        return $out;
      }
    
      // 初回の設定をセッションに保存
      if (!wp_doing_ajax()) {
        $_SESSION['my_fav_page_list_atts'] = $out;
      }
      // Ajax再描画時に復元
      elseif (isset($_SESSION['my_fav_page_list_atts'])) {
        $out = $_SESSION['my_fav_page_list_atts'];
      }
    
      $out['class'] .= ($out['class'] ? ' ' : '') . 'favorite';
    
      $GLOBALS['favorite'] = true;
      add_action('widget_entry_card_date_before', 'get_fav_button', 10, 2);
    
      // 新着記事終了時に状態をリセット
      add_filter('do_shortcode_tag', function ($output, $tag) {
        if ($tag === 'new_list') {
          $GLOBALS['favorite'] = false;
          remove_action('widget_entry_card_date_before', 'get_fav_button');
        }
    
        return $output;
      }, 10, 2);
    
      return $out;
    }, 10, 3);
    
    
    // ウィジェットに設定メニューを追加
    add_action('in_widget_form', function ($widget, $return, $instance) {
      // 対象ウィジェットを確認
      if ($widget->id_base !== 'new_entries') {
        return;
      }
    
      $f_id    = $widget->get_field_id('favorite');
      $f_name  = $widget->get_field_name('favorite');
      $checked = checked(!empty($instance['favorite']), true, false);
    
      ?>
      <p>
        <input type="checkbox" id="<?= $f_id; ?>" class="widefat" name="<?= $f_name; ?>" value="1" <?= $checked; ?>>
        <label for="<?= $f_id; ?>">お気に入り</label>
      </p>
      <?php
    }, 10, 3);
    
    
    // ウィジェットの入力データを更新
    add_filter('widget_update_callback', function ($instance, $new_instance) {
      $instance['favorite'] = isset($new_instance['favorite']) ? 1 : 0;
    
      return $instance;
    }, 10, 2);
    
    
    // ウィジェットの表示設定を変更
    add_filter('widget_display_callback', function ($instance, $widget, $args) {
      if ($widget->id_base === 'new_entries' && !empty($instance['favorite'])) {
        $GLOBALS['favorite'] = true;
        add_action('widget_entry_card_date_before', 'get_fav_button');
      } else {
        $GLOBALS['favorite'] = false;
        remove_action('widget_entry_card_date_before', 'get_fav_button');
      }
    
      return $instance;
    }, 10, 3);
  • ラベル
    「お気に入りをリセット」ボタンを追加

    以下のコードをfunctions.phpに追加します。

    add_shortcode('my_clear_fav_button', function () {
      ob_start();
      ?>
      <style>
      .clear-container {
        text-align: center;
      }
    
      /* 非活性 */
      .clear-fav-btn {
        background-color: var(--fav-button-off);
        border-color: var(--fav-button-off);
        pointer-events: none;
      }
    
      /* 活性 */
      .clear-fav-btn.active {
        background-color: var(--fav-button-on);
        border-color: var(--fav-button-on);
        cursor: pointer;
        pointer-events: auto;
      }
      </style>
    
      <script>
      jQuery(function ($) {
    
        /**
         * リセットボタンの状態を更新
         *
         * @return {void}
         */
        function syncClearButtonUI() {
          if (typeof $.cookie === 'undefined') {
            return;
          }
          // クッキーからデータを取得
          const currentCook = JSON.parse($.cookie('my_fav_page_list') || '[]');
          $('.clear-fav-btn').toggleClass('active', currentCook.length > 0);
        }
    
        $(document).on('click', '.clear-fav-btn', function (e) {
          e.preventDefault();
    
          if (typeof $.removeCookie !== 'undefined') {
            // クッキーを削除
            $.removeCookie('my_fav_page_list', { path: '/' });
          }
    
          // ボタンの状態を更新
          syncClearButtonUI();
    
          // お気に入り一覧の更新を通知
          $(document).trigger('favListUpdated');
        });
    
        // お気に入り一覧更新時に状態を更新
        $(document).on('favListUpdated', function () {
          syncClearButtonUI();
        });
    
        // 初回の状態を確認
        syncClearButtonUI();
      });
      </script>
    
      <div class="clear-container">
        <button class="clear-fav-btn btn">お気に入りをリセット</button>
      </div>
      <?php
      return ob_get_clean();
    });
タイトルとURLをコピーしました