Monacaを使ってクイズアプリをたった1時間で作ってみる-後編-
前回はクイズアプリのトップページとクイズページの途中まで作成いたしました。今回はデータオブジェクトからクイズデータを持って来てバインド、正解不正解のアラート表示、結果画面でのスコア表示、全体のレイアウトを行いたいと思います。
クイズデータを作ろう
まずはクイズのデータを作成します。データは管理しやすくするためコントローラー内ではなくサービス(value)内で管理します。jsフォルダ内にservicesフォルダを新規作成して下さい。更にservicesフォルダ内にquestionsService.jsファイルを作成します。このファイル内にサービスを下記コードのように記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | app.value('questionsService', { questions : [ /*{ "text": "問題文", "answer": "答え", "choices": ["答え以外の選択肢","答え以外の選択肢","答え以外の選択肢"] },*/ { "text": "Google社製のWEBブラウザは?", "answer": "Chrome", "choices": ["FireFox","Opera","Safari"] }, ... ... ... ] }); |
2行目からのquestions配列の中に、問題ごとの問題文(text)・答え(answer)・答え以外の選択肢×3の配列(choices)を保持したオブジェクトを問題数分記述します。
作成したquestionsServiceファイルをindex.htmlのbody閉じタグ手前で読み込ませます。
1 2 3 | <!------------------------------angular services-----------------------------------> <script src="js/services/questionsService.js"></script> </body> |
データバインド用にクイズページを書き換えよう
前編で作成途中だったクイズページですが、仮で入れていた部分をデータバインドするように下記のように変更します。まだクイズページ用コントローラー(gameCtrl)の記述は済んでいないため、次章と照らし合わせるとデータバインド部分が分かるかと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <ons-page> <div ng-controller="gameCtrl as game"> <ons-toolbar> <div class="left" ng-click="game.backTop()"> <ons-toolbar-button> <ons-icon icon="ion-close-round" fixed-width="false" style="font-size: 26px; vertical-align: -4px"></ons-icon> </ons-toolbar-button> </div> <div class="center"> Quiz App </div> <div class="right"> {{game.items.currentNum + 1}} / {{game.items.totalNum}}問目 </div> </ons-toolbar> <div class="question"> <p> {{game.items.currentQ.text}} </p> </div> <div class="choices"> <ons-row> <ons-col width="50%" align="center" ng-repeat="choice in game.items.currentQ.choices track by $index"> <div class="choice"> <button class="button button--large--cta" ng-click="game.getAnswer($index)"> {{choice}} </button> </div> </ons-col> </ons-row> </div> </div> </ons-page> |
まず4行目ではクローズボタンがクリックされた時に実行する関数をng-clickで指定します。
13行目では現在のクイズ番号(items.currentNum)と全クイズ数(items.items.totalNum)をデータバインドさせます。items.currentNumは0から始まるので1を足します。
18行目では現在のクイズオブジェクト(items.currentQ)の問題文(text)をデータバインドさせます。
23行目からはng-repeatにて、現在のクイズオブジェクト(items.currentQ)の選択肢(choices)の分だけ、ons-col要素を繰り返させます。
25行目のng-clickにて解答ボタンがクリックされた時に実行する関数に、引数で選択肢の番号($index)を渡します。
26行目で解答ボタンに表示させるテキスト(choice)をデータバインドさせます。
クイズページ用コントローラーを作ろう
クイズページ用コントローラーでは、
- 前々章で作成したクイズデータを取得
- 前章で作成したクイズ用ページへのデータバインド
- 解答ボタンがクリックされた時の正誤判定をアラート表示
- 次のクイズを用意or結果ページへ遷移
の機能を実装します。app.jsのgameCtrlの関数内に下記のコードを追記してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | app.controller('gameCtrl',function(questionsService,$scope){ var me = this;//thisをmeに退避 me.items = {};//データバインド用オブジェクト var rightNum = 0;//正解数 var anserNum = null;//正解番号 var questions = null;//クイズデータ var init = function(){ me.items.currentNum = 0;//現在のクイズ番号(1問目) questions = JSON.parse(JSON.stringify(questionsService.questions));//クイズデータをサービスより取得&ディープコピー me.items.totalNum = questions.length;//取得したクイズデータの全クイズ数 questionInit(); } //解答選択肢用意 var questionInit = function(){ var currentQ = questions[me.items.currentNum];//現在のクイズ var qLength = currentQ.choices.length;//答え以外の選択肢数 answerNum = Math.floor(Math.random() * (qLength + 1));//答えの番号をランダムに決める currentQ.choices.splice(answerNum , 0 , currentQ.answer);//選択肢に答えを混ぜる me.items.currentQ = currentQ;//現在のクイズをデータバインド用オブジェクトに代入 }; //解答ボタンが押されたら me.getAnswer = function(ind){ var flag = answerNum == ind;//正解か間違いか判定 var flagText = "間違い"; if(flag){//正解だったら rightNum++;//正解数を増やす flagText = "正解"; } ons.notification.alert({//解答をアラート表示 message: '正解は『' + me.items.currentQ.choices[answerNum] + '』です', title: flagText, buttonLabel: 'NEXT', animation: 'default', callback: function() {// NEXTがクリックされたら if(me.items.currentNum >= me.items.totalNum-1){//全問終了したら myNavigator.pushPage('result.html',{totalNum:me.items.totalNum,rightNum:rightNum}); }else{//まだクイズが残っていれば me.items.currentNum++; $scope.$apply(questionInit);//次のクイズ用意($scope.$applyを使うことにより強制的にデータバインドさせる) } } }); }; //closeボタンがクリックされたらトップページへ戻る me.backTop = function(){ myNavigator.pushPage('top.html', { animation: "none" }); }; init(); }); |
コードの細かい部分はコメントを見てもらえば分かると思いますので、大事な点だけ下記にピックアップします。
1行目の引数にquestionsServiceを指定することによりクイズデータを収めたサービスをコントローラー内で使えるようにしています。
32行目のons.notification.alertではonsenUIの機能によりアラート表示を使用しています。詳しくはコチラ。
39行目はこれから作成する結果ページへの遷移をさせます。pushPageの第二引数にオブジェクトを指定することにより、ページナビゲーター(myNavigator)にデータを保持させたままページ遷移させることが出来ます。全クイズ数と正解数をデータとして持たせ結果ページで使います。
結果ページを作ろう
全クイズの解答が完了したら、スコアを表示させる結果ページを表示させます。結果ページには
- 何問中何問正解だったか
- スコア
- トップへ戻るボタン
を配置させます。wwwフォルダ内にresult.htmlを新規作成してください。元から記述があれば削除して下記のコードを参考にしてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <ons-page> <div ng-controller="resultCtrl as result"> <ons-toolbar> <div class="center"> 結果発表 </div> </ons-toolbar> <div class="result"> <p class="detail">{{result.items.totalNum}} 問中 {{result.items.rightNum}} 問正解</p> <p class="score"><span>{{result.items.score}}</span> point</p> </div> <div class="back-btn"> <ons-button modifier="large" ng-click="result.backTop()"> トップへ戻る </ons-button> </div> </div> </ons-page> |
ここまで来たら特に難しいことはありませんね。
それでは続けて結果ページ用コントローラーを下記のようにapp.jsへ追記します。
1 2 3 4 5 6 7 8 | app.controller('resultCtrl',function(){ var rate = 100; this.items = myNavigator.getCurrentPage().options; this.items.score = this.items.rightNum * rate; this.backTop = function(){ myNavigator.pushPage('top.html', { animation: "none" }); }; }); |
3行目のページナビゲーターのgetCurrentPage().optionsによってクイズページ用コントローラー(gameCtrl)より遷移時に渡されたデータ( {totalNum:全クイズ数,rightNum:正解数} )を取得しています。
以上で動きやシステム部分は完了です。iPhoneとAndroidで若干違いますが、各ページ下記のような見え方になっているかと思います。
レイアウトを調整しよう
最後に見え方を良くするためレイアウト調整で仕上げに入ります。ここからは単純にCSSを扱うだけなので好きなようにしていただければと思いますが、下記が今回使ったCSSです。cssフォルダに元からあるstyle.cssに書きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | /*************** *トップページ ****************/ .title{ text-align: center; margin-top: 140px; margin-bottom: 180px; color: #FF9D0F; } .title h1 span{ font-size: 0.7em; } .title p{ display: inline; font-size: 14px; padding: 2px 10px; background-color: #666; color: #FFF; border-radius: 20px; } .start-btn,.back-btn{ padding: 0 20px; } /*************** *クイズページ ****************/ .right{ line-height: 40px; } .question{ padding: 10px 20px; min-height: 240px; background-color: #EEE; } .question p{ font-size: 24px; } .choices{ } .choices .choice{ width: 90%; margin: 40px auto 0 auto; } /*************** *結果ページ ****************/ .result{ min-height: 370px; } .result-title{ text-align: center; } .detail{ text-align: center; font-size: 18px; } .score{ text-align: center; font-size: 28px; font-weight: bold; } .score span{ color: #FC3; font-size: 60px; } |
このCSSを付けることにより下の画像のように見えやすくなりました。
また、背景やテキスト、コンポーネント各種の色をブラウザ上でカスタマイズできるOnsen Theme Rollerを使うと便利です。使い方は簡単ですが、詳しく知りたい方はコチラ。
今回はツールバー部分とボタン部分をオレンジに変更しました。ダウンロードしたCSSファイルをcssフォルダに置き、index.htmlで読み込みます。
以上で完成!!お疲れ様でした。下記のような動き、デザインになりました。(GIFアニメーション)
まとめ
念の為、完成形のコードをGitへ上げておきます。wwwフォルダ内を参考にしてください。
https://github.com/matorel/monaca_quiz_app/tree/v1.0.0
今回の作り方はAngularJS流でしたので少しまどろっこしい部分もありますが、今後拡張していく上でその方が便利です。例えばクイズデータをサーバーから取得したり、クイズにレベルを設けたり、ハイスコアをデータベースに登録したりする複雑な工程はAngularJS流に書いたほうが自由にできます。
ここから実際はアプリをデプロイしてリリースする作業があります。それはまたの機会に紹介できたらと思います。