| 商品ID | 商品名 | カテゴリー | 価格 | 説明 | 画像 | 詳細 |
|---|---|---|---|---|---|---|
| P001 | 有機ブレンドコーヒー | 飲料 | 1280 | 香り豊かな中深煎りのブレンドです。 | https://picsum.photos/seed/coffee/720/480 | https://amanogawaginga.jp/products/p001 |
| P002 | 瀬戸内レモンティー | 飲料 | 980 | 爽やかなレモンの香りを楽しめます。 | https://picsum.photos/seed/lemontea/720/480 | https://amanogawaginga.jp/products/p002 |
| P003 | 手織りコットンタオル | 生活雑貨 | 1650 | やわらかな肌触りの国産タオルです。 | https://picsum.photos/seed/towel/720/480 | https://amanogawaginga.jp/products/p003 |
| P004 | 真鍮デスクトレイ | 文具 | 3200 | 小物を美しくまとめるデスクトレイです。 | https://picsum.photos/seed/tray/720/480 | https://amanogawaginga.jp/products/p004 |
| P005 | 国産蜂蜜 | 食品 | 2450 | 季節の花から採れたまろやかな蜂蜜です。 | https://picsum.photos/seed/honey/720/480 | https://amanogawaginga.jp/products/p005 |
| P006 | 木製カードスタンド | 文具 | 750 | 無垢材を使ったシンプルなカード立てです。 | https://picsum.photos/seed/cardstand/720/480 | https://amanogawaginga.jp/products/p006 |
| P007 | 和紅茶ティーバッグ | 飲料 | 1180 | 渋みが少なく甘い香りの和紅茶です。 | https://picsum.photos/seed/blacktea/720/480 | https://amanogawaginga.jp/products/p007 |
| P008 | 帆布ポーチ | 服飾雑貨 | 2100 | 丈夫な帆布で作った収納ポーチです。 | https://picsum.photos/seed/pouch/720/480 | https://amanogawaginga.jp/products/p008 |
| P009 | 陶器のマグカップ | 食器 | 2800 | 手になじむ形の落ち着いたマグカップです。 | https://picsum.photos/seed/mug/720/480 | https://amanogawaginga.jp/products/p009 |
| P010 | スパイスクッキー | 食品 | 860 | 数種類のスパイスを使った焼き菓子です。 | https://picsum.photos/seed/cookie/720/480 | https://amanogawaginga.jp/products/p010 |
| P011 | リネンハンカチ | 服飾雑貨 | 1350 | 使うほどになじむリネン素材です。 | https://picsum.photos/seed/linen/720/480 | https://amanogawaginga.jp/products/p011 |
| P012 | ガラスの一輪挿し | 生活雑貨 | 3600 | 窓辺に似合う小さなガラス花器です。 | https://picsum.photos/seed/vase/720/480 | https://amanogawaginga.jp/products/p012 |
カスタムHTMLコード
<!--
WordPressの「カスタムHTML」ブロックに、このファイルの内容を貼り付けてください。
データ元の切り替え:
- HTML内データ: data-source="html"
- 外部CSV: data-source="csv" data-csv-url="CSVのURL"
data-detail-urlには、個別表示用に作成したWordPress固定ページのURLを指定します。
CSVの1行目の項目名は、各thのdata-keyと一致させてください。
-->
<div class="wpdv" data-wp-dataviewer="" data-source="html" data-csv-url="https://amanogawaginga.jp/wp-content/uploads/products.csv" data-detail-url="https://amanogawaginga.jp/product-detail/" data-detail-key="id" data-detail-param="id" data-page-size="6" data-page-sizes="3,6,12,24">
<div class="wpdv__controls">
<label class="wpdv__search">
<span class="wpdv__label">キーワード検索</span>
<input type="search" data-wpdv-search="" placeholder="商品名・カテゴリーなど">
</label>
<label class="wpdv__page-size">
<span class="wpdv__label">表示件数</span>
<select data-wpdv-page-size=""></select>
</label>
<div class="wpdv__view-switch" role="group" aria-label="表示形式">
<button type="button" data-wpdv-view="list" aria-pressed="true">リスト</button>
<button type="button" data-wpdv-view="tile" aria-pressed="false">タイル</button>
</div>
</div>
<p class="wpdv__status" data-wpdv-status="" aria-live="polite"></p>
<div class="wpdv__content" data-wpdv-content=""></div>
<nav class="wpdv__pagination" data-wpdv-pagination="" aria-label="ページ送り"></nav>
<!--
この非表示テーブルが、列設定とHTMLモードの元データです。
data-type:
string / number / date / image / link
data-sortable:
"false"にすると並べ替え対象外
data-searchable:
"false"にするとキーワード検索対象外
data-tile-role:
タイル表示での役割。image / title / meta / body を指定
-->
<table class="wpdv__source" data-wpdv-source="" hidden="">
<thead>
<tr>
<th data-key="id" data-type="string" data-tile-role="meta">商品ID</th>
<th data-key="name" data-type="string" data-tile-role="title">商品名</th>
<th data-key="category" data-type="string" data-tile-role="meta">カテゴリー</th>
<th data-key="price" data-type="number" data-tile-role="meta">価格</th>
<th data-key="description" data-type="string" data-sortable="false" data-tile-role="body">説明</th>
<th data-key="image" data-type="image" data-sortable="false" data-searchable="false" data-tile-role="image">画像</th>
<th data-key="url" data-type="detail-link" data-sortable="false" data-searchable="false" data-tile-role="meta">詳細</th>
</tr>
</thead>
<tbody>
<tr><td>P001</td><td>有機ブレンドコーヒー</td><td>飲料</td><td>1280</td><td>香り豊かな中深煎りのブレンドです。</td><td>https://picsum.photos/seed/coffee/720/480</td><td>https://amanogawaginga.jp/products/p001</td></tr>
<tr><td>P002</td><td>瀬戸内レモンティー</td><td>飲料</td><td>980</td><td>爽やかなレモンの香りを楽しめます。</td><td>https://picsum.photos/seed/lemontea/720/480</td><td>https://amanogawaginga.jp/products/p002</td></tr>
<tr><td>P003</td><td>手織りコットンタオル</td><td>生活雑貨</td><td>1650</td><td>やわらかな肌触りの国産タオルです。</td><td>https://picsum.photos/seed/towel/720/480</td><td>https://amanogawaginga.jp/products/p003</td></tr>
<tr><td>P004</td><td>真鍮デスクトレイ</td><td>文具</td><td>3200</td><td>小物を美しくまとめるデスクトレイです。</td><td>https://picsum.photos/seed/tray/720/480</td><td>https://amanogawaginga.jp/products/p004</td></tr>
<tr><td>P005</td><td>国産蜂蜜</td><td>食品</td><td>2450</td><td>季節の花から採れたまろやかな蜂蜜です。</td><td>https://picsum.photos/seed/honey/720/480</td><td>https://amanogawaginga.jp/products/p005</td></tr>
<tr><td>P006</td><td>木製カードスタンド</td><td>文具</td><td>750</td><td>無垢材を使ったシンプルなカード立てです。</td><td>https://picsum.photos/seed/cardstand/720/480</td><td>https://amanogawaginga.jp/products/p006</td></tr>
<tr><td>P007</td><td>和紅茶ティーバッグ</td><td>飲料</td><td>1180</td><td>渋みが少なく甘い香りの和紅茶です。</td><td>https://picsum.photos/seed/blacktea/720/480</td><td>https://amanogawaginga.jp/products/p007</td></tr>
<tr><td>P008</td><td>帆布ポーチ</td><td>服飾雑貨</td><td>2100</td><td>丈夫な帆布で作った収納ポーチです。</td><td>https://picsum.photos/seed/pouch/720/480</td><td>https://amanogawaginga.jp/products/p008</td></tr>
<tr><td>P009</td><td>陶器のマグカップ</td><td>食器</td><td>2800</td><td>手になじむ形の落ち着いたマグカップです。</td><td>https://picsum.photos/seed/mug/720/480</td><td>https://amanogawaginga.jp/products/p009</td></tr>
<tr><td>P010</td><td>スパイスクッキー</td><td>食品</td><td>860</td><td>数種類のスパイスを使った焼き菓子です。</td><td>https://picsum.photos/seed/cookie/720/480</td><td>https://amanogawaginga.jp/products/p010</td></tr>
<tr><td>P011</td><td>リネンハンカチ</td><td>服飾雑貨</td><td>1350</td><td>使うほどになじむリネン素材です。</td><td>https://picsum.photos/seed/linen/720/480</td><td>https://amanogawaginga.jp/products/p011</td></tr>
<tr><td>P012</td><td>ガラスの一輪挿し</td><td>生活雑貨</td><td>3600</td><td>窓辺に似合う小さなガラス花器です。</td><td>https://picsum.photos/seed/vase/720/480</td><td>https://amanogawaginga.jp/products/p012</td></tr>
</tbody>
</table>
</div>
<style>
.wpdv {
--wpdv-accent: #176b5d;
--wpdv-accent-soft: #e7f2ef;
--wpdv-border: #d9dfdd;
--wpdv-muted: #63706d;
--wpdv-surface: #fff;
color: #17211f;
font-family: inherit;
}
.wpdv *,
.wpdv *::before,
.wpdv *::after {
box-sizing: border-box;
}
.wpdv__controls {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: end;
margin-bottom: 16px;
padding: 16px;
border: 1px solid var(--wpdv-border);
border-radius: 12px;
background: #f7f9f8;
}
.wpdv__search {
flex: 1 1 260px;
}
.wpdv__page-size {
flex: 0 0 110px;
}
.wpdv__label {
display: block;
margin-bottom: 5px;
color: var(--wpdv-muted);
font-size: 13px;
font-weight: 700;
}
.wpdv input,
.wpdv select,
.wpdv button {
min-height: 42px;
border: 1px solid var(--wpdv-border);
border-radius: 8px;
background: var(--wpdv-surface);
color: inherit;
font: inherit;
}
.wpdv input,
.wpdv select {
width: 100%;
padding: 8px 12px;
}
.wpdv button {
cursor: pointer;
padding: 8px 13px;
}
.wpdv button:hover,
.wpdv button:focus-visible {
border-color: var(--wpdv-accent);
}
.wpdv button:focus-visible,
.wpdv input:focus-visible,
.wpdv select:focus-visible {
outline: 3px solid color-mix(in srgb, var(--wpdv-accent) 25%, transparent);
outline-offset: 2px;
}
.wpdv__view-switch {
display: flex;
gap: 4px;
}
.wpdv__view-switch button[aria-pressed="true"],
.wpdv__pagination button[aria-current="page"] {
border-color: var(--wpdv-accent);
background: var(--wpdv-accent);
color: #fff;
}
.wpdv__status {
margin: 0 0 10px;
color: var(--wpdv-muted);
font-size: 14px;
}
.wpdv__table-wrap {
overflow-x: auto;
border: 1px solid var(--wpdv-border);
border-radius: 12px;
}
.wpdv__table {
width: 100%;
min-width: 760px;
margin: 0;
border-collapse: collapse;
background: var(--wpdv-surface);
}
.wpdv__table th,
.wpdv__table td {
padding: 12px;
border: 0;
border-bottom: 1px solid var(--wpdv-border);
text-align: left;
vertical-align: middle;
}
.wpdv__table tbody tr:last-child td {
border-bottom: 0;
}
.wpdv__table th {
background: #f3f6f5;
white-space: nowrap;
}
.wpdv__sort {
display: inline-flex;
gap: 6px;
align-items: center;
min-height: auto !important;
padding: 2px !important;
border: 0 !important;
border-radius: 3px !important;
background: transparent !important;
font-weight: 700;
}
.wpdv__sort-mark {
min-width: 1em;
color: var(--wpdv-accent);
}
.wpdv__thumb {
display: block;
width: 72px;
height: 54px;
border-radius: 7px;
object-fit: cover;
background: #edf0ef;
}
.wpdv__tiles {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 235px), 1fr));
gap: 16px;
}
.wpdv__card {
overflow: hidden;
border: 1px solid var(--wpdv-border);
border-radius: 14px;
background: var(--wpdv-surface);
box-shadow: 0 5px 18px rgb(30 50 45 / 7%);
}
.wpdv__card-image {
width: 100%;
aspect-ratio: 3 / 2;
object-fit: cover;
background: #edf0ef;
}
.wpdv__card-body {
padding: 15px;
}
.wpdv__card-title {
margin: 0 0 9px;
font-size: 18px;
line-height: 1.4;
}
.wpdv__card-meta,
.wpdv__card-text {
margin: 6px 0 0;
font-size: 14px;
line-height: 1.65;
}
.wpdv__card-meta {
color: var(--wpdv-muted);
}
.wpdv__field-label {
font-weight: 700;
}
.wpdv__link {
color: var(--wpdv-accent);
font-weight: 700;
}
.wpdv__empty,
.wpdv__error {
padding: 28px;
border: 1px dashed var(--wpdv-border);
border-radius: 12px;
text-align: center;
}
.wpdv__error {
color: #a12626;
background: #fff6f6;
}
.wpdv__pagination {
display: flex;
flex-wrap: wrap;
gap: 6px;
justify-content: center;
margin-top: 20px;
}
.wpdv__pagination button {
min-width: 42px;
padding-inline: 10px;
}
.wpdv__pagination button:disabled {
cursor: not-allowed;
opacity: 0.45;
}
@media (max-width: 600px) {
.wpdv__controls {
align-items: stretch;
}
.wpdv__page-size,
.wpdv__view-switch {
flex: 1 1 120px;
}
.wpdv__view-switch button {
flex: 1;
}
}
</style>
<script>
(() => {
"use strict";
const viewerSelector = "[data-wp-dataviewer]";
function parseCsv(text) {
const rows = [];
let row = [];
let field = "";
let quoted = false;
text = text.replace(/^\uFEFF/, "");
for (let i = 0; i < text.length; i += 1) {
const char = text[i];
const next = text[i + 1];
if (char === '"' && quoted && next === '"') {
field += '"';
i += 1;
} else if (char === '"') {
quoted = !quoted;
} else if (char === "," && !quoted) {
row.push(field);
field = "";
} else if ((char === "\n" || char === "\r") && !quoted) {
if (char === "\r" && next === "\n") i += 1;
row.push(field);
if (row.some((value) => value !== "")) rows.push(row);
row = [];
field = "";
} else {
field += char;
}
}
row.push(field);
if (row.some((value) => value !== "")) rows.push(row);
return rows;
}
function normalize(value, type) {
if (type === "number") {
const number = Number(String(value).replace(/[,¥¥円\s]/g, ""));
return Number.isNaN(number) ? Number.NEGATIVE_INFINITY : number;
}
if (type === "date") {
const time = new Date(value).getTime();
return Number.isNaN(time) ? Number.NEGATIVE_INFINITY : time;
}
return String(value ?? "").toLocaleLowerCase("ja");
}
function formatValue(value, type) {
if (type === "number" && value !== "") {
const number = Number(String(value).replace(/[,¥¥円\s]/g, ""));
return Number.isNaN(number) ? value : number.toLocaleString("ja-JP");
}
return value;
}
function makeElement(tag, className, text) {
const element = document.createElement(tag);
if (className) element.className = className;
if (text !== undefined) element.textContent = text;
return element;
}
function safeUrl(value) {
try {
const url = new URL(value, document.baseURI);
return ["http:", "https:"].includes(url.protocol) ? url.href : "";
} catch {
return "";
}
}
function makeDetailUrl(baseUrl, param, id) {
const safeBaseUrl = safeUrl(baseUrl);
if (!safeBaseUrl || !id) return "";
const url = new URL(safeBaseUrl);
url.searchParams.set(param || "id", id);
return url.href;
}
class WordPressDataViewer {
constructor(root) {
this.root = root;
this.sourceTable = root.querySelector("[data-wpdv-source]");
this.content = root.querySelector("[data-wpdv-content]");
this.status = root.querySelector("[data-wpdv-status]");
this.pagination = root.querySelector("[data-wpdv-pagination]");
this.searchInput = root.querySelector("[data-wpdv-search]");
this.pageSizeSelect = root.querySelector("[data-wpdv-page-size]");
this.viewButtons = [...root.querySelectorAll("[data-wpdv-view]")];
this.state = {
rows: [],
keyword: "",
sortKey: null,
sortDirection: "asc",
currentPage: 1,
pageSize: Number(root.dataset.pageSize) || 12,
view: "list"
};
}
async init() {
if (!this.sourceTable || !this.content) {
throw new Error("元データ用tableまたは表示領域がありません。");
}
this.columns = [...this.sourceTable.querySelectorAll("thead th")].map((th, index) => ({
index,
key: th.dataset.key || `column${index + 1}`,
label: th.textContent.trim(),
type: th.dataset.type || "string",
sortable: th.dataset.sortable !== "false",
searchable: th.dataset.searchable !== "false",
tileRole: th.dataset.tileRole || "meta"
}));
this.setupControls();
this.bindEvents();
this.setLoading(true);
if (this.root.dataset.source === "csv") {
this.state.rows = await this.loadCsv(this.root.dataset.csvUrl);
} else {
this.state.rows = this.loadHtml();
}
this.setLoading(false);
this.render();
}
setupControls() {
const sizes = (this.root.dataset.pageSizes || "12,24,48")
.split(",")
.map(Number)
.filter((size) => Number.isInteger(size) && size > 0);
if (!sizes.includes(this.state.pageSize)) sizes.unshift(this.state.pageSize);
[...new Set(sizes)].forEach((size) => {
const option = new Option(`${size}件`, String(size), false, size === this.state.pageSize);
this.pageSizeSelect.add(option);
});
}
bindEvents() {
this.searchInput.addEventListener("input", (event) => {
this.state.keyword = event.target.value.trim().toLocaleLowerCase("ja");
this.state.currentPage = 1;
this.render();
});
this.pageSizeSelect.addEventListener("change", (event) => {
this.state.pageSize = Number(event.target.value);
this.state.currentPage = 1;
this.render();
});
this.viewButtons.forEach((button) => {
button.addEventListener("click", () => {
this.state.view = button.dataset.wpdvView;
this.viewButtons.forEach((item) => {
item.setAttribute("aria-pressed", String(item === button));
});
this.renderContent(this.getPageRows());
});
});
this.content.addEventListener("click", (event) => {
const button = event.target.closest("[data-wpdv-sort]");
if (!button) return;
const key = button.dataset.wpdvSort;
this.state.sortDirection =
this.state.sortKey === key && this.state.sortDirection === "asc" ? "desc" : "asc";
this.state.sortKey = key;
this.state.currentPage = 1;
this.render();
});
this.pagination.addEventListener("click", (event) => {
const button = event.target.closest("[data-wpdv-page]");
if (!button || button.disabled) return;
this.state.currentPage = Number(button.dataset.wpdvPage);
this.render();
this.status.scrollIntoView({ behavior: "smooth", block: "nearest" });
});
}
loadHtml() {
return [...this.sourceTable.querySelectorAll("tbody tr")].map((tr) => {
const cells = [...tr.children];
return Object.fromEntries(
this.columns.map((column) => [column.key, cells[column.index]?.textContent.trim() || ""])
);
});
}
async loadCsv(url) {
if (!url) throw new Error("data-csv-urlにCSVのURLを指定してください。");
const response = await fetch(url, { credentials: "same-origin" });
if (!response.ok) throw new Error(`CSVを取得できませんでした(${response.status})。`);
const csvRows = parseCsv(await response.text());
const headers = csvRows.shift()?.map((header) => header.trim()) || [];
return csvRows.map((values) =>
Object.fromEntries(this.columns.map((column) => [column.key, values[headers.indexOf(column.key)] || ""]))
);
}
setLoading(loading) {
if (loading) {
this.status.textContent = "データを読み込んでいます…";
this.content.setAttribute("aria-busy", "true");
} else {
this.content.removeAttribute("aria-busy");
}
}
getFilteredRows() {
let rows = this.state.rows;
if (this.state.keyword) {
rows = rows.filter((row) =>
this.columns.some(
(column) =>
column.searchable &&
String(row[column.key] ?? "").toLocaleLowerCase("ja").includes(this.state.keyword)
)
);
}
if (this.state.sortKey) {
const column = this.columns.find((item) => item.key === this.state.sortKey);
const direction = this.state.sortDirection === "asc" ? 1 : -1;
rows = [...rows].sort((a, b) => {
const aValue = normalize(a[column.key], column.type);
const bValue = normalize(b[column.key], column.type);
if (typeof aValue === "number") return (aValue - bValue) * direction;
return aValue.localeCompare(bValue, "ja", { numeric: true }) * direction;
});
}
return rows;
}
getPageRows() {
const filtered = this.getFilteredRows();
const totalPages = Math.max(1, Math.ceil(filtered.length / this.state.pageSize));
this.state.currentPage = Math.min(this.state.currentPage, totalPages);
const start = (this.state.currentPage - 1) * this.state.pageSize;
return filtered.slice(start, start + this.state.pageSize);
}
render() {
const filtered = this.getFilteredRows();
const totalPages = Math.max(1, Math.ceil(filtered.length / this.state.pageSize));
this.state.currentPage = Math.min(this.state.currentPage, totalPages);
const start = (this.state.currentPage - 1) * this.state.pageSize;
const pageRows = filtered.slice(start, start + this.state.pageSize);
this.status.textContent = `${filtered.length.toLocaleString("ja-JP")}件中 ${
filtered.length ? start + 1 : 0
}〜${Math.min(start + this.state.pageSize, filtered.length)}件を表示`;
this.renderContent(pageRows);
this.renderPagination(totalPages);
}
renderContent(rows) {
this.content.replaceChildren();
if (!rows.length) {
this.content.append(makeElement("p", "wpdv__empty", "該当するデータがありません。"));
return;
}
if (this.state.view === "tile") {
this.renderTiles(rows);
} else {
this.renderList(rows);
}
}
renderList(rows) {
const wrap = makeElement("div", "wpdv__table-wrap");
const table = makeElement("table", "wpdv__table");
const thead = document.createElement("thead");
const headRow = document.createElement("tr");
this.columns.forEach((column) => {
const th = document.createElement("th");
th.scope = "col";
if (this.state.sortKey === column.key) {
th.setAttribute(
"aria-sort",
this.state.sortDirection === "asc" ? "ascending" : "descending"
);
}
if (column.sortable) {
const button = makeElement("button", "wpdv__sort");
button.type = "button";
button.dataset.wpdvSort = column.key;
button.setAttribute("aria-label", `${column.label}で並べ替え`);
button.append(makeElement("span", "", column.label));
const mark =
this.state.sortKey === column.key
? this.state.sortDirection === "asc"
? "\u25B2"
: "\u25BC"
: "\u2195";
button.append(makeElement("span", "wpdv__sort-mark", mark));
th.append(button);
} else {
th.textContent = column.label;
}
headRow.append(th);
});
thead.append(headRow);
table.append(thead);
const tbody = document.createElement("tbody");
rows.forEach((row) => {
const tr = document.createElement("tr");
this.columns.forEach((column) => {
const td = document.createElement("td");
td.append(this.renderValue(row[column.key], column, row));
tr.append(td);
});
tbody.append(tr);
});
table.append(tbody);
wrap.append(table);
this.content.append(wrap);
}
renderTiles(rows) {
const tiles = makeElement("div", "wpdv__tiles");
const titleColumn = this.columns.find((column) => column.tileRole === "title") || this.columns[0];
const imageColumn = this.columns.find((column) => column.tileRole === "image");
rows.forEach((row) => {
const card = makeElement("article", "wpdv__card");
const imageUrl = imageColumn ? safeUrl(row[imageColumn.key]) : "";
if (imageUrl) {
const image = makeElement("img", "wpdv__card-image");
image.src = imageUrl;
image.alt = row[titleColumn.key] || imageColumn.label;
image.loading = "lazy";
card.append(image);
}
const body = makeElement("div", "wpdv__card-body");
body.append(makeElement("h3", "wpdv__card-title", row[titleColumn.key]));
this.columns
.filter((column) => column !== titleColumn && column !== imageColumn)
.forEach((column) => {
if (!row[column.key]) return;
const line = makeElement(
"p",
column.tileRole === "body" ? "wpdv__card-text" : "wpdv__card-meta"
);
line.append(makeElement("span", "wpdv__field-label", `${column.label}: `));
line.append(this.renderValue(row[column.key], column, row));
body.append(line);
});
card.append(body);
tiles.append(card);
});
this.content.append(tiles);
}
renderValue(value, column, row) {
if (column.type === "image") {
const imageUrl = safeUrl(value);
if (!imageUrl) return document.createTextNode("");
const image = makeElement("img", "wpdv__thumb");
image.src = imageUrl;
image.alt =
row[this.columns.find((item) => item.tileRole === "title")?.key] || column.label;
image.loading = "lazy";
return image;
}
if (column.type === "link" && value) {
const linkUrl = safeUrl(value);
if (!linkUrl) return document.createTextNode("");
const link = makeElement("a", "wpdv__link", "詳細を見る");
link.href = linkUrl;
return link;
}
if (column.type === "detail-link") {
const detailKey = this.root.dataset.detailKey || "id";
const detailUrl = makeDetailUrl(
this.root.dataset.detailUrl,
this.root.dataset.detailParam,
row[detailKey]
);
if (!detailUrl) return document.createTextNode("");
const link = makeElement("a", "wpdv__link", "詳細を見る");
link.href = detailUrl;
return link;
}
return document.createTextNode(formatValue(value, column.type));
}
renderPagination(totalPages) {
this.pagination.replaceChildren();
if (totalPages <= 1) return;
const addButton = (label, page, options = {}) => {
const button = makeElement("button", "", label);
button.type = "button";
button.dataset.wpdvPage = page;
button.disabled = Boolean(options.disabled);
if (options.current) button.setAttribute("aria-current", "page");
if (options.label) button.setAttribute("aria-label", options.label);
this.pagination.append(button);
};
addButton("前へ", this.state.currentPage - 1, {
disabled: this.state.currentPage === 1
});
const start = Math.max(1, this.state.currentPage - 2);
const end = Math.min(totalPages, start + 4);
for (let page = Math.max(1, end - 4); page <= end; page += 1) {
addButton(String(page), page, {
current: page === this.state.currentPage,
label: `${page}ページ目`
});
}
addButton("次へ", this.state.currentPage + 1, {
disabled: this.state.currentPage === totalPages
});
}
}
async function initialize(root) {
if (root.dataset.wpdvInitialized === "true") return;
root.dataset.wpdvInitialized = "true";
try {
await new WordPressDataViewer(root).init();
} catch (error) {
const content = root.querySelector("[data-wpdv-content]");
const message = makeElement("p", "wpdv__error", `表示できませんでした: ${error.message}`);
content?.replaceChildren(message);
console.error(error);
}
}
document.querySelectorAll(viewerSelector).forEach(initialize);
})();
</script>
CSVサンプル
id,name,category,price,description,image,url
P001,有機ブレンドコーヒー,飲料,1280,香り豊かな中深煎りのブレンドです。,https://picsum.photos/seed/coffee/720/480,https://amanogawaginga.jp/products/p001
P002,瀬戸内レモンティー,飲料,980,爽やかなレモンの香りを楽しめます。,https://picsum.photos/seed/lemontea/720/480,https://amanogawaginga.jp/products/p002
P003,手織りコットンタオル,生活雑貨,1650,やわらかな肌触りの国産タオルです。,https://picsum.photos/seed/towel/720/480,https://amanogawaginga.jp/products/p003
P004,真鍮デスクトレイ,文具,3200,小物を美しくまとめるデスクトレイです。,https://picsum.photos/seed/tray/720/480,https://amanogawaginga.jp/products/p004
P005,国産蜂蜜,食品,2450,季節の花から採れたまろやかな蜂蜜です。,https://picsum.photos/seed/honey/720/480,https://amanogawaginga.jp/products/p005
P006,木製カードスタンド,文具,750,無垢材を使ったシンプルなカード立てです。,https://picsum.photos/seed/cardstand/720/480,https://amanogawaginga.jp/products/p006
P007,和紅茶ティーバッグ,飲料,1180,渋みが少なく甘い香りの和紅茶です。,https://picsum.photos/seed/blacktea/720/480,https://amanogawaginga.jp/products/p007
P008,帆布ポーチ,服飾雑貨,2100,丈夫な帆布で作った収納ポーチです。,https://picsum.photos/seed/pouch/720/480,https://amanogawaginga.jp/products/p008
P009,陶器のマグカップ,食器,2800,手になじむ形の落ち着いたマグカップです。,https://picsum.photos/seed/mug/720/480,https://amanogawaginga.jp/products/p009
P010,スパイスクッキー,食品,860,数種類のスパイスを使った焼き菓子です。,https://picsum.photos/seed/cookie/720/480,https://amanogawaginga.jp/products/p010
P011,リネンハンカチ,服飾雑貨,1350,使うほどになじむリネン素材です。,https://picsum.photos/seed/linen/720/480,https://amanogawaginga.jp/products/p011
P012,ガラスの一輪挿し,生活雑貨,3600,窓辺に似合う小さなガラス花器です。,https://picsum.photos/seed/vase/720/480,https://amanogawaginga.jp/products/p012
WordPress データ一覧の使い方
WordPress データ一覧の使い方
構成
・一覧ページ: wordpress-data-viewer.html
・個別ページ: wordpress-data-detail.html
・CSVサンプル: products-sample.csv
1. HTML内データを使う場合
「wordpress-data-viewer.html」の内容をWordPressのカスタムHTMLブロックへ貼り付けます。
先頭付近の設定は次のままにします。
data-source="html"
非表示テーブルのtbody内に、表示したいデータを追加してください。
個別ページにも同じデータを設定してください。
2. 外部CSVを使う場合
CSVファイルをWordPressと同じドメイン内へアップロードします。
「wordpress-data-viewer.html」先頭付近の設定を変更します。
data-source="csv"
data-csv-url="アップロードしたCSVのURL"
CSVの1行目の項目名は、非表示テーブル内の各thにあるdata-keyと一致させます。
CSVモードでは、非表示テーブルのtbody内のデータは使用されません。
一覧ページと個別ページの両方に同じCSVのURLを指定します。
3. 列を変更する場合
非表示テーブルのthead内にあるthを追加・削除します。
data-key CSVの項目名
data-type string / number / date / image / link
data-sortable falseにすると並べ替え不可
data-searchable falseにするとキーワード検索対象外
data-tile-role image / title / meta / body
4. 表示件数を変更する場合
data-page-size="6" 最初に表示する件数
data-page-sizes="3,6,12,24" 選択肢として表示する件数
5. 個別ページを作る場合
WordPressで「商品詳細」などの固定ページを1枚作り、
「wordpress-data-detail.html」の内容をカスタムHTMLブロックへ貼り付けます。
固定ページのURLが次の場合:
一覧ページの設定を次のように変更します。
data-detail-url="https://amanogawaginga.jp/product-detail/"
data-detail-key="id"
data-detail-param="id"
一覧の「詳細を見る」をクリックすると、次の形式で個別ページが開きます。
個別ページ側の設定:
data-id-key="id" IDが入る項目名
data-id-param="id" URLで使うパラメーター名
data-list-url="一覧ページのURL" 「一覧へ戻る」のリンク先
6. 個別ページの表示項目を変更する場合
個別ページ内の非表示テーブルにあるthのdata-detail-roleを変更します。
title ページタイトル
image メイン画像
meta 商品ID・価格などの基本情報
body 本文
link 外部リンクボタン
hidden 個別ページには表示しない
data-prefix・data-suffixで、値の前後へ文字を追加できます。
例:
data-suffix="円"
注意点
・CSVは同じWordPressサイト内に置くのが簡単です。別ドメインの場合は、配信側のCORS設定が必要です。
・WordPressの権限やセキュリティ設定によっては、カスタムHTMLブロック内のscriptタグが削除されます。
その場合は、管理者権限で投稿するか、JavaScript部分をテーマ・子テーマ・コード追加用プラグインへ配置してください。
・WordPressは標準状態でCSVアップロードを許可しない場合があります。その場合はメディア設定または専用プラグインでCSVを許可してください。