24
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Google Apps ScriptによるクイズWebアプリを作って英語学習する

Last updated at Posted at 2018-04-26

背景

 自分用のクイズWebアプリを作成したかった。(目的は、TOEIC対策”金フレ”本の復習用)
 アプリの要件は以下2点。

  • 通勤時に手軽にできる(外出先・スマホからでも、アクセス可能であること)
  • 問題(+答え)追加が簡単(外出先・スマホからでも、データ追記できること)

 調べていると、まさに望むアプリを作られている先駆者様を発見しました。
 Blog kon様 - 勉強用Google Apps Script (Quiz Script)

 

 ここから、GAS(Google Apps Script)を利用し、Googleスプレッドシートから問題・答えを取得・出題するWebアプリを目指そうと決めました。
 まず、kon様のコードをほぼコピペさせて頂き、自分用クイズWebアプリができました。

 しかし、2018年4月現在、コードの2つのクラスがGoogleによって非推奨とされていました。

 アプリ自体はよく出来ており現在動作もしますが、このまま非推奨クラス入りのアプリを使い続けていて、試験直前に突然動かなくなる等は避けたいな、と考えました。

目的

 kon様のクイズWebアプリを、リファクタリングさせていただきます。(Google非推奨クラス利用を回避する)
 また、私と同じくkon様のWebアプリを基にして、今後自分用クイズWebアプリをコーディングされる方のために、この資料が参考になればと思い、本記事を作成しました。

結論

 リファクタリング成功しました。

 ご参考に、リファクタリング前(kon様コードのほぼコピペ)は以下。

内容

 コードは以下の通り。

 以下2つのファイルをスプレッドシートスクリプトエディタ上に配置し、ウェブアプリケーションとして導入してください。(方法は、GASによるWebAppについて"もっと"入門向けに書いてみる をご参考に)
 
 SpreadsheetApp.openByUrl(~)には、データ取得先のスプレッドシートURLをコピペしてください。

index.html(ブラウザ側)
<html>
  <head>
    <title>Quiz Web App</title>
    <script>
    
    // ボタンがクリックされたとき呼び出されるハンドラ
    function onbtnclick(){
      
      // html読み取り
      var answer = document.getElementById("answer");
      var btn = document.getElementById("btn");
      
      if(answer.style.visibility == "hidden"){
        // 答えが表示されていないので、答えを表示しボタンを「次の問題」に
        btn.innerHTML = "次の問題";
        answer.style.visibility = "visible";
      } else {
        // 答えが表示されているので、問題・答えを取得して答えを非表示にしボタンを「答えを見る」に
        new_quiz();
        btn.innerHTML = "答えを見る";
        answer.style.visibility = "hidden";
      }
    }
    
    // サーバ側スクリプトnew_quiz_sv()が実行成功したとき呼び出されるハンドラ
    function onSuccess (res){

      // html読み取り
      var quiz = document.getElementById("quiz");
      var answer = document.getElementById("answer");
        
      // 問題のセット
      quiz.innerHTML = res[0];
      // 回答のセット
      answer.innerHTML = res[1];
      answer.style.visibility = "hidden";
        
    }
    
    // サーバサイド関数を稼働させて、問題・答えを取得する関数
    function new_quiz() {
      google.script.run.withSuccessHandler(onSuccess).new_quiz_sv();
    }
    
    </script>
  </head>
  <body>
    <table style="width: 100%;" cellspacing="100" cellpadding="0">
      <tbody>
        <tr>
          <td style="vertical-align: top;" align="left">
            <div id="quiz" style="font-size: 300%;"><br></div>
          </td>
        </tr>
        <tr>
          <td style="vertical-align: top;" align="left">
            <button type="button" id="btn" style="width: 100%; height: 200%; color: white; background: rgb(80, 184, 216) none repeat scroll 0% 0%; font-size: 300%;" onclick="onbtnclick()">答えを見る</button>
          </td>
        </tr>
        <tr>
          <td style="vertical-align: top;" align="left">
            <div id="answer" style="font-size: 300%;"></div>
          </td>
        </tr>
      </tbody>
    </table>
    <script>
    
    // 最初にHTMLが読み込まれたときに問題・答えを設定する
    new_quiz();
    
    </script>
  </body>
