Vue.jsを使ってみたかったので、2019年埼玉県知事選挙の候補者選びサイトを作ってみました!

概要説明

Vue.jsは、Webアプリケーションにおけるユーザーインターフェイスを構築するための、オープンソースのJavaScriptフレームワークです。

axiosは、HTTP通信を簡単に扱えることができるJavaScriptの
Ajax通信ライブラリです。Vue.jsのドキュメントで使用を推奨されています。

質問にYES、NOで答えていき候補者を1名に絞る簡単なサイトを構築します。

※ Vue.jsの利用を目的としているため、政策の深い部分までは考慮しません。

作成する資材一覧

資材名概要説明
index.html
最初のスタート画面です
※当記事での説明は省略
index.css
最初のスタート画面のデザインシートです
※当記事での説明は省略
main.html
質問と結果を表示する画面です
このページの中身を非同期通信で切り替えます
main.css
質問と結果を表示する画面のデザインシートです
app.js JavaScriptのファイルです
doQuestion.php 質問や候補者の情報を組み立てるサーバー側の処理です

HTML

main.htmlの全文です。

<!DOCTYPE HTML>
<HTML>
<HEAD>
    <TITLE>YES/NOに答えるだけ!2019年埼玉県知事選挙の候補者選び</TITLE>
    <META http-equiv="Content-Type" content="text/html; charset=utf-8">
    <META name="viewport" content="width=device-width, initial-scale=1">
    <META name="description" content="質問にYES/NOに答えるだけで2019年埼玉県知事選挙の候補者選びができるサイトです。">
    <link rel="stylesheet" type="text/css" href="main.css" media="all">
</HEAD>

<BODY>
<div id="app" class="app">
        <article id="elemSwitch">
            <!-- v-if isActiveがtrueの時はこちらの要素が表示される -->
            <div v-if="isActive">
                <h1>質問</h1>
                <div class="border">

                    <!-- transitionとv-bind:keyを組み合わせることで{{ question }}が変わったタイミングを検知してcssでエフェクトを付けている -->
                    <transition name="fade">

             <!-- {{ question }}はapp.jsのdataオプションと紐づいている -->
                        <div v-bind:key="question" class='question'>{{ question }}</div>
                    </transition>
                </div>

                <div id='btn' class='btn'>

                    <!-- v-on:clickを定義することでボタンの押下でapp.jsのonYesメソッドが呼ばれる -->  
                    <button type="primary" v-on:click="onYes" class="btn-yes">YES</button>

                    <!-- v-on:clickを定義することでボタンの押下でapp.jsのonNoメソッドが呼ばれる -->
                    <button type="primary" v-on:click="onNo" class="btn-no">NO</button>
                </div>

                <div style="display: none">
                    <p>{{ no }}</p>
                </div>
            </div>
            
            <!-- v-ifに対するelse文 isActiveがfalseの時はこちらの要素が表示される -->
            <div v-else>
                <h1>あなたへのお勧めは</h1>
                <div class="border">
           <!-- {{ seito }}{{ name }}はapp.jsのdataオプションと紐づいています -->
                    <div class='seito'>{{ seito }}</div>
                    <div class='name'>{{ name }}</div>

                    <!-- v-on:clickを定義することでボタンの押下でapp.jsの"toSite"メソッドが呼ばれる -->
                    <div class='site' v-on:click="toSite">WEBサイトへ</div>
                </div>
                <div>
                    <p>2019年埼玉県知事選挙は<br><span>8月25日(日)</span>です。<br>皆さん投票に行きましょう!</p>
                </div>
                <div>
                    <p>このサイトは
                        <a href="https://www.pref.saitama.lg.jp/e1701/documents/chiji2019.pdf">
                            選挙公報</a>
                        を参考に作成しています。
                        <br>遊び半分で作っているので参考までに。</p>
                </div>
                <div>
                    <p>
                        <a href="https://www.tec-engineer.com/">
                            ブログもやっているのでよろしくお願いします!</a>
                    </p>
                </div>
                <button type="primary" v-on:click="toMenu" class="btn-yes">戻る</button>
            </div>

        </article>
    </div>

    <!-- CDNを利用してVue.jsとaxiosを読み込む -->
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    
    <script src="app.js"></script>

</body>

</html>

JavaScript

app.jsの全文です。

const vm = new Vue({
   el: '#app',
   data: {
      question: 'NHKをぶっ壊したい',
      no: '1',
      isActive: true,
      name: "",
      seito: "",
      site:"",
   },

   methods: {
      onYes: function () {
         let params = new URLSearchParams();
         params.append('btn', 'yes');
         params.append('no', this.no);
         axios.post('doQuestion.php', params)
            .then(res => {
               this.question = res.data.qa;
               this.no = res.data.no;
               this.name = res.data.name;
               this.seito = res.data.seito;
               this.site = res.data.site;
               if (res.data.finalMark){
                  this.isActive = !this.isActive;
               }
            })
            .catch(function (error) { // => 失敗時
               console.log("error");
            })
      },
      onNo: function (id, name) {
         //console.log(params)
         let params = new URLSearchParams();
         params.append('btn', 'no');
         params.append('no', this.no);
         axios.post('doQuestion.php', params)
            .then(res => {
               this.question = res.data.qa;
               this.no = res.data.no;
               this.name = res.data.name;
               this.seito = res.data.seito;
               this.site = res.data.site;
               if (res.data.finalMark){
                  this.isActive = !this.isActive;
               }
            })
            .catch(function (error) { // => 失敗時
               console.log("error");
            })
      },
      toSite: function () {
         window.open(this.site, '_blank');
      },
      toMenu: function () {
         window.location.href = 'https://www.tec-engineer.com/chiba/';
      },
   },
})

