【無料GASアプリ】暗記カード型出題アプリ

無料公開アプリ

どうもcoto.です。

GASの練習がてら作成したwebアプリコードを無料公開します。GASのwebアプリではどんなことができるんだろうというサンプルとしても見ていただければ。

第1弾は暗記カード型出題アプリ。

簡単な動作説明

これは記憶系の勉強を補助するアプリです。スプレッドシートに問題と解答の一覧を入れるだけでオリジナル問題集アプリとして使えます。

高校や大学の定期テスト対策はもちろん、資格勉強、商品知識などある程度覚えなければならないことって社会に出てからも結構ありますよね。

でもニッチな分野で既存アプリや問題集がない、少しだけだからお金は使いたくない、暗記カードでは作って満足してしまう、という場合にオススメ。

利用環境

PC / タブレット / スマートフォン のいずれも利用可。(スマホでの利用を想定して作成したためPCでは文字が大きすぎて見えづらいかもしれません)

GASを利用したWebアプリのためインターネット接続が必要です。

基本的な使い方

①出題カテゴリーを選び、問題スタートボタンを押す

問題カテゴリーを選択して問題スタートボタンを押す

②表示された問題を見る(問題はランダムに10問ずつ出題されます)

問題がランダムに10問出題される

③解答を思い浮かべるor紙などに書いたら解答ボタンを押す

解答ボタンを押すと解答が表示される

の3工程で問題解答を繰り返し行うことができます。
解答は1問ごとに閲覧できるので自分のペースで勉強できます。

さらに、苦手な問題には「苦手」にチェックを入れて「結果を反映」ボタンを押すと、ページ下の苦手克服問題としてチャレンジできます。

また、簡単すぎる問題やあまり適さない問題は「出題除外」にチェックを入れて「結果を反映」ボタンを押すと、次回から出題されなくなります。

デモ版リンク

ともかく実際に使ってみるのが一番早いのでとりあえずデモ版をお試しください。

アプリ

スプレッドシート 

※どなたでもアプリ操作ができるように編集可能にしておりますが、デモ版自体の改変は行わないでください。出題アプリとして使用する場合はご自身のGアカウント内にスプレッドシートを作成し、アプリ化してご利用ください。
※デモ版使用時、ログイン情報・個人情報などは取得しておりませんのでご安心ください。

コード

このアプリの動作にはスプレッドシートに紐づけた以下3つのコードファイルが必要です。

コード.gs

//▼スプレッドシートを変更したらここのIDを変える▼

const ssID = "17ODA_C_5r0Iuv9Yd6ie9tPInKH7p6fnaa5JNtJi4eBI";

//▲スプレッドシートを変更したらここのIDを変える▲

const ss         = SpreadsheetApp.openById(ssID);       //スプレッドシートIDを指定
const sheetDB    = ss.getSheetByName("問題一覧");        //シート”問題一覧”を開く
var rowDB        = sheetDB.getLastRow();          //シートの最終行を取得
var head         = sheetDB.getRange("B2:H2").getValues();       //見出しデータ取得
var data         = sheetDB.getRange("B3:H"+rowDB).getValues();  //全データを取得


//▼html開く▼
function doGet(e) {
  let page = e.parameter.page;
  if (!page) {
    page = 'index';
  }
  var htmlOutput = HtmlService.createTemplateFromFile(page).evaluate();
  htmlOutput
   .setTitle('【デモ】暗記カード型出題アプリ')
   .setFaviconUrl('https://drive.google.com/uc?id=1WaQGKdN2b1BuSHvw0ybyodG0QcY7mxqs&.png');

  return htmlOutput
}

//▼html間でのページ遷移▼
function getAppUrl() {
  return ScriptApp.getService().getUrl();
}


