MENU

WordPress(SWELL)で都道府県・市区町村・駅を選択し、バナーリンクを動的に表示する方法

本マニュアルについて

本マニュアルを当サイトのお仕事以外で使用することは厳禁となります。

本コンテンツは非常に秘匿性・希少性・独自性の高いノウハウで構成されていますそれゆえ、情報流出のリスクを減らすため、本コンテンツを直接または間接的に外部共有することを固く禁止させて頂きます。

万が一、「当サイトに関係のない外部へ、無断かつ意図的な共有」が確認出来た場合、コンテンツ共有費として「無断で情報を共有したと認められる人数 × 100万円」をお支払い頂きます。

なお、ネット掲示板やSNS等、不特定多数が閲覧できる状態にした場合は、コンテンツ共有費として1,000万円をお支払い頂きます。本コンテンツを確認した時点でこの項目に同意頂いたものと致します。ご理解のほどお願い致します。

この内容に関する紛争は、東京地方裁判所を第一審の専属的合意管轄裁判所とする。

コチラに同意できた場合には、チャットの方で「社内パス」とだけコメントを送ってください。

はじめに

この記事では、WordPressテーマ「SWELL」を使用した環境で、ユーザーが「都道府県 → 市区町村 → 駅」を選択することで、該当エリアのバナー画像やHTMLコードを表示する仕組みを実装する手順を紹介します。

SWELLの標準機能では対応できない複雑なカテゴリ選択+バナー出力に対応しており、複数サイトにも汎用化可能です。

実装の前提条件

  • WordPressが導入されていること
  • 使用テーマがSWELLであること
  • プラグイン「Smart Custom Fields(SCF)」が導入・有効化されていること
  • Code Snippetsプラグインがインストール済であること(PHPとJSの埋め込み用)

ステップ1:カテゴリー構造を設定する

  1. WordPress管理画面 > 投稿 > カテゴリー から、以下のようにカテゴリーを構築してください:
    • 第一階層:都道府県(例:神奈川県)
    • 第二階層:市区町村(例:横浜市)
    • 第三階層:駅(例:関内駅)※無い場合は第二階層まででOK

ステップ2:カテゴリーにカスタムフィールドを追加する

Smart Custom Fields(SCF)を使用して、カテゴリーに以下のフィールドを追加します。

  1. WordPress管理画面 > SCF > フィールドグループを新規作成
  2. 投稿タイプではなく「タクソノミー:カテゴリー」を対象に設定
  3. 以下のフィールドを追加:
    • banner_image(画像):バナー画像
    • html_code(WYSIWYG):HTMLコード
    • banner_link_url(テキスト):リンク先URL
    • banner_link_name(テキスト):リンク先名

ステップ3:Code SnippetsでPHPコードを追加

以下の機能をPHPで実装します:

  • Ajaxで子カテゴリーの取得
  • Ajaxでバナー情報の取得
  • フロントでJavaScriptとスタイルを出力

// ▼ 都道府県・市区町村のカテゴリー取得(Ajaxハンドラ)
add_action('wp_ajax_get_child_terms', 'get_child_terms_callback');
add_action('wp_ajax_nopriv_get_child_terms', 'get_child_terms_callback');

function get_child_terms_callback() {
    $parent_id = isset($_POST['parent_id']) ? intval($_POST['parent_id']) : 0;

    if (!isset($_POST['parent_id'])) {
        wp_send_json_error('パラメータが不正です');
    }

    $terms = get_terms([
        'taxonomy' => 'category',
        'parent' => $parent_id,
        'hide_empty' => false
    ]);

    $response = [];
    $seen_ids = [];

    foreach ($terms as $term) {
        if (!in_array($term->term_id, $seen_ids)) {
            $response[] = [
                'id' => $term->term_id,
                'name' => $term->name
            ];
            $seen_ids[] = $term->term_id;
        }
    }

    wp_send_json($response);
}

// ▼ バナー画像+HTMLコードの取得(Ajaxハンドラ)
add_action('wp_ajax_get_banner_html', 'get_banner_html_callback');
add_action('wp_ajax_nopriv_get_banner_html', 'get_banner_html_callback');

