見出し画像

「英語学習にプログラミングを活用!」パート❺ - 覚えた単語はスキップする - JavaScriptのLocalStorageを利用して知らない単語だけトレーニング

JavaScriptでデータを保存する3つの方法

前回のパート4では、すでに組み込まれたTOEICとSATの単語リストによるトレーニングに加えて、自分で好きな単語を指定してトレーニングできる「マイリスト」の機能を追加しました。

自分がトレーニングしたい単語を指定して例文表示を開始できます。実際のデモはこのツイートを参考にしてみてください。

LocalStorageの使用で気を付けること

LocalStorageはHTML5で導入されたとても便利な機能です。今回のように各ユーザーが作った単語リストを長くローカルに保存することができ、それによってマイリスト機能が実現しているわけです。

ただし、今回のように単語リストなどプライバシーやセキュリティーに直接関係のないデータは問題ありませんが、外に漏れてはいけないデータは保存してはいけません。これは実際にLocalStorageを使ったコーディングをやればすぐに理解できるはずです。JavaScriptからキーの値がわかれば、それに対する保存データが簡単に取得できました。今回は"1000MyTermList"というユニークなキー名にして、他のアプリで簡単に使われないようにしましたが、それでもこのキーがわかれば別のサイトのJavaScripでも同じデータを取得できてしまいます。つまりデータを保護するメカニズムが一切ないのです。簡単だけに注意も必要ということです。

JavaScriptでデータを一時保存する方法には次の3つがあります。

❶ Cookieを使う
❷ Sessionを使う
❸ LocalStorageを使う

これらのメカニズムを利用する上での注意点についてはこの記事などを参考にしてみてください。

今回はデータ保護の必要がない文字列を保存するケースなので、簡単で便利なLocalStorageを利用します。

覚えた単語を表示させないようにする。でも・・・

では今回の追加機能ですが、トレーニングをやっていて、すでに覚えた単語はスキップしたいものです。マイリストの場合はリストから削除すればよいのですが、すでにサーバー側でリストが決まっているTOEICとSATについてはそうはいきません。

そこで、すでに覚えた単語は「除外リスト」というものを作っておいて、そこに入っている単語はスキップするようにします。方法はもう予想がついていると思いますが、前回と同様にLocalStorageを利用します。マイリストと同様に「マイ除外リスト」を作ればよいのです。

ところが問題が一つ。トレーニングを開始してランダムに単語が選ばれて、それがWeb APIの呼び出し結果で返ってきた段階で除外リストと照合すると、このサーバー側で例文を引っ張ってくる時間が無駄になるという点は理解できますか?

すでに気が付いていると思いますが、例文の取得には時間がかかります。サーバー側ではランダムに選択された単語をもとにWebスクレイピングで例文を取ってくるのですからプロセス時間が必要です。せっかく時間をかけて単語を選んで、例文を取ってきて、クライアントのブラウザに結果を返すと、その段階で「あっ、もう覚えた単語だからスキップして」と言われてたらどうでしょう。完全に処理時間がムダになりますよね。

サーバー側で”単語スキップ”をしてもらう

そこで今回はWeb APIにこの単語スキップ機能を入れました。つまり、Web APIでTOEICやSATの例文リストを要求する際に、除外リストもいっしょに送って「このリストにある単語は選ばないでね」という指示を出せるようにしました

具体的にはtraining.jsGetSamples関数の中にあるWeb APIの呼び出し部分で、単語リストの値について、TOEIFCかSATというパラメータの後に除外単語を{word1, word2, word3 ... word n}という文字列で渡すと、それが除外リストとして解釈され、そこに入っている単語はパスします。

次のコードの「それ以外は除外リストを渡す」のコメント以下のコードをみてください(コードはまた後程詳しく解説します)。

       else if(listtype == "TOEIC" || listtype == "SAT") {
           //除外単語リストを渡す
           var exclusions = localStorage.getItem ("1000ExclusionList");
           //全表示がチェックされていたら単語除外リストは送らない
           if (document.getElementById("show-allterms").checked == true || exclusions == null || exclusions == "" || exclusions == undefined) {
               webUrl = 
                 "https://magicwandapi.azurewebsites.net/api/eitango/"
                 + listtype;
           }
           //それ以外は除外リストを渡す
           else {
               webUrl = 
                  "https://magicwandapi.azurewebsites.net/api/eitango/"
                  + listtype + "/" + exclusions;
           }
       }