//▼カテゴリーリストを取得▼
function category() {

  var catar = sheetDB.getRange("C3:C" + rowDB).getValues();  //カテゴリーを取得
  catar = Array.prototype.concat.apply([], catar);  //catarが2次元配列なので1次元配列に変換
  catar = catar.filter(function(value, index, self){ return self.indexOf(value) === index;});  //重複削除
  return catar;
}



//▼問題を表示▼
function qa(category){
      var list = [];
      for (var i = 0; i < data.length; i++) { 
         if(data[i][1] == category &&  !data[i][2]){  //カテゴリー一致かつ出題列False
              list.push(data[i]);
         }
      } 
      var max = list.length-1;
      var ar = [];
      if(list.length<10){
        var html = '<div class = "ques">';
        for (var i = 0; i < list.length; i++) {
           var j = i + 1;
           html += '<center><div class="qbox"><span class="box-title">第' + j + '問</span><p>' + list[i][4];
           html += '</p></div><input type="button" value="解答" onclick="view(' + j;
           html += ')" class="answer"/>&nbsp;苦手:<input type="checkbox"  name="nigate" value="'+ list[i][0] + '"/>';
           html += '&nbsp;&nbsp;出題除外:<input type="checkbox"  name="range" value="'+ list[i][0] + '"/>';
           html += '</center>' + '<br><div class="abox" id="A' + j + '" style = "display:none;">';
           html += head[0][5] + ':' + list[i][5] + '<br>';
           html += head[0][6] + ':'  + list[i][6] + '</div><br>';
        }  
      }else{
         while (ar.length<10) {      
            var x = Math.floor(Math.random() * (max + 1)) ;  //ランダムな数字取得
            if(!ar.includes(x)){
              ar.push(x);                                   //重複していなければ配列に追加
            }
         } 

         var arX = [];
         for(var i = 0; i < 10; i++){  //↑で作ったランダムの数字から問題用の情報を取得
           arX.push(list[ar[i]]);
         }

        var html = '<div class = "ques">';

        for (var i = 0; i < 10; i++) {
           var j = i + 1;
           html += '<center><div class="qbox"><span class="box-title">第' + j + '問</span><p>' + arX[i][4];
           html += '</p></div><input type="button" value="解答" onclick="view(' + j;
           html += ')" class="answer"/>&nbsp;苦手:<input type="checkbox"  name="nigate" value="'+ arX[i][0] + '"/>';
           html += '&nbsp;&nbsp;出題除外:<input type="checkbox"  name="range" value="'+ arX[i][0] + '"/>';
           html += '</center>' + '<br><div class="abox" id="A' + j + '" style = "display:none;">';
           html += head[0][5] + ':' + arX[i][5] + '<br>';
           html += head[0][6] + ':'  + arX[i][6] + '</div><br>';
        }  
      }
    html += '</div>';
    return html    
}


//▼苦手問題を表示▼
function qa2(category){
      var list = [];
      for (var i = 0; i < data.length; i++) { 
         if(data[i][1] == category &&  !data[i][2] && data[i][3]){  //カテゴリー一致かつ出題列Falseかつ苦手True
              list.push(data[i]);
         }
      } 
      var max = list.length-1;
      var arX = [];
      var html = '<div class = "ques">現在の苦手個数:' + list.length;
      if(list.length<10){
        for (var i = 0; i < list.length; i++) {
           var j = i + 1;
           html += '<center><div class="qbox"><span class="box-title">第' + j + '問</span><p>' + list[i][4];
           html += '</p></div><input type="button" value="解答" onclick="view2(' + j;
           html += ')" class="answer"/>&nbsp;苦手:<input type="checkbox"  name="nigate2" value="'+ list[i][0] + '"/>';
           html += '</center>' + '<br><div class="abox" id="B' + j + '" style = "display:none;">';
           html += head[0][5] + ':' + list[i][5] + '<br>';
           html += head[0][6] + ':'  + list[i][6] + '</div><br>';

        }  
      }else{

        var ar = [];

        while (ar.length<10) {      
           var x = Math.floor(Math.random() * (max + 1)) ;  //ランダムな数字取得
             if(!ar.includes(x)){
               ar.push(x);                                   //重複していなければ配列に追加
             }
        } 

        var arX = [];
        for(var i = 0; i < 10; i++){  //↑で作ったランダムの数字から問題用の情報を取得
           arX.push(list[ar[i]]);
        }

        for (var i = 0; i < 10; i++) {
           var j = i + 1;
           html += '<center><div class="qbox"><span class="box-title">第' + j + '問</span><p>' + arX[i][4];
           html += '</p></div><input type="button" value="解答" onclick="view2(' + j;
           html += ')" class="answer"/>&nbsp;苦手:<input type="checkbox"  name="nigate2" value="'+ arX[i][0] + '"/>';
           html += '</center>' + '<br><div class="abox" id="B' + j + '" style = "display:none;">';
           html += head[0][5] + ':' + arX[i][5] + '<br>';
           html += head[0][6] + ':'  + arX[i][6] + '</div><br>';
        }  
      }
       html += '</div>';
      return html
}



