Vue.js と Ajax でお天気アプリっぽいのを作ってみた
投稿日:2017年07月21日
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の意味も込めて使いません。
- ajax系は jQuery の
- Vue
- 今回使用するJSフレームワーク。
html
,css
,そしてちょっとだけjs
の知識があれば触れるので、Angular
やReact
に比べて取っ付きやすいイメージです。
- 今回使用するJSフレームワーク。
使用するお天気API
無料で使用できて日本語だと Livedoorのが良さそうだったのでこちらを使用します。
実行環境
とりあえずMAMPで実行します。
最近のMacなら最初から PHP
と Apache
が入っていると思うので、そちらを使っても大丈夫です。
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";
}
2
3
4
5
6
7
8
9
その他ファイルを準備する
htmlのスタイリングは bootstrap を使います。
Vue.js
と axios
は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: '' // 天気の説明文
}
});
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
に入れます。 areas
をfor...of
文で回します。札幌、旭川、函館などの都市とIDが設定されてるcity
タグの一覧を配列形式で取得して変数cities
に入れます。cities
をfor...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()
}
});
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
select
の v-model
に city
を設定してあげます。
すると、STEP1 で data
の city
に入れた 016010
という値が、初期値になります。
ちなみに 016010
は札幌です。
option
を cities
で回してあげます。
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>
2
3
4
5
6
7
8
9
10
11
STEP3 天気を取得するメソッドを作成する
JS
場所の一覧が取得できたので、次は天気を取得します。
STEP1 で data
の city
に入れた値の場所から天気を取得するメソッドを書きます。
天気を取得したいので今回は get_weather
というメソッド名を付けました。
- 先ほど作った
ajax.php
の url パラメータに、JSONデータをリクエストするURLを渡してあげます。 axios.get()
で URLをリクエストし、オプションで timeout の時間と、返ってくるデータ形式を設定します。 今回はjson
形式で欲しいので、responseType
はjson
に設定します。- リクエストが上手いこと返ってきたら、
then
のcallback関数
の引数(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);
}
});
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
weather
を v-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>
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:change
に get_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>
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の良いところを上手く業務に生かせるようになりたいなぁと思います。