LoginSignup
0
1

More than 3 years have passed since last update.

【Javascript】多段階アンドゥの実装覚書2

Posted at

きっかけ

前回書いた記事
【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()によるイベント処理の使い方!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1