Ajax(非同期通信)で表示を更新する方法
動作シーケンス
JS⇔PHP間の動作シーケンスを以下にまとめました。
初回ページ読み込み
ブラウザ (JS)
サーバー (PHP)
1
1. ページをリクエスト
2
2. ウィジェットを空(0件)に初期化
通常の新着記事が一瞬表示されるのを防ぐため、クエリを空にする
3
3. 空のウィジェット枠を含むHTMLを返却
ここで画面が表示される(中身はまだ空)
4
4. JS起動 & ローカルストレージ参照
お気に入りID(例: [123, 456])を読み出す
5
5. Ajax POSTでIDをリクエスト
6
6. ショートコード実行 & リストHTML生成
届いたIDを元に、お気に入り記事だけのHTMLを組み立てる
7
7. 生成したリストのHTMLを返却
8
8. 枠内にリストを流し込み & ボタン同期
空だったお気に入り枠に記事を書き込み、ボタンが点灯して完成
お気に入りボタンクリック(追加・削除)
ブラウザ (JS)
サーバー (PHP)
読者がボタンをクリック
お気に入り追加/削除のトリガーが発生。
1
1. クリックされたIDを取得
Local Storageの配列内に該当IDがあれば削除、なければ追加(例: [123, 456] ➔ [456])。
2
2. ローカルストレージへデータを保存
最新の状態に更新された新しい配列 [456] をローカルストレージに保存。
3
3. syncUI()
通信を待たずに、画面内の該当する全てのボタンの見た目を即座にオフ(非活性)にする。
4
4. refreshFavWidget() 発火
UI同期後、最新リストを読み込むための関数を実行。
5
Ajax POST (fav_ids: [456])
6
6. ajax_refresh_fav_list() 発火
PHP側でリクエストを受信。SESSION情報をお気に入りの最新配列 [456] に更新。
7
7. ショートコードを再実行
最新のSESSIONを基に、ID: 456 のみが含まれる新しいお気に入りリストHTMLを生成。
8
HTML (Response) を返却
9
9. ウィジェットHTMLを差し替え
今削除した記事が消えた、1件減った状態の最新リストに丸ごと切り替わる。
10
10. syncUI()
新しく差し替わったウィジェット内のボタン状態を、Local Storageの内容と最終同期。
実装手順
以下のコードをfunctions.phpに追加します。
- ラベルPHPを追加
以下のコードをfunctions.phpに追加します。
// フロント用JS・CSSを追加 add_action('wp_enqueue_scripts', function () { $ajax_url = admin_url('admin-ajax.php'); ob_start(); ?> jQuery(function ($) { var KEY = 'my_fav_page_list'; /** * ローカルストレージからIDリストを取得 * * @return {Array<string|number>} 保存されているIDの配列 */ function getIdsFromLS() { try { return JSON.parse(localStorage.getItem(KEY) || '[]'); } catch (e) { return []; } } /** * IDリストをローカルストレージに保存 * * @param {Array<string|number>} ids 保存するIDの配列 * @return {void} */ function saveIds(ids) { localStorage.setItem(KEY, JSON.stringify(ids)); } /** * お気に入りボタンの状態を更新 * * @return {void} */ function syncUI() { const currentIds = getIdsFromLS().map(String); $('.fav-button').each(function () { const id = String($(this).data('id')); $(this).toggleClass('active', currentIds.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; } 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; } const ids = getIdsFromLS(); // ローカルストレージのIDをPOST送信 $.post('<?php echo esc_url($ajax_url); ?>', { action: 'refresh_fav_list', fav_ids: ids }, 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 ids = getIdsFromLS().map(String); const index = ids.indexOf(id); // お気に入り状態を変更 if (index > -1) { ids.splice(index, 1); } else { ids.push(id); } // ローカルストレージを更新 saveIds(ids); syncUI(); refreshFavWidget(); // お気に入り一覧の更新を通知 $(document).trigger('favListUpdated'); }); // お気に入り一覧更新時に再描画 $(document).on('favListUpdated', function () { syncUI(); refreshFavWidget(); }); refreshFavWidget(); syncUI(); }); <?php $js = ob_get_clean(); wp_add_inline_script('jquery', $js, 'after'); }); /** * Ajaxでお気に入り一覧を出力 * * @return void */ if (!function_exists('ajax_refresh_fav_list')): function ajax_refresh_fav_list() { // POSTされたお気に入りIDを取得 $fav_ids = isset($_POST['fav_ids']) ? array_map('intval', (array) $_POST['fav_ids']) : []; if (!session_id() && !headers_sent()) { session_start(); } $_SESSION['my_fav_page_list'] = $fav_ids; // お気に入り一覧を生成 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 (wp_doing_ajax() && isset($_SESSION['my_fav_page_list'])) { $ids = $_SESSION['my_fav_page_list']; } // お気に入り記事がない場合は終了 if (empty($ids) || !is_array($ids)) { $args['post__in'] = [0]; $args['posts_per_page'] = 0; } else { $ids = array_unique($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() { ?> <span class="fav-button" data-id="<?= get_the_ID(); ?>" title="お気に入り"><i class="fas fa-paperclip"></i></span> <?php } 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再描画時に復元 else if (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_filter('widget_entries_args', 'clear_initial_widget_args', 999); } else { $GLOBALS['favorite'] = false; } return $instance; }, 10, 3); /** * 初回ページ読み込み時のお気に入りクエリを初期化 * * @param array $args 現在のウィジェットのクエリ引数 * @return array 更新されたクエリ引数 */ if (!function_exists('clear_initial_widget_args')): function clear_initial_widget_args($args) { if (!wp_doing_ajax()) { $args['post__in'] = [0]; $args['posts_per_page'] = 0; } return $args; } endif; - ラベル「お気に入りをリセット」ボタンを追加
以下のコードを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 ($) { /** * ローカルストレージからIDリストを取得 * * @return {Array<string>} 保存されているIDの配列 */ function getIdsFromLS() { try { return JSON.parse(localStorage.getItem('my_fav_page_list') || '[]'); } catch (e) { return []; } } /** * リセットボタンの状態を更新 * * @return {void} */ function syncClearButtonUI() { const currentIds = getIdsFromLS(); $('.clear-fav-btn').toggleClass('active', currentIds.length > 0); } $(document).on('click', '.clear-fav-btn', function (e) { e.preventDefault(); // ローカルストレージを削除 localStorage.removeItem('my_fav_page_list'); // ボタンの状態を更新 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(); });
さいごに
今回の方法を使えば、プラグインを使わずに、独自の「お気に入り」機能を追加することができます。
「新着記事」ウィジェットに「お気に入り」オプションを設け、ユーザーは任意の投稿をお気に入りとして登録・表示できるようになります。
この仕組みにより、ユーザーが興味のあるコンテンツに簡単にアクセスでき、利便性を向上させることが可能です。