このWeb APIの拡張機能を利用して除外リストを指定できるようにしてみましょう。

スキップされた単語がわかるようにする

今回拡張でちょっと難しいのは、スキップだけしていくと「何がスキップされたかわからない」という事態になります。覚えた単語はどんどんと除外リストに入れていく一方で、どの単語を除外リストに入れたかをユーザーに見せる機能も必要となります。そこで、次のように追加機能をデザインしてみます。

❶ 覚えた単語はチェックボックスをクリックするだけで除外リストに追加できる
❷ 除外リストの単語がわかるように「スキップしない」モード(全表示モード)を追加する
❸ その際に、覚えた単語は背景が赤(通常は緑)で表示されるようにする

まず表示された単語の前にチェックボックスが表示され、すでに覚えた場合はここをチェックするだけで次回からは選択されないようになります。

画像1

そして、「覚えた単語も表示」といのが全表示モードで、除外リストに入った単語が出てくると背景が赤で表示されます。もちろんそこでチェックをはずして除外リストから取り出すこともできます。まだ覚えに自信がない場合、アンチェックすれば”復活”させることもできます。

画像2

ステップ1:まずは2つのチェックボックスを追加する

それでは簡単なUIの変更から始めてみます。index.htmlに「全表示モード」のチェックボックスを追加します。

「訳文文も表示する」のチェックボックスはこれでした。

<label><input class="nes-checkbox" type="checkbox" id="show-trans" /><span>訳文も表示する</span></label>

この下に新たなチェックボックスを追加します。

 <input class="nes-checkbox" type="checkbox" id="show-trans" /><span>訳文も表示する</span></label> &nbsp;
 <input class="exc-checkbox" type="checkbox" id="show-allterms" /><span>覚えた単語も表示</span>

二つのチェックボックスの間に &nbsp;(non-breaking space)というスペースを入れてちょっと見やすくしています。ここにidとしてshow-alltermsという値を設定しています。

さて、もう一方のチェックボックスは単語表示のところで除外リストに追加するためのものです。ただ、これはHTMLに静的に入っているものではなく、トレーニングをスタートさせて単語が選ばれた段階で動的に表示されます。つまり変更はtraining.jsの中で行います。

単語の表示に関するコードはGetSamples関数の次の箇所でした。ここはWeb APIが呼び出されて結果が出てきた時に 呼ばれる関数のコードでした。

oRequest.onload = function () {
   if (oRequest.status >= 200 && oRequest.status < 400) {
       SamplesJSON = JSON.parse(this.response);
       document.getElementById("term-box").innerHTML = SamplesJSON.term;
       DisplaySample();
       document.getElementById("run-train").innerHTML = "次を表示";
   }
};

単語表示する箇所は"term-box"というDIV要素。そこに例文データのJsonオブジェクトから"term"という文字列を取ってきてinnerHTMLとして入れていました。この単語を挿入する行に、チェックボックスのHTMLを入れるコードを追加します。変更は次の1行。

oRequest.onload = function () {
   if (oRequest.status >= 200 && oRequest.status < 400) {
       SamplesJSON = JSON.parse(this.response);
       //除外単語指定のためのチェックボックスを追加する
       document.getElementById("term-box").innerHTML 
         = "<input type=checkbox id=exclude-check onchange='AddExcludeTerm(\"" + SamplesJSON.term + "\");'>&nbsp;</input>" + SamplesJSON.term;
       DisplaySample();
       document.getElementById("run-train").innerHTML = "次を表示";           }
};
//除外単語指定のためのチェックボックスを追加する
document.getElementById("term-box").innerHTML
= "<input type=checkbox id=exclude-check onchange='AddExcludeTerm(\"" + SamplesJSON.term + "\");'>&nbsp;</input>" + SamplesJSON.term;

ここでチェックボックスのHTML文字列を入れます。そしてチェックボックスにチェックが入ったり、はずされたりするイベントとしてonchangeを指定。そこにこれから追加するAddExcludeTermという関数をセットします。これでチェックに変更があるとこの関数で除外リストに入れたり削除したりする操作を書き込みます。

ステップ2:除外リストの追加・削除をする関数AddExcludeTermを作る

ここは前回やったマイリストのlocalStorageへの保存と同じなのですが、一点だけ大きな違いがあります。まずはAddExcludeTerm関数をご覧ください。