CSS

main.cssの全文です。

html {
    font-family: sans-serif;
    font-weight: bold;
    width: 100%;
}

h1 {
    margin-top: 40px;
}

span {
    color: red;
    font-weight: bolder;
}

.app {
    text-align: center;
}

.border {
    border: solid 3px green;
    border-radius: 20px;
    margin: auto;
    margin-bottom: 20px;
    width: 90%;
}

.question {
    margin: auto;
    font-size: 30px;
    padding: 30px;
}

.fade-enter-active, .v-leave-active {
    transition: opacity 1s;
}

.v-leave-active {
    /*終了の状態を指定する*/
    position: absolute;
}

.fade-enter, .fade-leave-to {
    /*動作(イージングや時間)を指定する*/
    opacity: 0;
}

.btn {
    margin-bottom: 20px;
}

.btn-yes {
    padding: 10px 20px 10px 20px;
    background-color: green;
    border: none;
    border-radius: 10px;
    font-size: 20px;
    font-weight: bold;
    color: white;
    width: 130px;
}

.btn-yes:hover {
    background-color: lightgreen;
}

.btn-no {
    padding: 10px 20px 10px 20px;
    background-color: red;
    border: none;
    border-radius: 10px;
    font-size: 20px;
    font-weight: bold;
    color: white;
    width: 130px;
}

.btn-no:hover {
    background-color: lightcoral;
}

.seito {
    font-size: 20px;
    padding: 10px;
}

.name {
    font-size: 20px;
    padding: 10px;
}

.site {
    font-size: 20px;
    padding: 10px;
}

@media(min-width:768px) {
    .seito {
        font-size: 40px;
        padding: 10px;
    }
    .name {
        font-size: 40px;
        padding: 10px;
    }
    .site {
        font-size: 40px;
        padding: 10px;
    }
    .border {
        border: solid 3px green;
        border-radius: 20px;
        margin: auto;
        margin-bottom: 20px;
        width: 50%;
    }
    p {
        font-size: 30px;
    }
}

PHP

サーバー側で質問や候補者の情報を返すdoQuestion.php です。長いので抜粋しています。

<?php

$btn = $_POST["btn"];
$no = $_POST["no"];
header('Content-type: application/json; charset=utf-8');

$qa = "";
$name = "";
$seito = "";
$site = "";
$finalMark = false;

switch ($no) {
  case '1':
    if ($btn == 'yes') {
      $name = "浜田 聡(はまだ さとし)";
      $seito = "NHKから国民を守る党";
      $site = "http://www.nhkkara.jp/";
      $finalMark = true;
      break;
    } else {
      $qa = "これからは地熱発電を積極的に活用していくべきだ";
      $no = 2;
      break;
    }

・・・

  case '9':
    if ($btn == 'yes') {
      $name = "大野 元裕(おおの もとひろ)";
      $seito = "無所属";
      $site = "https://oonomotohiro.jp/";
      $finalMark = true;
      break;
    } else {
      $qa = "正直だれでもいいからランダムで決めてほしい";
      $no = 10;
      break;
    }
  case '10':
    if ($btn == 'yes') {

      switch (rand(1, 5)) {
        case '1':
          $name = "大野 元裕(おおの もとひろ)";
          $seito = "無所属";
          $site = "https://oonomotohiro.jp/";
          $finalMark = true;
          break;
        case '2':
          $name = "青島 健太(あおしま けんた)";
          $seito = "無所属";
          $site = "http://aoshima-kenta.jp/";
          $finalMark = true;
          break;
        case '3':
          $name = "櫻井 志津江(さくらい しずえ)";
          $seito = "無所属";
          $site = "";
          $finalMark = true;
          break;
        case '4':
          $name = "武田 信弘(たけだ のぶひろ)";
          $seito = "無所属";
          $site = "https://www.takedanobuhiro.com/";
          $finalMark = true;
          break;
        case '5':
          $name = "浜田 聡(はまだ さとし)";
          $seito = "NHKから国民を守る党";
          $site = "http://www.nhkkara.jp/";
          $finalMark = true;
          break;
      }
    } else {
      $qa = "NHKをぶっ壊したい";
      $no = 1;
      break;
    }
}

$array = array(
  "qa" => $qa,
  "no" => $no,
  "name" => $name,
  "seito" => $seito,
  "site" => $site,
  "finalMark" => $finalMark,
);

echo json_encode($array, JSON_UNESCAPED_UNICODE);