//▼苦手チェックを記録▼
function InsertRes(nigar, ranar){
  
  for (var i = 0; i < nigar.length; i++) { 
     for (var j = 0; j < data.length; j++) { 
       if(data[j][0] == nigar[i][0] ){  //問題IDが一致する行を検索して苦手状況を変更
           var row = j + 3;
           sheetDB.getRange("E"+ row).setValue(nigar[i][1]);  //一致した問題の行に結果を挿入
       }
     }    
  }

  for (var i = 0; i < ranar.length; i++) { 
     for (var j = 0; j < data.length; j++) { 
       if(data[j][0] == ranar[i][0] ){  //問題IDが一致する行を検索して苦手状況を変更
           var row = j + 3;
           sheetDB.getRange("D"+ row).setValue(ranar[i][1]);  //一致した問題の行に結果を挿入
       }
     }    
  }
    
  return '<center>更新しました</center>'
}

function InsertRes2(ar){
  
  for (var i = 0; i < ar.length; i++) { 
     for (var j = 0; j < data.length; j++) { 
       if(data[j][0] == ar[i][0] ){  //問題IDが一致する行を検索して苦手状況を変更
           var row = j + 3;
           sheetDB.getRange("E"+ row).setValue(ar[i][1]);  //一致した問題の行に結果を挿入
       }
     }    
  }
    
  return '<center>更新しました</center>'
}

//Copyright © 2022 コトミルIT All Rights Reserved. 

index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <?!= HtmlService.createHtmlOutputFromFile('css').getContent(); ?>

    <script type="text/javascript">  

  //▼categoryに投げて処理が終わったらcatDropに返す▼
  google.script.run.withSuccessHandler(catDrop).category();
  google.script.run.withSuccessHandler(catDrop2).category();

   function catDrop(catar){  

      var html =  "<center><select title='カテゴリーを選択する' id='category' name='cate' class='drop'>";
      for(var i = 0;i<catar.length;i++){         //カテゴリーをドロップダウンにいれる
        html += "<option>" + catar[i] + "</option>"
      }
  
       html += '</select> <input type="button" value="問題スタート" onclick="startBtn()" class = "start"/></center>';
    
      //プルダウンメニューを設置する
       document.getElementById("categorydrop").innerHTML = html;  //以下のbody内のcategorydropにここで作ったhtmlを追記 
   }

   function catDrop2(catar){  

      var html =  "<center><select title='カテゴリーを選択する' id='category2' name='cate2' class='drop'>";
      for(var i = 0;i<catar.length;i++){         //カテゴリーをドロップダウンにいれる
        html += "<option>" + catar[i] + "</option>"
      }
  
       html += '</select> <input type="button" value="苦手問題スタート" onclick="startBtn2()" class = "start"/></center>';
    
      //プルダウンメニューを設置する
       document.getElementById("categorydrop2").innerHTML = html;  //以下のbody内のcategorydropにここで作ったhtmlを追記 
   }  
    </script>
  </head>
  <body>
     <h1>【デモ】暗記カード型出題アプリ</h1>  
    <form>
    <div id="categorydrop">
    </div>
    </form>

    <div id="question">
    </div>

    <div id="comment">
    </div>

    <div id="result">
      <br><center><input type="button" value="結果を反映" onclick="resultBtn()" class = "start"/></center>
    </div>

    <div id="reload" style="text-align: right" class="reload">
       <a href="<?= getAppUrl() ?>?page=index"><img src="https://drive.google.com/uc?id=1Jcwqg3ZOsTfGalfxI2wECwaXGj3iGe-K&.png" alt=""></a>
    </div>
