#きっかけ
前回書いた記事
【Javascript】多段階アンドゥの実装覚書
のアプリ(?)をさらにバージョンアップさせたので誰かの役にたつかもって書いてみた
#アンドゥだけじゃない
間違えてページ遷移しちゃった場合も取り返せる様にローカルストレージにもチャレンジ!
今度はRedoも入れたいかな…
#コード
前より多機能にしたぶんよりわけがわからないので全文です
###html
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="workSheet.css">
<title>ベージ遷移後の回復?実装</title>
</head>
<body>
<div class="table">
<!-- 区切り文字・開始番号 -->
<form id="punctuationAndStartNum" class="tableRow">
<span class="tableCell textAlignRight">セルの区切りを選択:</span>
<span class="tableCell">
<select id="punctuation">
<option value=" ">タブ区切り</option>
<option value=",">カンマ区切り</option>
<option value=" ">半角空白区切り</option>
</select>
<span class="textAlignRight">開始番号:</span>
<input id="startNum" type="text" size="4" autocomplete="off" value="1">
</span>
</form>
<!-- 区切り文字・開始番号 end -->
<!-- 性別 -->
<form class="tableRow">
<span class="tableCell textAlignRight">性別を選択:</span>
<select id="sex" class="tableCell" autofocus>
<option value="男">男</option>
<option value="女">女</option>
<option value="記載無し">記載無し</option>
<option value="質問項目無し">質問項目無し</option>
</select>
</form>
<!-- 性別 end -->
<!-- 年代 -->
<form class="tableRow">
<span class="tableCell textAlignRight">年代を選択:</span>
<select id="age" class="tableCell">
<option value="20代">20代</option>
<option value="30代">30代</option>
<option value="40代">40代</option>
<option value="50代">50代</option>
<option value="60代">60代</option>
<option value="70代〜">70代〜</option>
<option value="記載無し">記載無し</option>
<option value="質問項目無し">質問項目無し</option>
</select>
</form>
<!-- 年代 end -->
<!-- 知ったきっかけ -->
<form class="tableRow">
<span class="tableCell textAlignRight">知ったきっかけ:</span>
<select id="trigger" class="tableCell">
<option value="公式ウェブサイト">公式ウェブサイト</option>
<option value="Facebook">Facebook</option>
<option value="twitter">twitter</option>
<option value="知人からの紹介">知人からの紹介</option>
<option value="メール">メール</option>
<option value="ブログ">ブログ</option>
<option value="お知らせハガキ">お知らせハガキ</option>
<option value="その他">その他</option>
<option value="記載無し">記載無し</option>
<option value="質問項目無し">質問項目無し</option>
</select>
</form>
<!-- 知ったきっかけ end -->
<!-- その他の理由 -->
<form class="tableRow">
<span class="tableCell textAlignRight">その他の理由:</span>
<input id="other" class="tableCell" type="text" autocomplete="off" placeholder="その他の理由を記入してください">
<input type="text" size="50" autocomplete="off" placeholder="見えないハス" style="display: none;"><!-- 改行入力無効化の為 -->
</form>
<!-- その他の理由 end -->
<!-- 問い合わせ内容 -->
<form class="tableRow">
<span class="tableCell textAlignRight vaTop">問い合わせ内容:</span>
<textarea id="inquiry" class="tableCell" name="name" rows="8" placeholder="問い合わせ内容を記入してください"></textarea>
</form>
<!-- 問い合わせ内容 end -->
<!-- 備考欄 -->
<form class="tableRow">
<span class="tableCell textAlignRight vaTop">備考欄:</span>
<textarea id="nb" class="tableCell" name="name" rows="3" placeholder="備考を記入してください"></textarea>
</form>
<!-- 備考欄 end -->
<!-- 入力・取消・コピー・削除ボタン -->
<form class="tableRow">
<span class="tableCell textAlignRight"></span>
<div class="tableCell">
<input id="inputButton" class="outputButton" type="button" value="入力" onclick="main();">
<input id="deleteButton" class="outputButton" type="button" value="最終行削除" onclick="oneStepBack();">
<input id="excelTableButton" class="outputButton" type="button" value="Excel出力用テーブル表示" onclick="excelTableShowHide()">
</div>
</form>
<!-- 入力・取消・コピー・削除ボタン end -->
</div> <!-- テーブル end -->
<!-- 出力 -->
<div>
<div>
<form id="makedTable">
<table>
<tr>
<th>修正</th>
<th>番号</th>
<th>性別</th>
<th>年代</th>
<th>きっかけ</th>
<th>その他理由</th>
<th>問い合わせ内容</th>
<th>備考</th>
</tr>
</table>
</div>
</form>
<div id="excelTableShowHide" style="display: none;">
<div id="excelTable">
<table>
<tr>
<th>番号</th>
<th>性別</th>
<th>年代</th>
<th>きっかけ</th>
<th>その他理由</th>
<th>問い合わせ内容</th>
<th>備考</th>
</tr>
</table>
</div>
</div>
<div>
<form>
<input class="outputButton" type="button" value="Text Copy" onclick="copyTextToClipboard(fullText);">
<input id="dl-xlsx" class="outputButton" type="button" value="Download XLSX">
<input class="outputButton" type="button" value="完全削除" onclick="removeText();">
<input id="localStorageConfirmationButton" class="outputButton" type="button" value="ローカルストレージから復元" style="display: none;" onclick="localStorageConfirmation();">
</form>
</div>
</div>
<!-- 出力 end -->
<script src="reload.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.9.10/xlsx.full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.3/FileSaver.min.js"></script>
<script src="export-xlsx.js"></script>
<script src="workSheet.js"></script>
</body>
</html>
###css
workSheet.css
@charset "UTF-8";
* {
margin: 5px;
font-size: 15px;
}
span {
white-space: nowrap;
}
span.space {
display: inline-block;
width: 90px;
margin: 0 5px;
padding: 0;
}
input, textarea {
border: solid 1px gray;
border-radius: 2px;
background: #eee;
}
textarea {
width: 423px;
}
#other {
width: 423px;
}
table, tr {
margin: 0;
padding: 0;
border-collapse: collapse;
border-spacing: 0;
}
th, td {
min-width: 2em;
margin: 0;
padding: 5px;
border: solid 1px gray;
text-align: center;
}
#startNum {
text-align: center;
}
#inputButton {
background: #ff9872;
}
#excelTableButton {
width: 199px;
}
.table {
display: table;
}
.tableRow {
display: table-row;
}
.tableCell {
display: table-cell;
box-sizing: border-box;
/* border: solid 1px green; */
}
.textAlignRight {
text-align: right;
font-weight: bold;
}
.vaTop {
padding-top: 7px;
vertical-align: top;
}
.outputButton {
height: 35px;
padding: 5px;
border: solid 1px black;
border-radius: 5px;
font-size: 15px;
}
.radioHead {
padding: 0;
box-sizing: border-box;
}
.radioLabel:first-child {
display: block;
position: relative;
width: 100%;
height: 100%;
margin: 0;
box-sizing: border-box;
content: "";
}
###JS
workSheet.js
const punctuationAndStartNum = document.getElementById('punctuationAndStartNum'); // 区切り文字と開始番号のID
const makedTable = document.getElementById('makedTable'); // テーブル作成場所のID
const makedExcelTable = document.getElementById('excelTable'); // 非表示 Excel出力用テーブルID
const startNum = document.getElementById('startNum'); // 開始番号の格納されたinput ID
// アンケート内容の場所を取得
const sex = document.getElementById('sex'); // 性別
const age = document.getElementById('age'); // 年齢
const trigger = document.getElementById('trigger'); // きっかけ
const other = document.getElementById('other'); // その他理由
const inquiry = document.getElementById('inquiry'); // 質問項目
const nb = document.getElementById('nb'); // 備考
// 初期化
var sexValue = ''; // 性別
var ageValue = ''; // 年齢
var triggerValue = ''; // きっかけ
var otherValue = ''; // その他理由
var inquiryValue = ''; // 質問項目
var inquiryValueDQ = ''; // "質問項目"
var nbValue = ''; // 備考
var nbValueDQ = ''; // "備考"
// 主なボタンのID
const inputButtonId = document.getElementById('inputButton'); // 入力ボタンのID
var inputButton = inputButtonId.value;
const deleteButtonId = document.getElementById('deleteButton'); // 削除ボタンのID
var deleteButton = deleteButtonId.value;
const localStorageConfirmationId = document.getElementById('localStorageConfirmationButton'); // 復元ボタンのID
// カウンターまわり
var count = 0; // か、カウンター 命
var excelNum = 0; // 開始番号固定
var oneStepBackNum = 0; // 削除可能か確認用
var confirmationNum = 0; // タブ変換時のアラート確認用
var fixGNum = 0; // 修正用グローバル変数
// テキスト処理関連
var textArray = [[]]; // 0コピペ用 1テーブル変換用 2編集テーブル用 3Excel出力テーブル用
var fullText = []; // コピペ用のテキスト
var json = ''; // ローカルストレージ保管用文字列
var jsonArray = []; // ローカルストレージ保管用に変換する変数
var jsonNum = 0; // ローカルストレージより取り出した開始番号
// テーブル関連のタグ格納
const tags0 = '<tr><th>番号</th><th>性別</th><th>年代</th><th>きっかけ</th><th>その他理由</th><th>問い合わせ内容</th><th>備考</th></tr>'
const tags1 = '<tr><th>修正</th><th>番号</th><th>性別</th><th>年代</th><th>きっかけ</th><th>その他理由</th><th>問い合わせ内容</th><th>備考</th></tr>'
var tableText = ['<table>' + tags1, , '</table>'];
var excelTable = ['<table class="table-to-export" data-sheet-name="アンケートExcel">' + tags0, , '</table>']
// 読込完了時にローカルストレージを確認しボタンの表示を決める
window.addEventListener('DOMContentLoaded', function() {
let getjson = localStorage.getItem('key');
let obj = JSON.parse(getjson);
if(obj != null) {
localStorageConfirmationId.style.display = 'inline';
if(confirm('ローカルストレージにファイルがあります\n読み込みますか?')) {
localStorageConfirmation();
}
}
});
// 多分主に使われる関数 入力・修正
function main() {
// カウンターまわりのログ確認用
// console.log('main() start');
// console.log('count ' + count);
// console.log('excelNum ' + excelNum);
// console.log('oneStepBackNum ' + oneStepBackNum);
// console.log('confirmationNum ' + confirmationNum);
// console.log('fixGNum ' + fixGNum);
inputButton = inputButtonId.value;
const punctuationId = document.getElementById('punctuation');
const punctuation = punctuationId.value;
if(jsonNum != 0) {
console.log('jsonNum');
}
else {
excelNum = startNum.value * 1;
console.log('startNum');
}
var joined = '';
var joinedExcel = '';
confirmationNum = 0;
sexValue = sex.value;
ageValue = age.value;
triggerValue = trigger.value;
otherValue = other.value.replace(/ /g, ' '); // 半角スペースを全角にする。
inquiryValue = inquiry.value.replace(/ /g, ' '); // 半角スペースを全角にする。
inquiryValueDQ = ('"' + inquiryValue + '"');
nbValue = nb.value.replace(/ /g, ' '); // 半角スペースを全角にする。
nbValueDQ = ('"' + nbValue + '"');
if(triggerValue === 'その他' && otherValue === '') {
alert('その他の理由を入力してください');
other.focus();
return;
} else if(triggerValue != 'その他' && otherValue != '') {
alert('"知ったきっかけ"または\n"その他の理由"をみなおしてください');
other.focus();
return;
}
otherValue = tabCheck(otherValue);
inquiryValueDQ = tabCheck(inquiryValueDQ);
nbValueDQ = tabCheck(nbValueDQ);
inquiryValue = tabCheck(inquiryValue);
nbValue = tabCheck(nbValue)
if(otherValue === false || inquiryValueDQ === false || nbValueDQ === false) {
return;
}
if(inputButton === '入力') {
substitution(count, punctuation);
count++;
rendering();
punctuationAndStartNum.style.display = 'none';
oneStepBackNum = 1;
}
else if(inputButton === '修正') {
substitution(fixGNum, punctuation);
rendering();
inputButtonId.value = '入力';
deleteButtonId.value = '最終行削除';
}
else {
console.log('error');
}
localStorageConfirmationId.style.display = 'none';
} // main end
// 整理代入…
function substitution(subNum, punctuation) {
textArray[subNum] = [];
joined = sexValue + punctuation + ageValue + punctuation + triggerValue + punctuation + otherValue + punctuation + inquiryValueDQ + punctuation + nbValueDQ;
joinedExcel = sexValue + punctuation + ageValue + punctuation + triggerValue + punctuation + otherValue + punctuation + inquiryValue + punctuation + nbValue;
textArray[subNum].push(joined); // 0
textArray[subNum].push(joinedExcel); // 1
textArray[subNum].push(tableMake(textArray, punctuation, subNum, 1)); // 2 ラジオボタン付き
textArray[subNum].push(tableMake(textArray, punctuation, subNum, 0)); // 3
} // substitution end
// テーブル作成・jsonに出力
function rendering() {
tableText[1] = [];
for(let i = (textArray.length - 1); i >= 0; i--) {
tableText[1].push(textArray[i][2]);
}
excelTable[1] = [];
fullText = [];
for(let i = 0; i < textArray.length; i++) {
excelTable[1].push(textArray[i][3]);
fullText.push(textArray[i][0] + '\n');
}
tableText[1] = tableText[1].join('');
excelTable[1] = excelTable[1].join('');
fullText = fullText.join('');
if(textArray.length != 0) {
jsonArray = []
for(let i = 0; i < textArray.length; i++) {
var jsonText = textArray[i][0].replace(/\t/g, '\\t').replace(/\n/g, '\\n').replace(/"/g, "'");
if(i === 0 && i === (textArray.length - 1)) {
jsonArray.push('[["' + jsonText + '"]]');
}
else if(i === 0) {
jsonArray.push('[["' + jsonText + '"],');
}
else if(i === (textArray.length - 1)) {
jsonArray.push('["' + jsonText + '"]]');
}
else {
jsonArray.push('["' + jsonText + '"],');
}
}
json = jsonArray.join('');
if(jsonNum != 0) {
localStorage.setItem('startNum', jsonNum); // 番号保存
}
else {
localStorage.setItem('startNum', startNum.value); // 番号保存
}
localStorage.setItem('key', json); // ローカルストレージ保存
}
else {
localStorage.clear();
}
makedTable.innerHTML = tableText.join('');
makedExcelTable.innerHTML = excelTable.join('');
sex.focus();
other.value = '';
inquiry.value = '';
nb.value = '';
} // rendering end
// 修正ラジヲボタン押し下げ
function fix(fixNum) {
alert('訂正後に修正ボタンを押してね')
fixText = textArray[fixNum][1].split(' ');
sex.value = fixText[0];
age.value = fixText[1];
trigger.value = fixText[2];
other.value = fixText[3];
inquiry.value = fixText[4];
nb.value = fixText[5];
inputButtonId.value = '修正';
deleteButtonId.value = 'キャンセル';
fixGNum = fixNum;
} // fix end
// 最終行削除!
function oneStepBack() {
const oneStepBackButton = deleteButtonId.value;
if(oneStepBackButton === '最終行削除') {
if(oneStepBackNum === 0 && textArray.length <= 1) {
alert('まだ何もしてません');
}
else if(textArray.length > 0) {
count -= 1;
textArray.pop();
rendering();
}
else if(textArray.length === 0) {
alert('取り消しはもうできません');
sex.focus();
}
else {
console.log('error');
}
}
else if(oneStepBackButton === 'キャンセル') {
rendering();
inputButtonId.value = '入力';
deleteButtonId.value = '最終行削除';
}
else {
console.log('error');
}
localStorageConfirmationId.style.display = 'none';
} //oneStepBack end
// タブが入力されてたら
function tabCheck(str) {
var tab = str.indexOf('\t');
if(tab != -1){
str = str.replace(/\t/g, ' ');
if(confirmationNum != 0) {
return str;
}
else {
if(confirm('タブは全角空白に置き換えます') && confirmationNum === 0) {
confirmationNum++;
return str;
}
else {
alert('入力は取り消されました');
return false;
}
}
}
return str;
} // tabCheck end
// json取り出し変換
function localStorageConfirmation() {
try {
var jsonArray = [];
var getJsonNum = localStorage.getItem('startNum');
jsonNum = JSON.parse(getJsonNum);
if(jsonNum === null) {
excelNum = startNum.value * 1;
}
else {
excelNum = jsonNum;
}
console.log('localStorageConfirmation jsonNum ' + jsonNum);
var getjson = localStorage.getItem('key');
var obj = JSON.parse(getjson);
if(obj === null) {
alert('ファイルが存在しません');
// console.log(obj);
}
else {
for(let i = 0; i < obj.length; i++) {
obj[i][0] = obj[i][0].replace(/'/g, '"'); // 0
obj[i].push(obj[i][0].replace(/"/g, '')); // 1
obj[i].push(tableMake(obj, '\t', i, 1)); // 2
obj[i].push(tableMake(obj, '\t', i, 0)) // 3
count++;
}
textArray = obj;
rendering();
punctuationAndStartNum.style.display = 'none';
oneStepBackNum = 1;
}
}
catch(e) {
alert(e);
}
localStorageConfirmationId.style.display = 'none';
} // localStorageConfirmation end
// テーブル作ります
function tableMake(str, punctuation, countNum ,radio) {
str = str[countNum][1].split(punctuation);
for(let i = 0; i < str.length; i++) {
if(i === 0) {
if(radio) {
str[i] = '<tr><td class="radioHead"><label class="radioLabel"><input class="radioButton" type="radio" name="radioButton" onclick="fix(' + countNum + ');"></label></td><td>' + (excelNum + countNum) + '</td><td>' + str[i] + '</td>';
}
else {
str[i] = '<tr><td>' + (excelNum + countNum) + '</td><td>' + str[i] + '</td>';
}
}
else if(i === (str.length - 1)) {
str[i] = '<td>' + str[i] + '</td></tr>';
}
else {
str[i] = '<td>' + str[i] + '</td>';
}
}
str = str.join('');
return str;
} // tableMake end
// コピーボタン
function copyTextToClipboard(textVal){
if(textVal != '') {
// テキストエリアを用意する
var copyFrom = document.createElement("textarea");
// テキストエリアへ値をセット
copyFrom.textContent = textVal;
// bodyタグの要素を取得
var bodyElm = document.getElementsByTagName("body")[0];
// 子要素にテキストエリアを配置
bodyElm.appendChild(copyFrom);
// テキストエリアの値を選択
copyFrom.select();
// コピーコマンド発行
var retVal = document.execCommand('copy');
// 追加テキストエリアを削除
bodyElm.removeChild(copyFrom);
// 処理結果を返却
return retVal;
}
else {
alert('結果が出力されていません');
}
} // copyTextToClipboard end
// ExcelTable表示・非表示
function excelTableShowHide() {
const excelTableShowHideId = document.getElementById('excelTableShowHide');
const excelTableButtonId = document.getElementById('excelTableButton');
if(excelTableButtonId.value === 'Excel出力用テーブル表示') {
excelTableShowHideId.style.display = 'block';
excelTableButtonId.value = 'Excel出力用テーブル非表示';
}
else {
excelTableShowHideId.style.display = 'none';
excelTableButtonId.value = 'Excel出力用テーブル表示';
}
}
// 完全削除
function removeText() {
localStorage.clear();
location.reload();
}
※Excelファイルの出力には下記リンクからjsファイルを作ってください
以上
#最後に
二次元配列仕様でさっぱりしたはずなのに
追加機能でもっと増えてしまった
汚いあれこれですがご勘弁を
コピーはここから拝借しました
JavaScript でテキストをクリップボードへコピーする方法
Excelファイルの出力はこちらから
HTMLのTableをExcelに出力するJavaScript - Qiita
JSONフォーマットはここから
JavaScriptプログラミング講座【JSON について】
addEventListenerはこっち
【JavaScript入門】addEventListener()によるイベント処理の使い方!