

下記のような駅一覧のページでは、jQueryのtoggleClassでboxクラスをクリックするとonboxクラスの追加と削除を繰り返して箱の中に色が付くようになっています。こういったページで「東京と小田原の行の箱だけクリックされて色がついている」という各行の状態を配列にし、LocalStorageなどに保存すれば、リロードしたり次回以降にドキュメントを読み込んだりしたとしてもリセットされることなく前回と同じ状態にすることができるということはわかりました。

しかし、肝心の配列の作成に行き詰ってます。  
空の配列を作って、そこに「東京」や「品川」の行のbox要素がクリックされonboxが追加した時に配列にその行を情報を保存。再度box要素がクリックされonboxが削除されたときにその行の情報を削除する。というイメージなのですが、うまくいきません。  
配列内にonboxのあるid要素を保存したり削除したりしたりする感じなのですが。hasClassでtrueとfalseを返したのものを配列に保存するなど迷走してます。

* * *
[code]

    <!DOCTYPE html>
    <html lang="ja" dir="ltr">
      <head>
        <meta charset="utf-8">
        <style>
        .box{
          margin:0 5px 0 0;
          padding: 10px 1px 10px 30px;
          width: 20px;
          height: 50px;
          border: 0.5px solid #000;
          display: inline;
        }
    
        .onbox{
          margin:0 5px 0 0;
          padding: 10px 1px 10px 30px;
          width: 20px;
          height: 50px;
          border: 0.5px solid #ccc;
          display: inline;
          background: #000;
        }
        </style>
    
        <title>東海道新幹線</title>
      </head>
      <body>
        <div class="wreppar">
        <div class="head">
        <h1>東海道新幹線</h1>
      </div>
    
      <h2>駅一覧</h2>
        <ul>
          <li><div　id="st1" class="box"></div>東京</li>
          <li><div id="st2" class="box"></div>品川</li>
          <li><div class="box"></div>新横浜</li>
          <li><div class="box"></div>小田原</li>
          <li><div class="box"></div>熱海</li>
        </ul>
    
      </div>
      </body>
    </html>
    
[/code]

[code]

    $(function() {
        $('.box').on('click', function() {
            $(this).toggleClass('onbox');
        });
    });
    
[/code]

配列を作成するコード:

[code]

    $('.box').on('click', function() {
      let slc = [];
    
      let p = $('#st1').hasClass('onbox');
      if( p ) {
        slc.shift(p);
        slc.push(p);
      } else {
        slc.shift(p);
        slc.push(p);
      }
    
      let r = $('#st2').hasClass('onbox');
      if( r ) {
        slc.shift(r);
        slc.push(r);
      } else {
        slc.shift(r);
        slc.push(r);
      }
    });
    
[/code]


CC BY-SA 4.0
2019-06-27T04:06:17.690
56130
2019-06-30T11:12:46.923
2019-06-29T04:39:03.327
32986
34882
question
javascript jquery
追加と削除を繰り返す配列について


ひとつの方法として、カスタムデータ属性、もしくは `id` 属性を用いて、各要素に一意な id を割り振っておき、クリックされた要素の id を配列として
localStorage に格納しておく方法があります。

このとき、 **`Storage` オブジェクトの key と value
が文字列のみをサポートしている**ため、配列を文字列形式に変換する必要があることに注意しなければなりません[[1]](https://html.spec.whatwg.org/multipage/webstorage.html#the-
storage-interface)。

> ### 11.2.1 The Storage
> interface[[1]](https://html.spec.whatwg.org/multipage/webstorage.html#the-
> storage-interface)
>
> Each `Storage` object provides access to a list of key/value pairs, which
> are sometimes called items. Keys are strings. Any string (including the
> empty string) is a valid key. Values are similarly strings.

この解決策として、[`JSON.stringify`
メソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)を使い、配列を
JSON 文字列に変換する方法があります。

[code]

    const arr = [1, 2, 3];
    const stringify = JSON.stringify(arr);
    
    console.log(stringify); //=> [1,2,3]
    console.log(JSON.parse(stringify)); //=> Array(3) [ 1, 2, 3 ]
    
[/code]

* * *

以上を踏まえた上で、質問文のコードを見てみると、 **`id`
属性がリスト項目の途中までしか割り振られていない**ことがわかります。そのため、まずはすべてのリスト項目に `id` 属性を割り振ります。

[code]

    <ul>
      <li><div id="st1" class="box"></div>東京</li>
      <li><div id="st2" class="box"></div>品川</li>
      <li><div id="st3" class="box"></div>新横浜</li>
      <li><div id="st4" class="box"></div>小田原</li>
      <li><div id="st5" class="box"></div>熱海</li>
    </ul>
    