Vue.jsとaxios の読み込み

HTMLにCDNを利用してVue.jsとaxiosを読み込んでいます。

main.html

<!-- CDNを利用してVue.jsとaxiosを読み込む -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

HTMLのDOMとJavaScriptのデータ項目の紐づき

app.jsの中にあるデータ項目は、HTMLに{{項目名}}と記載することで紐づけることができます。app.jsのデータが書き換われば、画面の表示も画面の再読み込みをすることなく変更されます。

app.js

   data: {
      question: 'NHKをぶっ壊したい',
      no: '1',
      isActive: true,
      name: "",
      seito: "",
      site:"",
   },

main.html

<div class="border">
  <div class='seito'>{{ seito }}</div>
  <div class='name'>{{ name }}</div>
  <div class='site' v-on:click="toSite">WEBサイトへ</div>
</div>

v-on:clickで処理を発火

ボタンのクリックなどイベントに反応して動くjsのメソッドを作成します。

YESボタンを押下すると、app.jsに定義された”onYes”メソッドが呼び出されます。

main.html

<!-- v-on:clickを定義することでボタンの押下でapp.jsのonYesメソッドが呼ばれる -->  
<button type="primary" v-on:click="onYes" class="btn-yes">YES</button>

app.js

methods: {
   onYes: function () { ~

axiosで非同期通信

質問の内容を入れ替えたり、候補者の情報を取得するために非同期通信を実施します。

app.js onYesメソッドの中でaxiosを使って非同期処理通信を行い、
doQuestion.php を呼び出しています。(onNoメソッドも同様)

app.js

   methods: {
      onYes: function () {
         let params = new URLSearchParams();
         params.append('btn', 'yes');
         params.append('no', this.no);
         axios.post('doQuestion.php', params)
            .then(res => {
               this.question = res.data.qa;
               this.no = res.data.no;
               this.name = res.data.name;
               this.seito = res.data.seito;
               this.site = res.data.site;
               if (res.data.finalMark){
                  this.isActive = !this.isActive;
               }
            })
            .catch(function (error) { // => 失敗時
               console.log("error");
            })
      },

パラメータとして押下したボタンと質問Noをセットしています。

let params = new URLSearchParams();
params.append('btn', 'yes');
params.append('no', this.no);

非同期通信でdoQuestion.phpを呼び出しています。引数でパラメータを渡しています。

axios.post('doQuestion.php', params)

PHP側は以下の処理でパラメータを受取っています。

$btn = $_POST["btn"];
$no = $_POST["no"];

そして、echo json_encodeでクライアント側にjsonデータを返却します。

$array = array(
  "qa" => $qa,
  "no" => $no,
  "name" => $name,
  "seito" => $seito,
  "site" => $site,
  "finalMark" => $finalMark,
);

echo json_encode($array, JSON_UNESCAPED_UNICODE);

非同期通信が成功すると.thenの処理が動きます。

res.data.{項目名}でPHPから返ってきたデータを取得可能です。

this{項目名}

.then(res => {
  this.question = res.data.qa;
  this.no = res.data.no;
  this.name = res.data.name;
  this.seito = res.data.seito;
  this.site = res.data.site;

v-if で画面に表示する要素を切り替え

v-if を利用し、質問画面から候補者が確定したら候補者情報を画面に表示するようにコントロールをします。

main.html画面に表示する要素を切り替えます。

<!-- v-if isActiveがtrueの時はこちらの要素が表示される -->
<div v-if="isActive">
  <h1>質問</h1>

~
               
<!-- v-ifに対するelse文 isActiveがfalseの時はこちらの要素が表示される -->
<div v-else>
  <h1>あなたへのお勧めは</h1>

    

app.jsのデータ項目で isActive の初期値に “true” を設定しているので、
<div v-if=”isActive”>の要素が表示さてます。

   data: {
      question: 'NHKをぶっ壊したい',
      no: '1',
      isActive: true,
      name: "",
      seito: "",
      site:"",
   },

非同期通信の結果で finalMark が “true” であれば isActive が “false” になります。これで <div v-else>側の要素が表示されます。

if (res.data.finalMark){
  this.isActive = !this.isActive;
}

transition と v-bind:key で値が変更された際の
エフェクトを作成する

質問が変わる際のエフェクトの設定をします。

Vue.js のトランジション (Transition) システムを使用すると、DOM から要素を取得したり削除するといったエフェクトを自動的に適用できます。

v-bind:keyに”question”を設定することで”question”の値が変更された時に、 transitionが発生します。

main.html

<!-- transitionとv-bind:keyを組み合わせることで{{ question }}が変わったタイミングを検知してcssでエフェクトを付けている -->
                    <transition name="fade">
                        <div v-bind:key="question" class='question'>{{ question }}</div>
                    </transition>

main.css

.fade-enter-active, .v-leave-active {
    transition: opacity 1s;
}

.v-leave-active {
    /*終了の状態を指定する*/
    position: absolute;
}

.fade-enter, .fade-leave-to {
    /*動作(イージングや時間)を指定する*/
    opacity: 0;
}

以上、何かの参考になれば幸いです。