//単語チェックボックスで除外リストの追加処理
function AddExcludeTerm (excludeterm) {
   //localStorageから除外リストを取得
   var excludeTerms = localStorage.getItem("1000ExclusionList");
   //もし除外リストが存在しなかったらまずは単語を追加する
   if (excludeTerms == null) {
       excludeTerms = excludeterm + ",";
   }
   //もし除外リストにあったらそこからはずす(アンチェック)
   else if (excludeTerms != null && excludeTerms.includes(excludeterm + ",")) {
       excludeTerms = excludeTerms.replace(excludeterm + ",", "");
   }
   //もし除外リストになかっらた新規追加(チェック)
   else {
       excludeTerms += excludeterm + ",";
   }
   //追加・除外したリストをローカルに保存
   localStorage.setItem("1000ExclusionList", excludeTerms);
}

単語のところにあるチェックボックスがクリックされると、この関数が単語(excludeterm)とともに呼び出されます。まずはローカルに保存されている除外リスト、1000ExclusionListというデータを取得します。注意が必要はのはここです。一番最初に除外リストを追加する際にはこのデータはまだ存在していません。最初の1単語を入れた瞬間にこのキーと値ができるため、一番最初はnullとして存在します(つまり「ない」ということ)。この状態でgetItemを呼び出すとエラーになります。ここに気を付けないといけません。

ロジックとして、

● (IF) もし指定したキーで除外リストがなかったら、これは一番最初のデータ処理なので、単語はそのまま入れる
● (IF ELSE) もし除外リストはあるものの、渡された単語がリストに存在していたら、それは「リストからはずす」ということ
● (ELSE) もし渡された単語が除外リストになければ、それは「リストに追加」ということ

となります。これで「チェックを付ける」=「除外リストに追加」「チェックをはずす」=「除外リストから削除」という操作になります。

この関数をtraining.jsの最後に追加したら、いったんすべて保存し、ブラウザでチェックしてみてください。localStorageのデータの変化はコンソールを使えば簡単にチェックできます。ブラウザ内の右クリックから、開発(検証)ツールを開いたらConsoleタブを開いてください。そこでlocalStorage.getIterm ("1000ExclusionList")と直接書いてEnterを押せば、中身が一発でわかります。下の例では、最初にlocalStorageを見るとnullになっていますが、スタートを押して出てきたwindshield(皆さんのブラウザでは別の単語が出てくるかもしれません)の単語にチェックを入れると除外リストに入っていることが確認できます。

画像3

ちなみに除外リストをすべてクリアしたい場合はlocalStorage.clear()と入れればOKです。ただし!! これをするとすべてのlocalStorageのデータが消えます。どんなアプリから保存されたものでもすべて消えます。もちろんマイリストのデータがあった場合もそれも消えてしまいます。これでlocalStorageの危険性がよくわかったと思います。自分のためにコーディングをやっている分には問題ありませんが、通常のアプリ開発で利用する場合は十分に注意が必要です。除外リストの中身だけ空にしたい場合には、localStorage.removeItem("1000ExclusionList")を使ってください。

画像6

ステップ3:除外リストの単語はスキップされるようにWeb APIを呼び出す

これで除外リストのコントロール(追加と削除)ができたので、次にWeb APIの呼び込みで除外リストを渡すコードを書いてみます。

まずはWeb APIで呼び出すエンドポイントの設定箇所からみてみます。training.jsGetSamples関数の中で次のコードをみてください。

        //ここからWeb APIの呼び出し
       var webUrl;
       //MYLISTの場合
       if (listtype == "MYLIST") {
           var myterms = document.getElementById("mylist").value;
           webUrl = "https://magicwandapi.azurewebsites.net/api/eitango/MYTERMS/" + myterms;
       }
       //TOEICかSATの場合
       else if(listtype == "TOEIC" || listtype == "SAT") {
           webUrl = "https://magicwandapi.azurewebsites.net/api/eitango/"  + listtype;
       }

ここのTOEICとSATの場合のコードを変更します。これまでは選ばれた単語帳に応じてTOEICかSATというパラメータを渡していましたが、これからは除外リストがある場合はそれも渡します。やり方は単純でTOEICの後のパラメータにカンマで区切られた単語リスト文字列を指定するだけです。

https://magicwandapi.azurewebsites.net/api/eitango/TOEIC/wrd1,word2.....