</html>
main.gs(サーバ側)
// GASが呼び出されたときに実行。HTMLを表示する
function doGet(e) {
  var app = HtmlService.createHtmlOutputFromFile("index")
  return app;
}

// スプレッドシート処理関数
function new_quiz_sv()
{
  const count_max = 10; // 最大出題回数
  
  // スプレッドシート処理
  var sheet = 
    SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxxxx/')
    .getSheetByName('List');
  var max_r = sheet.getLastRow() - 1;
  
  // 最大出題回数を満たした問題は出題しない
  do {
    // 全行からランダムに1行選択
    var r = Math.floor(Math.random() * max_r) + 2; 
  
    // 問題文、回答文、出題回数の取得
    var text_quiz   = sheet.getRange("B" + r).getValue().replace(/\n/g,"<br/>") + "<br/>" + 
                      sheet.getRange("C" + r).getValue().replace(/\n/g,"<br/>");
    var text_answer = sheet.getRange("D" + r).getValue();
    var count_q     = sheet.getRange("E" + r).getValue();
  } while (count_q >= count_max)
  
  // 出題回数を1増加
  sheet.getRange("E" + r).setValue(count_q + 1);
  
  return [text_quiz, text_answer];
  
}

 なおスプレッドシートは、以下の内容を要求します。

  1. シート名は "List"
  2. 2行目からデータ開始
  3. B列に問題(英文)
  4. C列に問題(日文)
  5. D列に答え
  6. E列はこれまでの出題回数が入力される

考え方

 Googleの指示通り、UiAppクラスの代わりに、HtmlServiceクラスを利用しました。
 HtmlServiceクラスはhtmlファイルをブラウザにロードするためだけのクラスで、UiAppクラスでできていたDOM作成・操作ができないため、ただクラスを置換するだけではうまくいきません。
 **DOM操作はhtmlファイル内(ブラウザ側)**で。**htmlファイル読込みおよびGoogleスプレッドシートからのデータ取得はGAS(サーバ側)**で、と処理を分離させる必要があると考えました。

つまづいた点

 ブラウザからのサーバ側スクリプトの実行および同期処理でつまづきました。

 ブラウザからサーバ側スクリプトを実行するために、google.script.runクラスがあります。これを利用し、ブラウザとGASサーバ間でデータ(問題・答え)の受け渡しをさせます。
 メソッドはwithSuccessHandler(Function)を使います。理由は後述します。

 コードは以下の様になります。
 サーバ側関数new_quiz_svの実行が完了したら、その戻り値が、ブラウザ側のコールバック関数onSuccessの引数として実行される流れになります。
 onSuccess(new_quiz_sv()); のイメージです。

ブラウザ側からサーバ側スクリプトを実行する
<script>
  google.script.run.withSuccessHandler(onSuccess).new_quiz_sv();
</script>

 withSuccessHandlerメソッドを使わなければならない理由は、同期処理を行うためです。
 これが無いと、スプレッドシートからの情報取得前にブラウザ側で描画を実行しようとするため、結果何も表示が変わりません。
 同期処理・コールバック関数については、「参考文献」のリンクをご覧ください。

考察

  1. 元々UiAppクラスは、GASでGUIを簡単に作成できたらしいGUIビルダーのために用意されたクラスであったのだと推測します。GUIビルダーが廃止となったのに伴い、UiAppクラスも非推奨・廃止予定となったのかな?
  2. 問題・答えの追加が結構しんどい。書籍を見ながら手打ちするので、PCからでも10問追加当たり3分強程かかります。スマホからの入力は非現実的。いい方法は無いものでしょうか。。
  3. これで勉強をやってみてますが、学習効果はもの凄くありそう。自分の間違った問題のみがランダムで出題され、しかも隙間時間にさくさくテストできるので、嫌でも覚えられます。英語のボキャブラリー・フレーズの引き出しが広がってく感じがします。

参考文献

  1. Blog kon様 - 勉強用Google Apps Script (Quiz Script)
  2. Google Apps Scriptプログラミング 中級編
  3. JavaScript中級者への道【5. コールバック関数】
24
25
5

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
24
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?