[/code]

次に jQuery を見ると、変数 slc に空の配列を代入しています。今回は localStorage に配列が存在する場合、
**localStorage から配列を取得する必要があります** 。そのため、この変数 slc の初期化部分は、以下のように書くことが出来ます。

ここで、配列を JSON 文字列として保存するため、[`JSON.parse`
メソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse)を用いて
**文字列を配列に変換しなければならない** ことに注意してください。

[code]

    $(function() {
      $(".box").on("click", function() {
        $(this).toggleClass("onbox");
        let slc = JSON.parse(localStorage.getItem("clicked_boxes")) || [];
        // 以下のコードは省略
      });
    });
    
[/code]

そして次のコードを見ると、[`shift`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/shift),
[`push`
メソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/push)の引数に対して
[`hasClass` メソッド](https://api.jquery.com/hasClass/)の結果を渡しています。ここではおそらく、「指定した
id の要素に onbox クラスが適用されていれば、配列から id を削除し、そうでなければ配列に id
を追加する」といった処理を行いたいのだと思います。

しかし、[`shift`
メソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/shift)は引数を使用せず、[`hasClass`
メソッド](https://api.jquery.com/hasClass/)は真理値を返します。このため、id の追加と削除が正常に機能せず、
**どの要素がクリックされたのかを判別出来なくなります** 。

そこで、配列内の id の削除には [`findIndex`
メソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex)と
[`splice`
メソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/splice)を使い、[`hasClass`
メソッド](https://api.jquery.com/hasClass/)の返り値は要素が onbox
クラスを持っているかの判定にのみ使用するようにします。ここで、[`findIndex`
メソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex)と
[`splice`
メソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/splice)を使った要素の削除は、以下のように動作します。

[code]

    var arr = [1, 2, 3, 4, 5];
    var index = arr.findIndex(v => v === 3);
    
    console.log(arr); //=> Array(5) [ 1, 2, 3, 4, 5 ]
    console.log(index); //=> 2
    
    arr.splice(index, 1);
    
    console.log(arr); //=> Array(4) [ 1, 2, 4, 5 ]
    
[/code]

また、この処理を行う要素はクリックされた要素だけであるため、`this` キーワードを用いてクリックされた要素のみにこの処理を行います。

> ## The event handler and its
> environment[[2]](https://api.jquery.com/on/#event-handler)
>
> When jQuery calls a handler, the `this` keyword is a reference to the
> element where the event is being delivered; for directly bound events this
> is the element where the event was attached and for delegated events this is
> an element matching `selector`. (Note that `this` may not be equal to
> `event.target` if the event has bubbled from a descendant element.) To
> create a jQuery object from the element so that it can be used with jQuery
> methods, use `$( this )`.

最後に、localStorage に値を保存するために [`setItem`
メソッド](https://developer.mozilla.org/ja/docs/Web/API/Storage/setItem)を使います。ここで、配列を
JSON 文字列に変換するため、 **[`JSON.stringify`
メソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)を使用する必要がある**ことに注意してください。

以上を行ない、ページ読み込み時に localStorage に保存されている id の要素に onbox
クラスを付与するようにすると、最終的なコードは以下のようになります。

[code]

    $(function() {
      var slc = JSON.parse(localStorage.getItem("clicked_boxes")) || [];
    
      $.map(slc, function(box) {
        $("#" + box).addClass("onbox");
      });
    
      $(".box").on("click", function() {
        var slc = JSON.parse(localStorage.getItem("clicked_boxes")) || [];
    
        if ($(this).hasClass("onbox")) {
          var index = slc.findIndex(v => v === $(this).attr("id"));
          slc.splice(index, 1);
        } else {
          slc.push($(this).attr("id"));
        }
    
        localStorage.setItem("clicked_boxes", JSON.stringify(slc));
        $(this).toggleClass("onbox");
      });
    });
[/code]

[code]

    .box {
      margin: 0 5px 0 0;
      padding: 10px 1px 10px 30px;
      width: 20px;
      height: 50px;
      border: 0.5px solid #000;
      display: inline;
    }
    
    .onbox {
      margin: 0 5px 0 0;
      padding: 10px 1px 10px 30px;
      width: 20px;
      height: 50px;
      border: 0.5px solid #ccc;
      display: inline;
      background: #000;
    }
[/code]

[code]

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div class="wreppar">
      <div class="head">
        <h1>東海道新幹線</h1>
      </div>
    
      <h2>駅一覧</h2>
      <ul>
        <li><div id="st1" class="box"></div>東京</li>
        <li><div id="st2" class="box"></div>品川</li>
        <li><div id="st3" class="box"></div>新横浜</li>
        <li><div id="st4" class="box"></div>小田原</li>
        <li><div id="st5" class="box"></div>熱海</li>
      </ul>
    </div>
[/code]

* * *

今回は、クリックされた要素に対して毎回判定処理を行ったうえで、localStorage への id の追加、削除を行いましたが、以下のように onbox
クラスが適用されている要素のみを localStorage に保存する方法でも、質問者さんの実現したいことは行なえると思います。  

[code]

    $(function() {
      var clicked_boxes = JSON.parse(localStorage.getItem("clicked_boxes")) || [];
      $.map(clicked_boxes, function(box) {
        $("[data-station-id='" + box + "']").addClass("onbox");
      });
    
      $(".box").on("click", function() {
        $(this).toggleClass("onbox");
    
        var clicked_boxes = $.map($(".box.onbox"), function(box) {
          return $(box).data("station-id");
        });
    
        localStorage.setItem("clicked_boxes", JSON.stringify(clicked_boxes));
      });
    });
[/code]

[code]

    .box {
      margin: 0 5px 0 0;
      padding: 10px 1px 10px 30px;
      width: 20px;
      height: 50px;
      border: 0.5px solid #000;
      display: inline;
    }
    
    .onbox {
      margin: 0 5px 0 0;
      padding: 10px 1px 10px 30px;
      width: 20px;
      height: 50px;
      border: 0.5px solid #ccc;
      display: inline;
      background: #000;
    }
[/code]

[code]

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    
    <div class="wreppar">
      <div class="head">
        <h1>東海道新幹線</h1>
      </div>
      <h2>駅一覧</h2>
      <ul>
        <li>
          <div class="box" data-station-id="1"></div>東京</li>
        <li>
          <div class="box" data-station-id="2"></div>品川</li>
        <li>
          <div class="box" data-station-id="3"></div>新横浜</li>
        <li>
          <div class="box" data-station-id="4"></div>小田原</li>
        <li>
          <div class="box" data-station-id="5"></div>熱海</li>
      </ul>
    </div>
[/code]


CC BY-SA 4.0
2019-06-27T22:13:37.850
56158
2019-06-27T22:24:18.840
2020-06-17T08:14:45.997
-1
32986
56130
answer


基本的な考え方は既存の通りですが、少し考え方を変えてみました。

  * デザインに影響するtoggle処理は `<input type="checkbox">` を利用する
  * `localStorage` への格納は「JSON化した配列初期化子」を利用する
  * 追加/削除処理は `new Set` を利用する
  * チェックされた駅が「0」の場合、 **`localStrage` のアイテムを削除**する(空配列を格納しない)

[code]

    'use strict';
    function handleChange (event) {
      const input = event.currentTarget, station = input.value;
      const stationList = event.data;
    
      input.checked ? stationList.add(station) : stationList.delete(station);
      stationList.size ? localStorage.setItem('station-list', JSON.stringify(Array.from(stationList))) : localStorage.removeItem('station-list');
    }
    
    function main () {
      const stationList = JSON.parse(localStorage.getItem('station-list')) || [];
    
      jQuery(stationList.map((station) => 'input[type="checkbox"][name="station"][value="' + station + '"]').join()).prop('checked', true);
    
      jQuery('#station-list').on('change', 'input[type="checkbox"][name="station"]', new Set(stationList), handleChange);
    }
    
    main();
[/code]

[code]

    input[type="checkbox"][name="station"] {
      display: none;
    }
    
    input[type="checkbox"][name="station"]+span:before {
      display: inline-block;
      margin: 0 3px 0 0;
      width: 10px;
      height: 10px;
      border: solid 1px black;
      background-color: white;
      content: ""
    }
    
    input[type="checkbox"][name="station"]:checked+span:before {
      background-color: black;
    }
[/code]

[code]

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <h2 id="h-station-list">駅一覧</h2>
    <ul id="station-list">
      <li><label><input type="checkbox" name="station" value="東京" /><span>東京</span></label></li>
      <li><label><input type="checkbox" name="station" value="新横浜" /><span>新横浜</span></label></li>
      <li><label><input type="checkbox" name="station" value="小田原" /><span>小田原</span></label></li>
      <li><label><input type="checkbox" name="station" value="熱海" /><span>熱海</span></label></li>
    </ul>
[/code]


CC BY-SA 4.0
2019-06-30T11:12:46.923
56262
2019-06-30T11:12:46.923
20262
56130
answer