はじめに
この記事では、WordPressテーマ「SWELL」を使用した環境で、ユーザーが「都道府県 → 市区町村 → 駅」を選択することで、該当エリアのバナー画像やHTMLコードを表示する仕組みを実装する手順を紹介します。
SWELLの標準機能では対応できない複雑なカテゴリ選択+バナー出力に対応しており、複数サイトにも汎用化可能です。
実装の前提条件
- WordPressが導入されていること
- 使用テーマがSWELLであること
- プラグイン「Smart Custom Fields(SCF)」が導入・有効化されていること
- Code Snippetsプラグインがインストール済であること(PHPとJSの埋め込み用)
ステップ1:カテゴリー構造を設定する
- WordPress管理画面 > 投稿 > カテゴリー から、以下のようにカテゴリーを構築してください:
- 第一階層:都道府県(例:神奈川県)
- 第二階層:市区町村(例:横浜市)
- 第三階層:駅(例:関内駅)※無い場合は第二階層まででOK
ステップ2:カテゴリーにカスタムフィールドを追加する
Smart Custom Fields(SCF)を使用して、カテゴリーに以下のフィールドを追加します。
- WordPress管理画面 > SCF > フィールドグループを新規作成
- 投稿タイプではなく「タクソノミー:カテゴリー」を対象に設定
- 以下のフィールドを追加:
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コードはコピー可能なボックスで出力され、ユーザーはそのまま使用できます。
- 今後、繰り返しバナーやスライダー表示にも応用可能です。
コメント