<script>
  //▼問題スタートボタン動作制御▼

   function startBtn(){
      var category = document.getElementById("category").value;  //ドロップダウンで選んだ値を取得

      google.script.run.withSuccessHandler(question).qa(category);  //情報を挿入関数に送る 
    }

   function question(html){
      document.getElementById('question').innerHTML = html;   //body内のquestionにここで作ったhtmlを追記 

    }

   function view(i){    //解答の表示・非表示切替
      var num   = 'A' + i;
      var divId = document.getElementById(num);

      if(divId.style.display=="block"){
	       divId.style.display ="none";
	    }else{
		     divId.style.display ="block";
	    }
  }


  //▼結果を反映ボタン動作制御▼
   function resultBtn(){
      var nigate = document.getElementsByName("nigate");  //苦手チェックの値を取得
      var nigar  = [];

      for (let i = 0; i < nigate.length; i++){ //チェック付いた問題のvalueを取得して配列に加える
        if(nigate[i].checked){    
        var nigatear = [nigate[i].value, true]
		 	    nigar.push(nigatear);
		   }else{
        var nigatear = [nigate[i].value, false]
		 	    nigar.push(nigatear);
      }
	  }

      var range  = document.getElementsByName("range");   //出題範囲の値を取得
      var ranar  = [];
        for (let i = 0; i < range.length; i++){ //チェック付いた問題のvalueを取得して配列に加える
        if(range[i].checked){    
        var rangear = [range[i].value, true]
		 	    ranar.push(rangear);
		   }else{
        var rangear = [range[i].value, false]
		 	    ranar.push(rangear);
      }
	  }

      google.script.run.withSuccessHandler(Comm).InsertRes(nigar, ranar);  //情報を挿入関数に送る 
    }  

   function Comm(text){
      document.getElementById('comment').innerHTML = text;   //body内のquestionにここで作ったhtmlを追記 
  } 
  

</script>



     <h1>苦手克服</h1>    
    <form>
    <div id="categorydrop2">
    </div>
    </form>

    <div id="question2">
    </div>

    <div id="comment2">
    </div>  

    <div id="result2">
      <br><center><input type="button" value="結果を反映" onclick="resultBtn2()" class = "start"/></center>
    </div>


    <div id="reload" style="text-align: right" class="reload">
       <a href="<?= getAppUrl() ?>?page=index"><img src="https://drive.google.com/uc?id=1Jcwqg3ZOsTfGalfxI2wECwaXGj3iGe-K&.png" alt=""></a>
    </div>

