Maple
(もみじ)
1
標準機能の絞り込みやURL内のクエリだと
「(条件1 AND 条件2)OR 条件3」のような検索はできません。
このような検索をするには、複数のレコードを取得するの
query
パラメーターに条件を指定して、取得したレコードを
カスタマイズビューに表示することになります。
しかし、カスタマイズービューは作り込まないと
このように全く装飾されていない表になり
- インライン編集
- 一覧画面からのレコード削除
- フィールド名をクリックした列でソート
- ページネーション
- 1ページあたりのレコード表示件数設定
- 絞り込み結果のレコード件数表示
といった標準機能が使えなくなります。
そこで、これらの機能を持ち、CSSで見た目も整えられていて
複雑な条件での検索もできるビューのサンプルを作りました。
おまけで標準機能ではできない漢字仮名1字の検索に対応した
文字を打ち込むだけで結果が出る検索ボックスも付けています。
「いいね!」 3
Maple
(もみじ)
2
HTML
一覧の設定画面でカスタマイズを選択した後で入力するHTML。
なお「ページネーションを表示する」のチェックは外します。
<div id="record-container"></div>
CSS
#record-container {
padding: 10px;
background-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 3px;
margin: 10px 0;
}
#record-container table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
#record-container th,
#record-container td {
padding: 10px 15px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
font-size: 14px;
}
#record-container thead th {
background-color: #f5f5f5;
font-weight: bold;
border-top: 2px solid #999999;
}
#record-container tbody tr:last-child td {
border-bottom: 2px solid #999999;
}
#record-container tr:hover {
background-color: #f9f9f9;
}
#record-container td {
background-color: #fff;
}
#record-container td,
#record-container th {
word-wrap: break-word;
overflow-wrap: break-word;
}
#record-container td a {
text-decoration: none;
color: #0072bc;
}
#record-container td a:hover {
text-decoration: underline;
}
#record-container button {
padding: 5px 10px;
margin: 2px;
background-color: #f1f1f1;
border: 1px solid #e0e0e0;
border-radius: 3px;
cursor: pointer;
}
#record-container button:hover {
background-color: #0072bc;
color: white;
border-color: #0072bc;
}
#record-container button:active {
background-color: #005a8e;
}
Maple
(もみじ)
3
JavaScript
kintone REST API Client の CDN を、このコードより上に配置する必要があります。
(() => {
'use strict';
let allRecords = []; // 全レコード格納用配列
let currentPage = 1; // 現在のページ番号を初期化
let searchQuery = ''; // 検索ボックスの絞り込み条件
let sortDirection = {}; // ソート条件
const client = new KintoneRestAPIClient(); // kintone REST API Client のインスタンス
// 1ページあたりの表示件数をローカルストレージから取得(取得できない場合は20件にする)
let recordsPerPage = parseInt(localStorage.getItem('customRecordsPerPage'), 10) || 20;
// レコード一覧画面でカスタマイズビューが選択されたら処理開始
kintone.events.on('app.record.index.show', async (event) => {
const recordContainer = document.getElementById('record-container'); // レコード表示用の要素
if (!recordContainer) {
console.warn('record-container が見つかりません。');
return event;
}
try {
const allResp = await client.record.getAllRecords({ app: kintone.app.getId() });
allRecords = allResp; // レコード取得結果を保存
// レコード番号で降順にソート
allRecords.sort((a, b) => Number(b['レコード番号'].value) - Number(a['レコード番号'].value));
currentPage = 1; // 初期ページを1ページ目にする
renderTable(currentPage); // カスタマイズビュー描画関数を呼び出す
} catch (error) {
console.error('レコード取得エラー:', error);
}
return event;
});
// レコードとページネーションの描画
const renderTable = (page) => {
// 条件に一致するレコードをフィルタ
const conditionFiltered = allRecords.filter(record =>
(record['文字列1行A'].value === '条件A' && record['文字列1行B'].value === '条件B') ||
record['文字列1行C'].value === '条件C'
);
// 検索ボックスの絞り込み条件でさらにフィルタ
const filteredRecords = conditionFiltered.filter(record =>
record['文字列1行A'].value.includes(searchQuery) ||
record['文字列1行B'].value.includes(searchQuery) ||
record['文字列1行C'].value.includes(searchQuery)
);
const start = (page - 1) * recordsPerPage;
const end = start + recordsPerPage;
const pageRecords = filteredRecords.slice(start, end); // 表示対象のレコードを抽出
// ページネーションの要素を生成
const generatePagination = (page, totalPages, position) => {
let html = `<div style="margin:${position === 'top' ? '0 0 10px' : '10px 0'}; text-align:center;">`;
if (page > 1) {
html += `<button id="prevPage-${position}">◀ 前へ</button>`;
}
html += `<span> ${page} / ${totalPages} </span>`;
if (page < totalPages) {
html += `<button id="nextPage-${position}">次へ ▶</button>`;
}
html += `</div>`;
return html;
};
const totalPages = Math.ceil(filteredRecords.length / recordsPerPage);
let html = '';
// レコードの上に、表示中のレコードの範囲、検索結果の件数、検索ボックス、1ページあたりの表示件数設定、ページネーションを配置
html += `<div style="margin-bottom:10px;">
表示中: ${start + 1} - ${Math.min(end, filteredRecords.length)}(全 ${filteredRecords.length} 件)
</div>`;
html += `<input type="text" id="searchBox" placeholder="検索..." style="margin-bottom: 10px;" value="${searchQuery}" />
<select id="recordsPerPage" style="margin-left: 10px;">
<option value="20" ${recordsPerPage === 20 ? 'selected' : ''}>20件</option>
<option value="40" ${recordsPerPage === 40 ? 'selected' : ''}>40件</option>
<option value="60" ${recordsPerPage === 60 ? 'selected' : ''}>60件</option>
<option value="80" ${recordsPerPage === 80 ? 'selected' : ''}>80件</option>
<option value="100" ${recordsPerPage === 100 ? 'selected' : ''}>100件</option>
</select>`;
html += generatePagination(page, totalPages, 'top'); // レコードの上に配置するページネーション
// レコードのヘッダの内容
html += `<table border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<th>詳細</th>
<th class="sortable" data-field="文字列1行A">文字列1行A <span class="sort-icon" id="sort-文字列1行A"></span></th>
<th class="sortable" data-field="文字列1行B">文字列1行B <span class="sort-icon" id="sort-文字列1行B"></span></th>
<th class="sortable" data-field="文字列1行C">文字列1行C <span class="sort-icon" id="sort-文字列1行C"></span></th>
<th>操作</th>
</tr>
</thead>
<tbody>`;
// 各レコード行の生成
pageRecords.forEach(record => {
const recordId = record['レコード番号'].value;
const recordUrl = `${location.origin}/k/${kintone.app.getId()}/show#record=${recordId}`;
html += `<tr data-record-id="${recordId}">
<td><a href="${recordUrl}" target="_blank">🔗</a></td>
<td>${record['文字列1行A'].value}</td>
<td>${record['文字列1行B'].value}</td>
<td>${record['文字列1行C'].value}</td>
<td>
<button class="edit-button">編集</button>
<button class="save-button" style="display:none;">保存</button>
<button class="cancel-button" style="display:none;">キャンセル</button>
<button class="delete-button">削除</button>
</td>
</tr>`;
});
html += `</tbody></table>`;
html += generatePagination(page, totalPages, 'bottom'); // レコードの下に配置するページネーション
// DOMにHTMLを挿入
document.getElementById('record-container').innerHTML = html;
// ページネーションのボタンにクリックイベントを設定
const setPaginationHandler = (id, handler) => {
const btn = document.getElementById(id);
if (btn) btn.onclick = handler;
};
setPaginationHandler('prevPage-top', () => renderTable(--currentPage));
setPaginationHandler('nextPage-top', () => renderTable(++currentPage));
setPaginationHandler('prevPage-bottom', () => renderTable(--currentPage));
setPaginationHandler('nextPage-bottom', () => renderTable(++currentPage));
// 1ページあたりの表示件数を変更したときの処理
document.getElementById('recordsPerPage').onchange = (e) => {
recordsPerPage = parseInt(e.target.value, 10);
localStorage.setItem('customRecordsPerPage', recordsPerPage);
renderTable(1);
};
// 検索ボックスの入力処理
let isComposing = false;
const searchBox = document.getElementById('searchBox');
if (searchBox) {
searchBox.value = searchQuery;
searchBox.addEventListener('compositionstart', () => { isComposing = true; });
searchBox.addEventListener('compositionend', (e) => {
isComposing = false;
searchQuery = e.target.value;
renderTable(1);
});
searchBox.addEventListener('input', (e) => {
if (isComposing) return;
const pos = e.target.selectionStart;
searchQuery = e.target.value;
renderTable(1);
setTimeout(() => {
const newBox = document.getElementById('searchBox');
if (newBox) {
newBox.focus();
newBox.setSelectionRange(pos, pos);
}
}, 0);
});
}
setupRowHandlers();
setupSortableColumns();
};
// ソート可能な列のヘッダにクリックイベントを設定
const setupSortableColumns = () => {
document.querySelectorAll('.sortable').forEach(header => {
const field = header.dataset.field;
const icon = document.getElementById(`sort-${field}`);
let isAsc = sortDirection[field] === 'asc';
header.onclick = () => {
isAsc = isAsc === undefined ? false : !isAsc;
sortDirection = {}; // ソート状態をリセット
sortDirection[field] = isAsc ? 'asc' : 'desc';
const sortedRecords = [...allRecords].sort((a, b) => {
const valueA = a[field].value;
const valueB = b[field].value;
if (valueA > valueB) return isAsc ? 1 : -1;
if (valueA < valueB) return isAsc ? -1 : 1;
return 0;
});
allRecords = sortedRecords;
renderTable(currentPage);
};
// ソートアイコンの表示を制御
if (sortDirection[field] !== undefined) {
updateSortIcon(icon, sortDirection[field] === 'asc');
} else {
icon.innerHTML = '';
}
});
};
// ソートアイコンを ▲ か ▼ に更新
const updateSortIcon = (icon, isAsc) => {
if (icon) icon.innerHTML = isAsc ? '▲' : '▼';
};
// 各行の編集、保存、キャンセル、削除ボタンの動作設定
const setupRowHandlers = () => {
document.querySelectorAll('tr').forEach(row => {
const recordId = row.getAttribute('data-record-id');
if (!recordId) return;
const editBtn = row.querySelector('.edit-button');
const saveBtn = row.querySelector('.save-button');
const cancelBtn = row.querySelector('.cancel-button');
const deleteBtn = row.querySelector('.delete-button');
const editableCells = Array.from(row.querySelectorAll('td')).slice(1, 4); // 編集対象のセル
// インライン編集に切替
editBtn.onclick = () => {
editableCells.forEach(cell => {
const value = cell.textContent;
const input = document.createElement('input');
input.value = value;
input.className = 'editable-input';
input.setAttribute('data-field-code', cell.dataset.fieldCode || '');
cell.setAttribute('data-original-value', value);
cell.innerHTML = '';
cell.appendChild(input);
});
editBtn.style.display = 'none';
saveBtn.style.display = 'inline-block';
cancelBtn.style.display = 'inline-block';
deleteBtn.style.display = 'none';
};
// 編集をキャンセル
cancelBtn.onclick = () => {
editableCells.forEach(cell => {
cell.textContent = cell.getAttribute('data-original-value');
});
editBtn.style.display = 'inline-block';
saveBtn.style.display = 'none';
cancelBtn.style.display = 'none';
deleteBtn.style.display = 'inline-block';
};
// 保存処理
saveBtn.onclick = async () => {
const updatedRecord = {};
editableCells.forEach((cell, index) => {
const input = cell.querySelector('input');
const fieldCode = ['文字列1行A', '文字列1行B', '文字列1行C'][index];
updatedRecord[fieldCode] = { value: input.value };
});
try {
await client.record.updateRecord({
app: kintone.app.getId(),
id: recordId,
record: updatedRecord
});
editableCells.forEach(cell => {
const input = cell.querySelector('input');
cell.textContent = input.value;
});
editBtn.style.display = 'inline-block';
saveBtn.style.display = 'none';
cancelBtn.style.display = 'none';
deleteBtn.style.display = 'inline-block';
} catch (e) {
console.error('更新エラー:', e);
alert('レコードの更新に失敗しました。');
}
};
// 削除処理
deleteBtn.onclick = async () => {
if (!confirm(`レコードID ${recordId} を削除してよろしいですか?`)) return;
try {
await client.record.deleteRecords({
app: kintone.app.getId(),
ids: [recordId]
});
allRecords = allRecords.filter(r => r['レコード番号'].value !== recordId);
renderTable(currentPage);
} catch (e) {
console.error('削除エラー:', e);
alert('削除に失敗しました');
}
};
});
};
})();
「いいね!」 1
system
(system)
クローズされました:
4
このトピックはベストアンサーに選ばれた返信から 3 日が経過したので自動的にクローズされました。新たに返信することはできません。