では次のステップとして、セルのドラッグ&ドロップで移動、行・列のソート機能付き CSV エディタ を作ります。
初心者でも理解できるようにシンプルに、かつブラウザだけで動く形にしています。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>CSV ドラッグ&ソート エディタ</title>
<style>
body { font-family: sans-serif; margin: 20px; }
table { border-collapse: collapse; margin-top: 10px; }
td, th { border: 1px solid #ccc; padding: 5px 10px; min-width: 80px; text-align: center; }
td.drag-over { background-color: #ffa; }
td.selected { background-color: #cff; }
input[type="file"], select, button, input[type="text"] { margin: 5px 0; }
</style>
</head>
<body>
<h2>CSV 高機能エディタ(ドラッグ&ソート対応)</h2>
<div id="controls">
<input type="file" id="csvFile" accept=".csv">
区切り文字:
<select id="delimiter">
<option value=",">カンマ (,)</option>
<option value=";">セミコロン (;)</option>
<option value="\t">タブ</option>
</select>
<button id="addRow">行追加</button>
<button id="addCol">列追加</button>
<button id="downloadBtn">CSV ダウンロード</button>
<br>
検索: <input type="text" id="searchText" placeholder="検索文字">
置換: <input type="text" id="replaceText" placeholder="置換文字">
<button id="replaceBtn">置換実行</button>
<button id="sortRow">選択列で行ソート</button>
</div>
<div id="tableContainer"></div>
<script>
let selectedCell = null;
let dragSrcCell = null;
// CSV → 配列
function csvToArray(csvStr, delimiter = ',') {
return csvStr.split('\n').map(line => line.split(delimiter).map(item => item.replace(/^"(.*)"$/, '$1')));
}
// 配列 → CSV
function arrayToCSV(arr, delimiter = ',') {
return arr.map(row =>
row.map(item => (typeof item==='string'&&(item.includes(delimiter)||item.includes('\n'))?`"${item}"`:item)).join(delimiter)
).join('\n');
}
// 配列 → HTML テーブル表示
function renderTable(data) {
const container = document.getElementById('tableContainer');
const table = document.createElement('table');
table.innerHTML = '';
data.forEach((row,i)=>{
const tr = document.createElement('tr');
row.forEach((cell,j)=>{
const td = document.createElement(i===0?'th':'td');
const input = document.createElement('input');
input.value = cell;
input.style.width = '100%';
td.appendChild(input);
// セルクリックで選択
td.addEventListener('click', ()=>{
if(selectedCell) selectedCell.classList.remove('selected');
selectedCell = td;
td.classList.add('selected');
});
// ドラッグ&ドロップ
td.setAttribute('draggable', true);
td.addEventListener('dragstart', e=>{
dragSrcCell = td;
e.dataTransfer.effectAllowed = 'move';
});
td.addEventListener('dragover', e=>{
e.preventDefault();
td.classList.add('drag-over');
});
td.addEventListener('dragleave', e=> td.classList.remove('drag-over'));
td.addEventListener('drop', e=>{
e.preventDefault();
td.classList.remove('drag-over');
if(dragSrcCell && dragSrcCell!==td){
const tmp = dragSrcCell.querySelector('input').value;
dragSrcCell.querySelector('input').value = td.querySelector('input').value;
td.querySelector('input').value = tmp;
}
});
tr.appendChild(td);
});
table.appendChild(tr);
});
container.innerHTML='';
container.appendChild(table);
}
// CSV ファイル読み込み
document.getElementById('csvFile').addEventListener('change', function(e){
const file = e.target.files[0];
if(!file) return;
const reader = new FileReader();
reader.onload = evt=>{
const delim = document.getElementById('delimiter').value;
const data = csvToArray(evt.target.result, delim);
renderTable(data);
};
reader.readAsText(file,'UTF-8');
});
// 行追加
document.getElementById('addRow').addEventListener('click', ()=>{
const table = document.querySelector('#tableContainer table');
if(!table) return alert('テーブルがありません');
const colCount = table.rows[0].cells.length;
const tr = document.createElement('tr');
for(let i=0;i<colCount;i++){
const td = document.createElement('td');
const input = document.createElement('input'); input.style.width='100%';
td.appendChild(input);
td.setAttribute('draggable', true);
td.addEventListener('click', ()=>{ if(selectedCell) selectedCell.classList.remove('selected'); selectedCell=td; td.classList.add('selected'); });
tr.appendChild(td);
}
table.appendChild(tr);
});
// 列追加
document.getElementById('addCol').addEventListener('click', ()=>{
const table = document.querySelector('#tableContainer table');
if(!table) return alert('テーブルがありません');
Array.from(table.rows).forEach((tr,i)=>{
const td = document.createElement(i===0?'th':'td');
const input = document.createElement('input'); input.style.width='100%';
td.appendChild(input);
td.setAttribute('draggable', true);
td.addEventListener('click', ()=>{ if(selectedCell) selectedCell.classList.remove('selected'); selectedCell=td; td.classList.add('selected'); });
tr.appendChild(td);
});
});
// CSV ダウンロード
document.getElementById('downloadBtn').addEventListener('click', ()=>{
const table = document.querySelector('#tableContainer table');
if(!table) return alert('CSV を読み込んでください');
const delim = document.getElementById('delimiter').value;
const data = Array.from(table.rows).map(tr=>Array.from(tr.cells).map(td=>td.querySelector('input').value));
const csvStr = arrayToCSV(data, delim);
const blob = new Blob([csvStr], { type:'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a'); a.href=url; a.download='edited.csv'; a.click(); URL.revokeObjectURL(url);
});
// 置換
document.getElementById('replaceBtn').addEventListener('click', ()=>{
const search = document.getElementById('searchText').value;
const replace = document.getElementById('replaceText').value;
if(!search) return;
const table = document.querySelector('#tableContainer table');
Array.from(table.rows).forEach(tr=>Array.from(tr.cells).forEach(td=>{
const input = td.querySelector('input');
if(input.value.includes(search)) input.value = input.value.split(search).join(replace);
}));
});
// 選択列で行ソート
document.getElementById('sortRow').addEventListener('click', ()=>{
if(!selectedCell) return alert('セルを選択してください');
const colIndex = Array.from(selectedCell.parentNode.children).indexOf(selectedCell);
const table = document.querySelector('#tableContainer table');
const rows = Array.from(table.rows).slice(1); // ヘッダー以外
rows.sort((a,b)=>{
const aVal = a.cells[colIndex].querySelector('input').value;
const bVal = b.cells[colIndex].querySelector('input').value;
return aVal.localeCompare(bVal, undefined, {numeric:true});
});
rows.forEach(r=>table.appendChild(r));
});
</script>
</body>
</html>
HTML新機能まとめ
- セルのドラッグ&ドロップ移動
- 同じテーブル内のセルを自由に入れ替え可能
- 行ソート
- 選択した列を基準にして、文字列・数値を自然順で並べ替え
- 既存の編集機能も保持
- CSV 読み込み・ダウンロード
- 行・列追加
- 検索・置換
ポイント
- ドラッグ&ドロップは
draggable属性 +dragstart/dragover/dropイベントで実装 - 行ソートは選択セルの列インデックスを使い、
localeCompareで文字列・数値を自然順ソート - 既存の CSV 編集・置換機能と組み合わせて、簡単に高機能エディタになる
