Vue.js と Ajax でお天気アプリっぽいのを作ってみた

投稿日:2017年07月21日

Tags: php vue メモ JavaScript

jQuery LOVE な僕ですが、MVCの概要もロクに理解できていないくせに、生意気にもJSのフレームワークに手を出してみました。

先日(2017-7/13, 14)の OSC 北海道 2017 でWordBench 札幌の田中 広将さんのセッション「WordPress REST API と Vue.js を使ったフロントエンド開発」を見て、 Vue.js やってみたいなと思い、行動に至った次第です。

技術的に色々と中途半端なので突っ込みどころが多々あるかと思いますが、そういう時はツイッターをフォローしていただき、コソッとDMでご指摘いただけると助かります。 ※エントリーのコメント欄でもOKです

使用するライブラリ

  • axios
    • ajax系は jQuery の $.ajax() が超絶便利ですが、今回は脱jQueryの意味も込めて使いません。
  • Vue
    • 今回使用するJSフレームワーク。html,css,そしてちょっとだけ jsの知識があれば触れるので、AngularReact に比べて取っ付きやすいイメージです。

使用するお天気API

無料で使用できて日本語だと Livedoorのが良さそうだったのでこちらを使用します。

実行環境

とりあえずMAMPで実行します。

最近のMacなら最初から PHPApache が入っていると思うので、そちらを使っても大丈夫です。

LAMP環境が作れればOKです。

僕のMAMP環境は下記になります。

  • PHP v7.0.15
  • MySQL 5.6.35
  • Apache

確認環境

このエントリーで作成したページは下記の環境で確認してます。

  • Mac OS X El Capitan
    • Chrome 最新
    • Safari 最新
    • Firefox 最新

PHPを準備する

ajaxをクロスドメインで実行しようとすると怒られちゃうので、対応策として下記phpを作成します。 ググッた結果、一番実装が簡単そうなヤツを使わせていただいております。 phpは全然分からないのですが、受け取ったデータに url っていうパラメータが含まれていて、プロトコルに不正が無ければ(http: or https: ならば)実行するみたいなことだと思います。

実行されたら file_get_contents() で受け取ったパラメータのURLからファイルの内容を全て文字列に読み込んで、mb_convert_encoding()UTF-8 に変換して、データを吐き出しているんだと思います。