<script>
  //▼苦手問題スタート動作制御▼
   function startBtn2(){
      var category = document.getElementById("category2").value;  //ドロップダウンで選んだ値を取得

      google.script.run.withSuccessHandler(question2).qa2(category);  //情報を挿入関数に送る 
    }

   function question2(html){
      document.getElementById('question2').innerHTML = html;   //body内のquestion2にここで作ったhtmlを追記 

    }

   function view2(i){
      var num   = 'B' + i;
      var divId = document.getElementById(num);

      if(divId.style.display=="block"){
	       divId.style.display ="none";
	    }else{
		     divId.style.display ="block";
	    }
  }

  //▼結果を反映ボタン動作制御▼
   function resultBtn2(){
      var nigate = document.getElementsByName("nigate2");  //ドロップダウンで選んだ値を取得
      var ar = [];

      for (let i = 0; i < nigate.length; i++){ //チェック付いた問題のvalueを取得して配列に加える
        if(nigate[i].checked){    
        var nigatear = [nigate[i].value, true]
		 	    ar.push(nigatear);
		   }else{
        var nigatear = [nigate[i].value, false]
		 	    ar.push(nigatear);
      }
	  }

      google.script.run.withSuccessHandler(Comm2).InsertRes2(ar);  //情報を挿入関数に送る 
    }  

   function Comm2(text){
      document.getElementById('comment2').innerHTML = text;   //body内のquestionにここで作ったhtmlを追記 
  } 
</script>

  </body>

 <!--Copyright © 2022 コトミルIT All Rights Reserved. -->
</html>

css.html

<style>
html {
  font-size: 2em;}

.reload{
  margin-top:2em;
  margin-right:2em;  
}

h1 {
  color: #fff;/*文字色*/
  padding: 1rem 1.5rem;
  background: #036c91;
  text-align: center;
}  

.qbox {
    position: relative;
    width: 80%;
    margin-top:2em;
    padding: 0.5em 1em;
    border: solid 3px #036c91;
}
.qbox .box-title {
    position: absolute;
    display: inline-block;
    top: -1.5em;
    left: -3px;
    padding: 0 0.5em;
    height: 1.5em;
    //line-height: 10px;
    background: #036c91;
    color: #ffffff;
    font-weight: bold;
    border-radius: 0.2em 0.2em 0 0;
}
.qbox p {
    text-align:left;
    margin: 0; 
    padding: 0;
}

.abox {
 margin: 0em 0em 2em 4em;
 padding: 5px 10px;
 border-left: 6px double #036c91; 
}


.ques{
  left: 50%;
}

input[type=checkbox] {
  clear: both;
	transform: scale(4);
  margin: 2em;
  vertical-align:middle;
}

.drop {
  width: 40%;
  height: 80px;
  font-size: 1.2em;
  border-radius: 0.2em;
  border: 2px solid #036c91;
}

input[type="button"].start{
   color: #fff;/*文字色*/
   width: 50%;
   min-width: 250px;
   height: 80px;
   background: #036c91;
   font-size: 1.2em;
   border: 1px solid #036c91;
   border-radius: 0.2em; 
}

input[type="button"].answer{
   margin: 0.3em 1.5em;
   color: #fff;/*文字色*/
   width: 15%;
   min-width: 250px;
   height: 80px;
   font-size: 1em;
   background: #036c91;
   border: 1px solid #036c91; 
   border-radius: 0.2em;      
}

</style>

スプレッドシートの説明

スプレッドシートに問題と解答を用意します

・シート名を変更すると動作しなくなります。

・E列以降見出しのテキストが解答に反映されます。

問題ID: 問題の識別番号。重複しなければ文字、数字なんでもOK。ただし改行不可。

カテゴリー:問題のカテゴリー。ここの値を自動で抽出し、アプリのドロップダウンの選択肢にします。

出題可否:チェックを入れると出題されなくなります。チェックをつけるのはアプリからもできますが、チェックを外すのはスプレッドシートからのみ可能です。

苦手:チェックを入れると苦手克服モードのほうにも出題されるようになります。間違えた問題や苦手な問題などに。

問題文:問題として表示されます。

メーカー、場所:解答に表示されます。デモでは2項目作製していますが設定により1項目のみ、3項目以上も可能です。

利用規約・免責事項

・個人、法人を問わずどなたでもご利用いただけます
・有料、無料を問わず再配布は不可です
・アプリを使用したことによるあらゆる損害は賠償いたしかねます

コメント