function get_banner_html_callback() {
    $term_id = isset($_POST['term_id']) ? intval($_POST['term_id']) : 0;
    if (!$term_id) {
        wp_send_json_error('IDが不正です');
    }

    $image_id = get_term_meta($term_id, 'banner_image', true);
    $image_url = wp_get_attachment_url($image_id);
    $html_code = get_term_meta($term_id, 'html_code', true);
    $html_code = html_entity_decode($html_code);

    error_log('Term ID: ' . $term_id);
    error_log('Image ID: ' . print_r($image_id, true));
    error_log('Image URL: ' . print_r($image_url, true));

    wp_send_json([
        'image' => esc_url($image_url),
        'html' => $html_code,
        'link_url' => esc_url(get_term_meta($term_id, 'banner_link_url', true)),
        'link_name' => get_term_meta($term_id, 'banner_link_name', true)
    ]);
}

// ▼ JavaScript + CSS
add_action('wp_footer', 'custom_area_selector_script');
function custom_area_selector_script() {
?>
<style>
#banner-result {
  display: block !important;
  visibility: visible;
  opacity: 1;
  padding: 20px;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 0 12px rgba(0, 0, 0, 0.1);
  font-size: 14px;
  font-family: "Helvetica Neue", sans-serif;
  color: #333;
  text-align: left;
}
#banner-result .banner-html-box {
  padding: 12px;
  background: #fdfdfd;
  border: 1px solid #bbb;
  display: inline-block;
  margin-bottom: 10px;
}
#banner-result code {
  font-family: Consolas, monospace;
  font-size: 13px;
  white-space: pre-wrap;
  word-break: break-word;
  display: block;
  background: #f5f5f5;
  padding: 12px;
  border: 1px dashed #888;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.2s ease-in-out;
  margin-top: 10px;
}
#banner-result code:hover {
  background: #eee;
}
#banner-result .html-note {
  color: red;
  font-weight: normal;
  font-size: 12px;
  margin-top: 15px;
  margin-bottom: 5px;
}
#show-banner {
  background-color: #4CAF50;
  color: white;
  padding: 6px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  transition: background 0.3s ease-in-out;
  margin-left: 12px;
}
#show-banner:hover {
  background-color: #45a049;
}
</style>
<script>
(function() {
  const ajaxurl = "<?php echo admin_url('admin-ajax.php'); ?>";
  const excludeNames = ["未分類", "X投稿用"];

  function initAreaSelector() {
    const prefectureSelect = document.getElementById('prefecture');
    const areaSelect = document.getElementById('area');
    const stationSelect = document.getElementById('station');
    const showBannerBtn = document.getElementById('show-banner');
    const bannerResult = document.getElementById('banner-result');

    function populateSelect(selectElement, data) {
      const placeholder = selectElement.querySelector('option[value=""]')?.outerHTML || '';
      selectElement.innerHTML = placeholder;

      const seenIds = new Set();
      data.forEach(term => {
        if (!seenIds.has(term.id) && !excludeNames.includes(term.name)) {
          seenIds.add(term.id);
          const option = document.createElement('option');
          option.value = term.id;
          option.textContent = term.name;
          selectElement.appendChild(option);
        }
      });
    }

    function loadPrefectures() {
      fetch(ajaxurl, {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: new URLSearchParams({ action: "get_child_terms", parent_id: 0 })
      })
      .then(res => res.json())
      .then(data => {
        prefectureSelect.innerHTML = '<option value="">都道府県を選択</option>';
        populateSelect(prefectureSelect, data);
      });
    }

    function loadAreas(parentId) {
      areaSelect.innerHTML = '<option value="">市区町村を選択</option>';
      stationSelect.innerHTML = '<option value="">駅を選択</option>';
      if (!parentId) return;

      fetch(ajaxurl, {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: new URLSearchParams({ action: "get_child_terms", parent_id: parentId })
      })
      .then(res => res.json())
      .then(data => {
        populateSelect(areaSelect, data);
      });
    }

    function loadStations(parentId) {
      stationSelect.innerHTML = '';
      if (!parentId) return;

      fetch(ajaxurl, {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: new URLSearchParams({ action: "get_child_terms", parent_id: parentId })
      })
      .then(res => res.json())
      .then(data => {
        if (data.length === 0) {
          const opt = document.createElement('option');
          opt.textContent = '決定をクリックしてください';
          opt.disabled = true;
          opt.selected = true;
          stationSelect.appendChild(opt);
          return;
        }

        const defaultOpt = document.createElement('option');
        defaultOpt.textContent = '駅を選択';
        defaultOpt.value = '';
        stationSelect.appendChild(defaultOpt);

        populateSelect(stationSelect, data);
      });
    }

    function loadBanner(termId) {
      if (!termId) return;

      fetch(ajaxurl, {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: new URLSearchParams({ action: "get_banner_html", term_id: termId })
      })
      .then(res => res.json())
      .then(data => {
        bannerResult.innerHTML = '';

        if (data.image) {
          const img = document.createElement('img');
          img.src = data.image;
          img.style.maxWidth = '100%';
          bannerResult.appendChild(img);
        }

        if (data.link_url || data.link_name) {
          const metaInfo = document.createElement('div');
          metaInfo.innerHTML =
            (data.link_url ? `<div style="text-align:left;margin-top:6px;">【リンク先URL】 ${data.link_url}</div>` : '') +
            (data.link_name ? `<div style="text-align:left;margin-top:4px;margin-bottom:10px;">【リンク先名】 ${data.link_name}</div>` : '');
          bannerResult.appendChild(metaInfo);
        }

        if (data.html) {
          const note = document.createElement('div');
          note.className = 'html-note';
          note.innerText = '下に表示されているテキストを丸ごとコピーしてHTMLソースに貼り付けてください。';
          bannerResult.appendChild(note);

          const code = document.createElement('code');
          code.textContent = data.html;
          code.title = 'クリックでコピー';
          code.addEventListener('click', function() {
            navigator.clipboard.writeText(data.html);
            alert('HTMLコードをコピーしました!');
          });
          bannerResult.appendChild(code);
        }
      });
    }

    function triggerBannerLoad() {
      const stationValue = stationSelect.value;
      const areaValue = areaSelect.value;
      const targetId = stationSelect.options.length > 1 && stationValue ? stationValue : areaValue;
      loadBanner(targetId);
    }

    prefectureSelect.addEventListener('change', () => loadAreas(prefectureSelect.value));
    areaSelect.addEventListener('change', () => {
      loadStations(areaSelect.value);
      if (stationSelect.options.length <= 1) {
        triggerBannerLoad();
      }
    });
    stationSelect.addEventListener('change', () => triggerBannerLoad());
    showBannerBtn.addEventListener('click', () => triggerBannerLoad());

    loadPrefectures();
  }

  window.addEventListener('DOMContentLoaded', initAreaSelector);
})();
</script>
<?php
}