コードは次のようになります。

       //ここからWeb APIの呼び出し
       var webUrl;
       //MYLISTの場合
       if (listtype == "MYLIST") {
           var myterms = document.getElementById("mylist").value;
           webUrl = "https://magicwandapi.azurewebsites.net/api/eitango/MYTERMS/" + myterms;
       }
       //TOEICかSATAの場合
       else if(listtype == "TOEIC" || listtype == "SAT") {
           //除外単語リストを渡す
           var exclusions = localStorage.getItem ("1000ExclusionList");
           //全表示がチェックされていたら単語除外リストは送らない
           if (document.getElementById("show-allterms").checked == true || exclusions == null || exclusions == "" || exclusions == undefined) {
               webUrl = "https://magicwandapi.azurewebsites.net/api/eitango/"  + listtype;
           }
           //それ以外は除外リストを渡す
           else {
               webUrl = "https://magicwandapi.azurewebsites.net/api/eitango/"  + listtype + "/" + exclusions;
           }
       }

TOEICとSATのケースのコードを拡張しています。まずlocalStorageから除外リストを取得します。そして、デフォルトでは全表示モードはオフなので、elseの条件である「除外リストを渡す」ためのURLとなります。もし全表示モードがチェックされていたらスキップはしないということなので、これまで通りTOEICかSATだけを渡すURLにします。

確かにこのコード変更だけで除外リストにある単語(つまりチェックされていった単語)は選ばれなくなります。でも「よくわからない」ですよね。たくさんある単語の中でいくつも指定してやらないと「表示されなくなった」という確認がなかなかできないわけです(繰り返しますが、実際にやってみるとこの問題がよくわかります)。ちなみにもし全部除外リストに入れるともう単語は出てこなくなります。ここまでいかないと機能しているかどうかの確信が持てないでしょう。

そこで、スキップ機能をオフにして、除外リストの単語が出てきたら特別な表示をしてすぐにわかるようにします。具体的には、チェックされた単語がもう一度出てきたらチェックマークが入った状態で表示し、さらに単語表示の背景を赤にしてみます。

ステップ4:全表示モードによって除外単語を可視化する

先に単語ボックスの中で除外チェックボックスを追加しましたが、同時に次の条件で表示変更をします。

● もし「全表示モード」がオンになっていたら、
     ⇒ 除外語が表示されたら、チェックボックスにチェックを入れる
     ⇒ さらにボックスの背景色を赤にする

これがコード変更です。

//除外単語指定のためのチェックボックスを追加する
 document.getElementById("term-box").innerHTML 
    = "<input type=checkbox id=exclude-check onchange='AddExcludeTerm(\"" + SamplesJSON.term + "\");'>&nbsp;</input>" + SamplesJSON.term;
//もし全表示だったらチェック状態と赤の背景で表示
if (document.getElementById("show-allterms").checked == true) {
    var exclusions = localStorage.getItem('1000ExclusionList');
    if (exclusions != null && exclusions.includes(SamplesJSON.term + ",")) {
        document.getElementById("exclude-check").checked = true;
        document.getElementById("term-box").style.backgroundColor = "darkred";
    }
}

これによって全表示モードで除外単語が出てくると赤で表示されるようになります。これも実際にやってみてください。「覚えた単語も表示」をチェックした上で、出てきた単語にどんどんとチェックを入れていくと、いつか除外単語が選択さるはずです。そこで次のような表示に変わります。

画像4

ステップ5:バグ修正

これでメインの機能は完成ですが、バグが一つ。全表示モードで除外単語が出てきて単語ボックスの背景が赤になるのはよいのですが、そこから「次の単語」をクリックすると背景色が赤になったままになります。

画像5

これはよろしくないので、単語ボックスにデータが表示される場合は必ず緑で初期化するようにしますGetSamples関数の冒頭で、「サンプル文章をロード中・・・」というメッセージ表示のところで背景色を緑にする一行を加えてください。

const GetSamples = function (listtype) {    
   //最初の時だけサンプル文章データの取得を行う
   if (SampleIndex == 0) {
       //ボックスを緑にして、表示中のメッセージを入れる
       document.getElementById("term-box").style.backgroundColor = "darkslategrey";
       document.getElementById("term-box").innerHTML = "サンプル文章をロードしています・・・";
       //ここからWeb APIの呼び出し

これで除外単語が出てきて単語ボックスが赤に変更された後でも、「次を表示」を押すと緑に戻るようになります。

全コードのおさらい(パート4からの変更)


では今回変更した箇所を含め、index.htmlとtraining.jsをすべて示しておきます。これまでのコード変更で問題があればここからすべてをコピペしてみてください。

ここから先は

9,995字

¥ 100

この記事が気に入ったらサポートをしてみませんか?