<?php
  // ajax.php
  if( isset($_GET["url"]) && preg_match("/^https?:/",$_GET["url"]) ){
    $data = file_get_contents($_GET["url"]);
    $data = mb_convert_encoding($data, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
    echo $data;
  } else {
    echo "error";
  } 
1
2
3
4
5
6
7
8
9

その他ファイルを準備する

htmlのスタイリングは bootstrap を使います。

Vue.jsaxios はCDNを利用します。

script.js など適当なJSファイルを作成しておきます。

STEP1 Vueインスタンスの作成

script.js にVueインスタンスを作成し、マウントする要素 (el) と、データ (data) をそれぞれ作成します。

// script.js
const vm = new Vue({
  el: '#app',		// div#app にマウントする
  data: {
    cities: [],		// 取得する都市の一覧
    city: '016010',	// 都市の初期値
    weather: [],	// 天気情報の一覧
    title: '',		// 表示する都市の名前
    text: ''		// 天気の説明文
  }
});
1
2
3
4
5
6
7
8
9
10
11

STEP2 エリア一覧を取得するメソッドを作成する

JS

場所を取得したいという意味で、とりあえず get_location というメソッド名を付けました。

メソッド名の付け方も勉強中です…。

get_location メソッドでやってることは下記になります。

  • 先ほど作った ajax.php の url パラメータに、APIで取得できる都市の名前とIDが書かれた xml のURLを渡してあげます。
  • axios.get() で URLをリクエストし、オプションで timeout の時間と、返ってくるデータ形式を設定します。 デフォルトの設定では json 形式で返ってくるのですが、今回渡すURLが xml なので、 document 形式に設定します。
  • リクエストした xml が上手いこと返ってきたら、各都道府県が設定してある pref タグの一覧を配列形式で取得して変数 areas に入れます。
  • areasfor...of 文で回します。札幌、旭川、函館などの都市とIDが設定されてる cityタグの一覧を配列形式で取得して変数 cities に入れます。
  • citiesfor...of 文で回します。都市名とIDをオブジェクトにそれぞれ入れます。
  • 都市名とIDが入ったオブジェクトを、STEP1で作成した Vueのデータの cities へ順番に入れていきます。
  • get_location メソッドを、ページ展開時に実行したいので created に登録します。

※ 今回、エラー処理は無視します。正しいエラー処理は勉強します…。

※ axiosの詳しい使い方も割愛させていただきます。

// script.js
const vm = new Vue({
  // ~ 省略 ~
  methods: {
    get_location: function(){
      const request = 'ajax.php?url=http://weather.livedoor.com/forecast/rss/primary_area.xml'
      axios.get(request,{
        timeout: 3000,
        responseType: 'document'
      })
      .then(function (response) {
        const xml = response.data;
        const areas = xml.getElementsByTagName('pref');
        for(const area of areas) {
          const cities = area.getElementsByTagName('city');
          for(const city of cities) {
            const obj = {
              name: city.title,
              id: city.id
            };
            vm.$data.cities.push(obj);
          }
        }
      })
      .catch(function (error) {
        console.log(error);
      });
    }
  },
  created: function(){
    this.get_location()
  }
});
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

HTML

selectv-modelcity を設定してあげます。

すると、STEP1 で datacity に入れた 016010 という値が、初期値になります。

ちなみに 016010 は札幌です。

optioncities で回してあげます。

value とテキストは get_location() で取得した場所のIDと名前が入ります。

<div id="app">
  <div class="app-header">
    <h1>天気予報</h1>
    <div>
      エリアを選択:
      <select class="form-control" v-model="city">
        <option v-for="item in cities" v-bind:value="item.id">{{ item.name }}</option>
      </select>
    </div>
  </div>
</div>
1
2
3
4
5
6
7
8
9
10
11

STEP3 天気を取得するメソッドを作成する

JS

場所の一覧が取得できたので、次は天気を取得します。

STEP1 で datacity に入れた値の場所から天気を取得するメソッドを書きます。

天気を取得したいので今回は get_weather というメソッド名を付けました。

  • 先ほど作った ajax.php の url パラメータに、JSONデータをリクエストするURLを渡してあげます。
  • axios.get() で URLをリクエストし、オプションで timeout の時間と、返ってくるデータ形式を設定します。 今回は json 形式で欲しいので、responseTypejson に設定します。
  • リクエストが上手いこと返ってきたら、 thencallback関数 の引数(response)に json が入ってるので、Vueのデータオブジェクトに取得した値をそれぞれ代入します。
  • get_weather メソッドを、ページ展開時に実行したいので created に登録します。引数にはVueのデータオブジェクトに設定した city の値を渡します。

※ エラー処理は無視します。

// script.js
const vm = new Vue({
  // ~ 省略 ~
  methods: {
    get_weather: function(city){
      const request = 'ajax.php?url=http://weather.livedoor.com/forecast/webservice/json/v1?city=' + city;
      axios.get(request,{
        timeout: 3000,
        responseType: 'json'
      })
      .then(function (response) {
        vm.$data.weather = response.data.forecasts;
        vm.$data.title = response.data.title;
        vm.$data.text = response.data.description.text;
      })
      .catch(function (error) {
        vm.$data.weather = {
          title: 'エラーです'
        }
      });
    }
  },
  created: function(){
    this.get_location()
    this.get_weather(this.city);
  }
});
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

HTML

weatherv-for で回します。

v-if で分岐させて、最高気温、最低気温が取得できなかった場合は気温の代わりに「-」を出力させます。

この段階で札幌の天気が取得できます。

<div id="app">
  <!-- ~ 省略 ~-->
  <div class="app-body">
    <h2>{{ title }}</h2>
    <p>{{ text }}</p>
    <table class="table table-bordered text-center">
      <thead>
        <tr>
          <th>日付</th>
          <th v-for="item in weather">{{ item.date }}</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th>天気</th>
          <td v-for="item in weather">{{ item.telop }} <br>
          <img v-bind:src="item.image.url" v-bind:alt="item.image.title"></td>
        </tr>
        <tr>
          <th>最高気温</th>
          <td v-for="item in weather">
          <span v-if="item.temperature.max !== null">{{ item.temperature.max.celsius }}</span>
          <span v-else=>-</span></td>
        </tr>
        <tr>
          <th>最低気温</th>
          <td v-for="item in weather">
          <span v-if="item.temperature.min !== null">{{ item.temperature.min.celsius }}</span>
          <span v-else=>-</span></td>
        </tr>
      </tbody>
    </table>
  </div>
</div>
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

STEP4 地域一覧で選択した地域の天気を表示させる

HTML

セレクトボックスで選択した地域の天気を取得したいので、STEP2 で作成した select タグに v-on ディレクティブを追加して、メソッドを発火できるようにします。

select タグの値が変更(選択)されたら、選択された地域の天気を取得したいので v-on:changeget_weather(city) を追加します。

select タグの値が変更されると get_weather(city) が発火して、変更された値が v-model に代入されます。

これで表示される天気が変更できます。

// index.html
<div id="app">
  <div class="app-header">
    <h1>天気予報</h1>
    <div>
      エリアを選択:
      <select class="form-control" v-model="city" v-on:change="get_weather(city)">
        <option v-for="item in cities" v-bind:value="item.id">{{ item.name }}</option>
      </select>
    </div>
  </div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12

DEMO

下記からご自分の環境にチェックアウトしてお使いくださいませ。

https://github.com/nagasawaaaa/vue-and-axios-demo

終わりに

一覧表示部分と天気を表示する部分をコンポーネント化できたら、もっとフレームワーク使ってる感が出たのかもしれませんが、それはまたいつの日か…。

普段の仕事ではDOMをjQueryでclass付け替えたり アコーディオン実装したり、スライダー実装したりくらいなので、今回のように自分でデータを持ってきて、表示するっていうだけでも楽しかったです。

僕は仕事柄、SPAを作るような事はしませんが、Vueの良いところを上手く業務に生かせるようになりたいなぁと思います。

参考

Last Updated: 1/29/2021, 12:59:28 PM