ステップ4:フロントでセレクタとバナーを表示

固定ページやカスタムテンプレートに以下のHTMLを記載:

<div id="area-selector">
  <select id="prefecture">
    <option value="">都道府県を選択</option>
  </select>

  <select id="area">
    <option value="">市区町村を選択</option>
  </select>

  <select id="station">
    <option value="">駅を選択</option>
  </select>

  <button id="show-banner">バナーリンクを表示する</button>
</div>

<div id="banner-result"></div>

固定ページのCSSは以下

#show-banner {
  background-color: #4A4A4A !important;
  color: white;
  font-weight: bold;
  padding: 6px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  transition: background 0.3s ease-in-out;
  display: block;
  margin-top: 16px;
  margin-left: 0;
  text-align: left;
}

追加CSS

/* バナー選択 */

#area-selector {
  margin-bottom: 20px;
}

#area-selector select,
#area-selector button {
  padding: 8px;
  margin-right: 10px;
}

#banner-result {
  margin-top: 20px;
  background: #f9f9f9;
  padding: 20px;
}




#banner-result {
  display: block !important;
  visibility: visible;
  opacity: 1;
  max-height: unset;
}

ステップ4:SWELLテーマの子テーマ(推奨)の footer.php に追記

SWELLを直接編集するのは非推奨なので、子テーマがある場合は footer.php をそちらにコピー&編集しますfooter.php に debug 用のPHPコードを追加。

以下のように <?php wp_footer(); ?>直前に入れてください。

<?php
$term_id = ここに確認したいカテゴリーID;
if (function_exists('SCF::get_term_meta')) {
    $image_id = SCF::get_term_meta($term_id, 'banner_image', 'category');
    $html = SCF::get_term_meta($term_id, 'html_code', 'category');

    echo '<!-- SCF関数は存在します -->';
    echo '<!-- debug: image=' . $image_id . ', html=' . htmlspecialchars($html) . ' -->';
} else {
    echo '<!-- SCF関数が存在しません(footer.php内) -->';
}
?>
<?php wp_footer(); ?>
</body>
</html>

補足

  • 駅がないエリアでは市区町村で自動的にバナーが表示されます。
  • HTMLコードはコピー可能なボックスで出力され、ユーザーはそのまま使用できます。
  • 今後、繰り返しバナーやスライダー表示にも応用可能です。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次