JavaScriptの習作。Yahooの住所ディレクトリAPIと気象情報APIを利用して一時間後の予想降水量を調べるWEBアプリのサンプルを作ってみました。

Yahoo デベロッパーネットワーク というYahooが開発/提供しているツール群があります。その中のに全国の市町村名を取得することができるAPIと、予想降水量を取得できるAPIがあるのですが、それを利用して指定した地域の一時間後の予想降水量を取得できるWEBアプリのサンプルを練習がてら作ってみました。

アプリにアクセスしたりクリックイベントが発火するとJavaScriptのgetJSONからproxy_rest.phpにデータが渡され、そのデータに基づいて各種APIからデータを取り出し、htmlに表示する、という流れで動作します。

上記のデモページにアクセスすると、select要素の中に都道府県が入っています。loadイベント実行時に、getJSONを使ってAPIから都道府県のデータを取得して表示します。

select要素から任意の選択肢を選ぶと自動的にchangeイベントが発火し、市町村名を五十音順でフィルタリングしたボタンが表示されます。このボタンはjsファイルに記述してある連想配列と、前段階で取得しているAPIから得たデータを組み合わせて生成しています。

同時に戻るボタンも出ます。文字通りひとつ前の段階に戻る機能を持っていますが、やっていることはhtml要素にclassをつけたり外したりしてCSS3のtransitionを利用してウィンドウの外に追い出したり引き入れたりして画面表示を変更しているだけです。

五十音ボタンをクリックすると、属する市町村名ボタンが出ます。こちらもAPIからデータを取得して生成しています。次の段階で利用する緯度経度の値をdata属性として持たせています。

最後に市町村名ボタンをクリックすると、一時間後の予想降水量が表示されます。ボタンの中に入れてある緯度経度の値をAPIに渡してデータを取得することにより実現してます。

と、文章で書いてもなかなか伝わりませよね。コードを掲載してみます。

JavaScript

JavaScript初学者が作った学習用のコードのため、間違いや非効率なコードが含まれている可能性があります。ご了承ください。ご指摘、ご助言いただけますと非常にありがたいです!コメント欄をご利用ください。
//script.js

jQuery(function() {
/**
 * API設定
 */

//name space
var gVar = gVar || {};

//JSON取得用URLを切り替えるワード
gVar.changingWord = {
	country : 'country',
	city : 'city',
	geometry: 'geometry'
};

//市区町村 絞込 リクエストパラメーター 頭文字コード
gVar.cityFiltering = [
	{
		code: 'a',
		codeVal: 'あ行',
	} , {
		code : 'k',
		codeVal : 'か行、が行'
	} , {
		code: 's',
		codeVal: 'さ行、ざ行',
	} , {
		code: 't',
		codeVal: 'た行、だ行',
	} , {
		code: 'n',
		codeVal: 'な行',
	} , {
		code: 'h',
		codeVal: 'は行、ば行、ぱ行',
	} , {
		code: 's',
		codeVal: 'ま行',
	} , {
		code: 'y',
		codeVal: 'や行',
	} , {
		code: 'r',
		codeVal: 'ら行',
	} , {
		code: 'w',
		codeVal: 'わ行',
	}
];

/**
 * 都道府県名表示
 */
 (function(){

	jQuery(window).one('load',function(){

		jQuery.getJSON('proxy_rest.php',{changingWord:gVar.changingWord.country})
		.done(function(data){

			jQuery('.country select').append('');

			for(var i = 0; i < data.ResultInfo.Count; i++ ){
				var val = {
					name : data.Feature[0].Property.AddressDirectory[i].Name,
					AreaCode : data.Feature[0].Property.AddressDirectory[i].AreaCode
				}
				jQuery('.country select').append('');
			}
		})
		.fail(function(){
			window.alert('失敗');
		});

	});

 }());


/**
 * 市区町村名50音 表示
 */
(function(){

	jQuery(document).on('change','.country select', function(){
		jQuery('.city_filtering_body').empty();
		var $cityName = jQuery('.country select option:selected').text();
		var $areaCode = jQuery(this).val();
		for(var i = 0; i <= gVar.cityFiltering.length -1; ++i){
			jQuery('.city_filtering_body').append('');
		}
		removeOut('.city_filtering');
		addOut('.country');
		setName('.city_filtering_body',$cityName);
		removeNone('.controller_city_filtering');
	});

}());


/**
 * 市区町村名/座標情報(緯度経度) 表示
 */
 (function(){

 	jQuery(document).on('click','.city_filtering_body button',function(){

 		var cityfiltering_code = jQuery(this).attr('data-cityfiltering_code');

		jQuery.getJSON('proxy_rest.php',{
			changingWord:gVar.changingWord.city,
			cityFilteringCode:cityfiltering_code
		})
		.done(function(data){
			jQuery('.city_body').empty();
			for(var i = 0; i < data.ResultInfo.Count; i++ ){
				var val = {
					name : data.Feature[0].Property.AddressDirectory[i].Name,
					coordinates : data.Feature[0].Property.AddressDirectory[i].Geometry.Coordinates
				}
				jQuery('.city_body').append('');
			}
			removeOut('.city');
			addOut('.city_filtering');
			removeNone('.controller_city');
			removeNone('.city_body');
			addNone('.controller_city_filtering');
		})
		.fail(function(){
			window.alert('失敗');
		});

	});

}());


/**
 * 天気情報 表示
 */
(function(){
	jQuery(document).on('click','.city_body button',function(){

		var dataCoordinates = jQuery(this).attr('data-coordinates');
		addNone('.city_body');

		jQuery.getJSON('proxy_rest.php',{
			changingWord:gVar.changingWord.geometry,
			coordinates:dataCoordinates
		})
		.done(function(data){
			jQuery('.weather_body').empty();
			var val = {
				weather :data.Feature[0].Property.WeatherList.Weather[6].Rainfall
			}
			jQuery('.weather_body').append('

1時間後の予想降水量
' + val.weather + '
mm/h

'); removeOut('.weather'); addOut('.city'); removeNone('.controller_weather'); addNone('.controller_city'); }) .fail(function(){ window.alert('失敗'); }); }); }()); /** * リターン/クローズボタン */ jQuery('.controller_city_filtering button[name=return]').on('click',function(){ selectClear('.country select'); addOut('.city_filtering'); removeOut('.country'); addNone('.controller_city_filtering'); }) jQuery('.controller_city button[name=return]').on('click',function(){ addOut('.city'); removeOut('.city_filtering'); addNone('.controller_city'); removeNone('.controller_city_filtering'); jQuery('.city_body').empty(); }) jQuery('.controller_weather button[name=return]').on('click',function(){ removeNone('.city_body'); addOut('.weather'); removeOut('.city'); addNone('.controller_weather'); removeNone('.controller_city'); }) jQuery('.controller button[name=close]').on('click',function(){ removeOut('.country'); addOut('.city'); addOut('.weather'); addNone('.controller_city_filtering'); addNone('.controller_city'); addNone('.controller_weather'); selectClear('.country select'); jQuery('.city_body').empty(); }) /** functions */ function setName($name,$elm){ jQuery($name).prepend('

' + $elm + '

'); } function removeOut($elm){ jQuery($elm).removeClass('out'); } function addOut($elm){ jQuery($elm).addClass('out'); } function removeNone($elm){ jQuery($elm).removeClass('none'); } function addNone($elm){ jQuery($elm).addClass('none'); } function selectClear($elm){ jQuery($elm).each(function() { this.selectedIndex = 0; }); } });

PHP

<?php

//proxy_rest.php

mb_http_output('utf-8');
mb_internal_encoding('utf-8');
header('Content=Type: text/xml;charset=UTF-8');

/**
 * 受信したデータを判定し、JSONを取得するURL生成メソッドを切り替えて呼び出す
 * changingWordはURLを切り替えるために使う
 */
if (!isset($_GET["changingWord"]) || $_GET["changingWord"] === ''){

	echo 'no data';

} else {

	$json = new createJson();
	$changingWord = $_GET["changingWord"];

	if ($_GET["changingWord"] === 'country') {

		//都道府県名をリクエスト
		$json->responseData($changingWord);

	} else if ($_GET["changingWord"] === 'city') {

		//市区町村名(五十音順)をリクエスト
		if (isset($_GET["cityFilteringCode"])){
			$json->responseData($changingWord,$_GET["cityFilteringCode"]);
		}

	} else if ($_GET["changingWord"] === 'geometry') {

		//天気をリクエスト
		if (isset($_GET["coordinates"])){
			$json->responseData($changingWord,$_GET["coordinates"]);
		}

	}

}

/**
 * APIからJSON形式のデータを取得/表示する
 */
class createJson{

	//API URL 住所ディリクトリAPI
	private $url_dir     = 'http://search.olp.yahooapis.jp/OpenLocalPlatform/V1/addressDirectory';

	//API URL 気象情報API
	private $url_weather = 'http://weather.olp.yahooapis.jp/v1/place';
	
	//アプリケーションID
	private $appid       = ***; //Yahooより取得したアプリケーションIDを入力する

	//出力形式
	private $output      = 'json';

	//住所コード 地域名を表現するリテラルを格納する
	private $ac;

	//緯度経度を表す数値を格納する
	private $cd;

	//url生成/返却データ表示
	function responseData($word,$setVal = false){

		//条件に応じたリクエストを呼び出す
		if ($word == 'country') {

			//都道府県名をリクエスト
			$url = $this->requestUrl_country();

		} else if ($word == 'city') {

			//住所コードをセット
			$this->ac = $setVal;

			//市区町村名(五十音順)をリクエスト
 			$url = $this->requestUrl_city();

		} else if ($word == 'geometry') {

			//緯度経度をセット
			$this->cd = $setVal;

			//天気をリクエスト
			$url = $this->requestUrl_weather();

		}

		//返却されたjsonデータを表示
		echo file_get_contents($url);
		
	}

	function requestUrl_country(){
		return $this->url_dir."?appid=".$this->appid."&output=".$this->output."&ac=JP&callBack=?";
	}

	function requestUrl_city(){
		return $this->url_dir."?appid=".$this->appid."&output=".$this->output."&ac=".$this->ac."&mode=1"."&callBack=?";
	}

	function requestUrl_weather(){
		//"http://weather.olp.yahooapis.jp/v1/place?coordinates=139.732293,35.663613&output=json&appid=dj0zaiZpPUpUR2x2UG04azBPbiZzPWNvbnN1bWVyc2VjcmV0Jng9ZmE-&callBack"
		return $this->url_weather."?appid=".$this->appid."&output=".$this->output."&coordinates=".$this->cd."&callBack";
	}

} //createJson end

Html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link href="style.css" rel="stylesheet" type="text/css" media="all" />
<script src="http://code.jquery.com/jquery-2.2.3.min.js"></script>
<script src="js/custom.js"></script>
</head>

<body>

    <div class="controller">
        <div class="controller_city_filtering none">
            <button name="return" class="btn">戻る</button>
        </div>
        <div class="controller_city none">
            <button name="return" class="btn">戻る</button>
            <button name="close" class="btn">閉じる</button>
        </div>
        <div class="controller_weather none">
            <button name="return" class="btn">戻る</button>
            <button name="close" class="btn">閉じる</button>
        </div>        
    </div>

    <div class="country">
        <div  class="country_inner">
            <h1 class="title">一時間後の予想降水量</h1>
            <div class="country_body"><select></select></div>        
        </div>
    </div>

    <div class="city_filtering out">
        <div class="city_filtering_body"></div>
    </div>

    <div class="city out">
        <div class="city_body"></div>
    </div>

    <div class="weather out">
        <div class="weather_body"></div>
    </div>


</body>
</html>

まとめ

一部例外処理を省略してはいますが、概ね期待した動作をしています。コードは割りとシンプルに書けたかな、とは思いますが、いかんせん経験不足なためコードの良し悪しを自己評価するのが難しいです。良くないところとか改善できるところがたくさんあるのだろうとは思うのですが、もうこれは経験を積み重ねた後に解ることなので精進あるのみですね。

もっと機能を追加する場合は、APIから取得したデータをhtmlに埋め込むのではなく、配列を作ってキャッシュし、必要に応じてデータを再利用しやすくするような感じがいいのでしょうかね。

たとえば、複数の地域の降水量情報を並列で表示して、一方のデータのみ切り替えて比較するような動作とか、降水量は一時間おきだけじゃなく、2時間後、3時間後とかもたしかとれるはずなので平均値を求めたり、まぁいろいろあると思います。

そうなるとAngularJSとかVue.jsとかMVCフレームワークの出番があるのかなーとか思います。想像していると楽しくなってきますね。そういったフレームワークも機会があれば勉強してみたいと思います。

今後の予定は、さしあたってアニメーションに特化したjsライブラリをやってみようかなと検討しています。候補にあるのがTweenMaxというやつ。なんか便利そう。p5.jsもいいらしい。いろいろ試してみようと思います。

[API]phpでAPIにアクセスしてgetJSON()で取得する。APIキーを隠蔽する方法。

以前の投稿でぐるなびAPIを利用したSPAのサンプルをご紹介しましたが、JavaScriptで作ったため、本来公開すべきではないAPIキーを隠蔽できない、という問題がありました。

それを解決する方法として、PHPでAPIキーを使って情報を取得し、それをJavaScript側で取得する、という方法があるとわかったのでサンプルコードを書いてみました。

実際の動作は以下のdemoから確認できます。

サンプルコード

demoのコードを簡略化したものを以下にご紹介します。

html

ボタンをクリックするとイベントが発生し、.resultの中にAPIから取得したデータを表示させます。

<html>
<head>
	<script type='text/javascript' src="jquery.js"></script>
	<script type='text/javascript' src='api.js'></script>
</head>
<body>
<div class="main-content">
	<section>
		<ul class="result"></ul>
		<input type="button" class="btn" value="表示する" />
	</section>
</div>
</body>
</html>

PHP

APIのURLにパラメーターを付けてアクセスするとAPIからデータが戻ってくるのですが、(ぐるなびAPIの場合)必須のパラメーターのひとつにAPIキーがあります。

つまり、JavaScriptのイベントなどで非同期にAPIにアクセスする場合、jQueryを使うならajax()やgetJSON()などを使ってAPIキーを含んだパラメーター付きのURLにアクセスする必要があるのですが、jsファイルなどにAPIキーを埋め込むと当然隠蔽できません。

なので、phpなどでサーバー側でAPIにアクセスして処理結果を得られる仕様にすれば、APIキーが隠蔽できる、ということになります。

というわけで以下のphpでAPIを叩いてデータを取得。JavaScriptに戻す値を生成します。

<?php
//proxy.php

	mb_http_output('utf-8');
	mb_internal_encoding('utf-8');
	header('Content=Type: text/xml;charset=UTF-8');

	//エンドポイントのURIとフォーマットパラメータを変数に入れる
	$uri_rest = "http://api.gnavi.co.jp/RestSearchAPI/20150630/"; //レストラン検索API

	//APIアクセスキーを変数に入れる
	$acckey = "a6154b84e515ec****************************";

	//返却値のフォーマットを変数に入れる
	$format = "json";

	//キーワード設定
	$freeword = 'コーヒー,カフェ,珈琲,喫茶,喫茶店'; 

	//get送信されたデータを取得
	if (isset($_GET["pref"])) {
		$pref = $_GET['pref'];
	} else {
		$pref = 'PREF13';
	}

	//URL組み立て
	$url = sprintf("%s%s%s%s%s%s%s%s%s%s%s", $uri_rest, "?keyid=", $acckey, "&format=", $format, "&freeword=", $freeword, "&pref=", $pref);

	//API実行
	echo file_get_contents($url);

JavaScript(jQuery)

getJSON()の戻り値として、(result)の中にproxy.phpで生成されたJSONデータが入っています。それを使ってhtmlを生成します。


//api.js

jQuery(function() {

	jQuery('.btn').on('click', function(){

		//get送信、戻り値取得
		jQuery.getJSON('proxy.php', {
			//必要に応じてAPI用のパラメータをセットする
			pref:PREF01
		})
		.done(function(result){
			//データ表示
			for ( var i in result.rest ){
				shop_image1: result.rest[i].image_url.shop_image1
				jQuery('.result').append('
  • '); } }) }); });

    まとめ

    非同期通信の勉強を始めたばかりで、この投稿でご紹介したphpと連携するやり方も今まで知りませんでした。

    慣れた方にとっては標準的なやり方なのだろうと思いますが、これはなかなかおもしろいですね。非同期通信関連の処理は他にもいろいろと面白い機能があるようで、今後も楽しみに勉強を続けて行きたいと思います!

    jQuery.ajax()を使ってクリックイベントで投稿を取得。WP REST APIを試してみました。

    JSON形式のデータを取得できるWP REST APIというWordPressプラグインを使ってクリックイベントでWordPressの投稿を取ってくるサンプルコードを作成しました。

    このプラグインを使ってサイトのホームのurl+/wp-json/wp/v2にアクセスするとJSON形式のデータが返却される機能が提供されます。更にパラメータを追加してWP_Queryのように投稿を取得することもできます。このデータを利用して、以下の様な記述で投稿を取得できました。

    demo

    JavasScript(jQuery)

    //最新の投稿6件を取得するスクリプト
    jQuery(function() {
     
    //jsonデータが返却されるurl
    var APIurl = 'https://stella-design.biz/wp-json/wp/v2/posts/?filter[posts_per_page]=6';
     
    //投稿データ表示
    jQuery('.btn-api-load').on('click', function(){
    	jQuery('.api').empty().removeClass('fade-in');
    	jQuery('.loading').removeClass('hidden');
    	setTimeout(function(){
    		ajaxGetPost();
    	},2000);
    });
     
    //投稿データ取得 
    function ajaxGetPost(){
    	jQuery.ajax({
    		type: "GET",
    		url: APIurl,
    		dataType: "json"
    	})
    	.then(
    		function(json){
    			for(var i in json){
    				jQuery('.api').append('
  • ' + json[i].title.rendered + '
  • '); } }, function(){ alert("データをロードできませんでした。"); } ); jQuery(document).ajaxStop(function(){ jQuery('.loading').addClass('hidden'); jQuery('.api').addClass('fade-in'); }); } });//jQuery end

    html

    
    <button class="sd-btn btn-api-load">click</button>
    <div class="develop-block">
        <ul class="ul api"></ul>
        <div class="loading hidden">loading</div>
    </div>
    

    css

    
    @-webkit-keyframes ani-opa {
    	0% {
    		opacity: 0;
    	}
    	50% {
    		opacity: 1;
    	}
    	100% {
    		opacity: 0;
    	}
    }
    @keyframes ani-opa {
    	0% {
    		opacity: 0;
    	}
    	50% {
    		opacity: 1;
    	}
    	100% {
    		opacity: 0;
    	}
    }
     
    .loading {
    	margin: 15px 0;
    	-webkit-animation: ani-opa 1s infinite;
    	animation: ani-opa 1s infinite;
    }
     
    .hidden {
      visibility: hidden;
    }
     
    .api {
    	opacity: 0;
    	-webkit-transition-property: opacity;
    		  transition-property: opacity;
    	-webkit-transition-duration: 2s;
    		  transition-duration: 2s;
    	-webkit-transition-timing-function: ease;
    		  transition-timing-function: ease;
    	-webkit-transition-delay: 0.1s;
    		  transition-delay: 0.1s;
    }
     
    .fade-in {
    	opacity: 1;
    }
    
    .btn-api-load{
    	margin-top: 15px;
    }
    
    .develop-block{
    	padding: 30px 0;
    }
    

    昨年春のマット・マレンウェッグ氏のイベントによると、WP REST APIがWordPressのコアにマージされる時期が近づいているようです。

    JavaScriptを使ってスマートフォンネイティブアプリに近いUIを備えたwebサイトは今後ますます増えていくと個人的には思っています。その流れに対応できるよう、今年は昨年以上にがっちりJavaScriptに取り組んで行く予定です。

    しかしながら、JavaScriptをやればやるほど、サーバーサイドスクリプトの知識の必要性を感じるようになりますね。両方出来たほうがよりよいものが作れる。ひとまずはPHPももっとがんばって、WEBサービス的なものを作れるようになれたらいいな、と思い始めています。

    ぐるなびAPIを使ってJavaScript(jQuery)で動作するシングルページアプリケーションを作ってみました。

    ご存じの方もいらっしゃると思いますが、ぐるなびというレストランの検索サイトでは、レストラン検索などができるAPIを公開しています。(ぐるなびWeb サービス for Developers)

    APIの勉強がしたいと思っていろいろ調べていたところ、このAPIにたどり着きました。解説ページの作りが非常によく、サンプルコードもわかりやすかったので、このAPIを使わせていただくことにしました。

    都道府県別カフェ検索アプリを作る。

    ぐるなびに登録されているカフェを都道府県ごとに検索できるごく簡単なWebアプリケーションを作ったのでデモページとコードを公開してみます。

    上記デモURLにアクセスすると、東京都のカフェ一覧が表示されます。クリックするとぐるなびの店舗紹介にリンクします。

    「さらに表示する」ボタンをクリックすると次の10件のデータを取得して表示します。

    都道府県を「変更する」ボタンをクリックすると、文字通り都道府県が切り替わって、条件にマッチしたデータを取得して表示します。

    JavaScript(jQuery)

    JavaScript初学者が作った学習用のコードのため、間違いや非効率なコードが含まれている可能性があります。ご了承ください。ご指摘、ご助言いただけますと非常にありがたいです!コメント欄をご利用ください。
    //masonryも併用しています
    jQuery(function() {
    
    (function(){
    
    	/**************************************************************************************
    	 * 変数定義
    	 **************************************************************************************/
    
    	// masonry 初期設定
    	var $container = jQuery('.grid'); //wrapper要素指定
    	var $inner = '.grid-item'; //inner要素指定
    
    	// API
    	var api_key = '********************************'; //アクセスキー
    	var hit_per_page_num = 10; //一度に表示する件数
    	var offset_page_num = 1; //初期ページ
    	var pref_name_ini = 13; //都道府県初期設定 (PREF13=東京都)
    	var pref_name_key = 'PREF' + pref_name_ini; //都道府県名キー作成
    
    	// API URL
    	var url_rest = 'http://api.gnavi.co.jp/RestSearchAPI/20150630/?callback=?'; //レストラン検索API
    	var url_pref = 'http://api.gnavi.co.jp/master/PrefSearchAPI/20150630/?callback=?'; //エリアマスタ取得API
    
    	//API 基本パラメータ
    	var params = {
    		keyid: api_key,
    		format: 'json'
    	};
    
    	// API 店舗データ取得用パラメータ設定
    	var params_shop = jQuery.extend({ }, params);
    	params_shop.pref = pref_name_key; //都道府県設定
    	params_shop.freeword = 'コーヒー,カフェ,珈琲,喫茶,喫茶店'; //キーワード設定
    	params_shop.hit_per_page = hit_per_page_num;
    	params_shop.offset_page = offset_page_num; //ページ数
    
    	// API 店舗データ数判定
    	var resultLooplength = 0;
    
    
    
    	/**************************************************************************************
    	 * masonry データ配置処理
    	 **************************************************************************************/
    
    	var preLoad = function(){
    		$container.imagesLoaded(function(){
    			$container.masonry({
    				itemSelector: $inner, //タイトル状に配置する要素のclassの指定
    				isFitWidth: true, //親要素の幅に合わせてタイル状のコンテンツ数を自動調整
    				isAnimated: false //伸縮時のアニメーションの設定
    			});
    		});
    	};
    
    
    
    	/**************************************************************************************
    	 * API 店舗データ 出力
    	 **************************************************************************************/
    
    
    	//API 店舗データ出力
    	var resultLoop = function(result){
    
    		for ( var i in result.rest ){
    
    			var elm = {
    				name: result.rest[i].name, //店舗名取得
    				shop_image1: result.rest[i].image_url.shop_image1, //画像取得
    				url: result.rest[i].url, //ぐるなび店舗詳細ページURL取得
    				address:result.rest[i].address //店舗住所取得
    			}
    			
    			var img_url = elm.shop_image1.toString();
    			//変数img_urlは、APIで画像が登録されていない場合のif文の条件分岐の値として使います。
    			//店舗によっては画像が登録されておらず、[object Object]というデータが返されます。
    			//[object Object]は、調べたところオブジェクトの型を表現しているようですが、よくわかりませんでした。
    			//toString()メソッドを使って文字列に変換すると同値演算ができるようになったので、
    			//最善では無いと思いますが、ひとまずこのような変数を作りました。
    
    			if(img_url === '[object Object]'){
    				preLoad();
    				jQuery('.result').append('
  • ' + elm.name + '
    ' + elm.address + '
  • '); } else { var img_li = '' + '
    '; preLoad(); jQuery('.result').append('
  • ' + img_li + elm.name + '
    ' + elm.address + '
  • '); } resultLooplength++; if( resultLooplength >= result.total_hit_count ){ jQuery('.btn--more-load').after('

    全データを表示しました。

    ').addClass('none'); resultLooplength = 0; } } params_shop.offset_page++; }; //API 取得件数を表示 var resultNum = function(result){ if ( result.total_hit_count > 0 ) { jQuery('.total').html( result.total_hit_count + '件のお店が見つかりました。\n' ); } else { jQuery('.total').html( 'お店が見つかりませんでした。' ); } }; /************************************************************************************** * API 都道府県データ 出力 **************************************************************************************/ // API 地域名初期表示 var farstPrefTitle = function(result){ var pref_title = result.pref[pref_name_ini - 1].pref_name; jQuery('.pref_title').html(pref_title); }; //現在の地域名を更新 var changePrefTitle = function(val,result){ jQuery('.pref_title').html(val); }; //API 都道府県データをselect要素として生成する var resultPref = function(result){ for ( var i in result.pref ){ var name = result.pref[i].pref_name; var code = result.pref[i].pref_code; jQuery("[name = pref_name]").append( '' ); } }; /************************************************************************************** * APIデータ取得/イベント実行 **************************************************************************************/ //ページがロードされたら jQuery(window).one('load', function(){ //店舗データを取得し表示する jQuery.getJSON(url_rest, params_shop, function(result){ //店舗件数を表示 resultNum(result); //店舗データ表示 resultLoop(result); //ページ数を更新 offset_page_num++; }) //都道府県データを取得し表示/セットする jQuery.getJSON(url_pref, params, function(result){ farstPrefTitle(result); resultPref(result); }) }); //指定要素がクリックされたら次のデータを取得し表示する jQuery('.btn--more-load').on('click', function(){ //APIデータを取得 jQuery.getJSON(url_rest, params_shop, function(result){ //店舗データ表示 resultLoop(result); //masonryを再配置 $container.masonry('reloadItems'); }) }); //指定要素がクリックされたら都道府県を変更して店舗データを再取得し表示する jQuery('[name = pref_btn]').on('click', function(){ //店舗データ数判定を初期化する resultLooplength = 0; //「全データ取得済み」表示を削除する jQuery('.vew-end').remove(); //データ取得ボタンを再表示する jQuery('.btn--more-load').removeClass('none'); //で選択されている要素(都道府県名)を取得 var selectPref_name = jQuery('[name = pref_name] option:selected').text(); //商品データ一覧を初期化 $container.empty(); //ページ数を初期化 params_shop.offset_page = 1; //都道府県パラメータを都道府県データ取得API用オブジェクトにセット params_shop.pref = pref_val; //現在の地域名を更新 changePrefTitle(selectPref_name); //APIデータを取得 jQuery.getJSON(url_rest, params_shop, function(result){ //店舗件数を表示 resultNum(result); //店舗データ表示 resultLoop(result); //masonryを再配置 $container.masonry('reloadItems'); }) }); }).call(this); });

    html

    
    <!DOCTYPE html>
    <html lang="ja">
    <head>
    	<meta charset="UTF-8">
    	<meta name="viewport" content="width=device-width" />
    	<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
    	<link href="style.css" rel="stylesheet">
    	<script src="http://code.jquery.com/jquery-1.11.2.min.js"></script>
    	<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/jquery.imagesloaded/4.1.0/imagesloaded.pkgd.min.js'></script>
    	<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/masonry/4.0.0/masonry.pkgd.min.js'></script>
    	<script type='text/javascript' src='js/gnavi_api.js'></script>
    	<script type='text/javascript' src='js/custom.js'></script>
    </head>
    
    <body>
    
    	<article class="wrap">
    
    		<header class="header">
    			<div class="inner">
    				<h1>都道府県別カフェ検索<br />(ぐるなびAPIサンプルコード)</h1>
    				<h2 class="pref_title"></h2>
    				<p><span class="total"></span></p>
    				<div class="pref_search">
    					<h3>都道府県変更</h3>
    					<select name="pref_name"></select>
    					<button type="button" name="pref_btn">変更する</button>
    				</div>
    				<p class="text">このサイトはぐるなびAPIを使って作ったSPAのサンプルサイトです。ぐるなびに掲載されているカフェを、都道府県ごとに表示させる機能を提供します。</p>
    				<div>
    					<a href="http://www.gnavi.co.jp/"> 
    						<img src="http://apicache.gnavi.co.jp/image/rest/b/api_155_20.gif" width="155" height="20" border="0" alt="グルメ情報検索サイト ぐるなび">
    					</a>
    				</div>
    			</div>
    		</header>
    		<div class="main-content">
    			<section>
    				<ul class="ul result grid"></ul>
    				<input type="button" class="btn--more-load" value="さらに表示する" />
    			</section>
    		</div>
    
    		<footer>
    			<div class="container">
    			</div>
    		</footer>
    		<div class="go-to-top"><a class="go-to-top_body" href="#">▲<br />もどる</a></div>
    
    	</article>
    
    </body>
    </html>
    

    ※CSSは割愛します。

    雑感

    イベントの実行が煩雑になってしまったので、もっとすっきりわかりやすくできたらいいと思いました。デザインパターンを勉強すると良い気がします。

    addEventListenerの使い方を覚えるといいのかな?時間を取って調べて行きたいですね。MVCフレームワークも覚えられたらいいですね。

    あと、APIの仕様で残念なことが一つありました。それは、店舗の画像を表示させるパラメーターが2種類しかなく、しかもどんな画像なのか事前にわからない仕様だったこと。

    当初のイメージでは、コーヒーそのものまたは室内の様子がわかる写真を並べて写真で魅せるデザインにしたかったのですが、ロゴや犬の写真が出てくることもあって(笑)、コントロールできなさそうでした。

    食べ物、飲み物、店内、外観、それらをキーワードで指定、とかできたらなお良いですね。

    [JavaScript]animate.cssとjQeuryを使ってスクロールに応じて要素を動かす関数を作ってみました。

    縦長にデザインされたサイトで、スクロールするとフェードインしたり震えたりジャンプしたりいろいろ動いてクリックを誘導するデザインはポピュラーですよね。

    よく使うものなので、簡単に使いまわせるように関数を作ってみました。

    サンプル

    demo

    codepen

    使用するライブラリ

    jQuery
    animate.css

    animate.cssは、要素に様々なエフェクトを与えるCSSのみで書かれたライブラリ。headで読み込んで、動かしたい要素にclassを割り当てるだけで動いてくれるびで、とても便利です。

    今回のサンプルでは、bounce(跳ねる)、pulse(膨らんでしぼむ)、swing(揺れる)、fadeIn(フェードイン)の4つを使ってみましたが、animate.cssの配布元サイトには数十にも及ぶエフェクトのサンプルが用意されています。

    html

    <html>
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width" />
    <link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
    <link href="animate.css" rel="stylesheet">
    <script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
    </head>
    <body>
        <div class="container">
            <div class="col-xs-12">
                <h1>Please scroll ↓↓↓</h1>
                    <p class="ani-bounce">bounce</p>
                    <p class="ani-pulse">pulse</p>
                    <p class="ani-swing">swing</p>
                    <p class="ani-fadeIn">fadeIn</p>
                    </div>
    	</div>
    </body>
    </html>
    

    CSS

    h1{
      margin-bottom: 800px;
    }
    
    p{
      font-size: 24px;
      text-align:center;
      padding: 60px 30px;
      margin: 90px 180px;
      border: 5px solid #CCCCCC;
      background-color:#FFFFFF;
    }
    

    JavaScript

    function animate($ani, $valPos) {
    
    	if ($ani === 'fadeIn') {
    		$(".ani-" + $ani).css("opacity", "0");
    	}
    
    	$(".ani-" + $ani).each(function() {
    
    		var imgPos = $(this).offset().top;
    		var scroll = $(window).scrollTop();
    		var windowHeight = $(window).height();
    
    		if (scroll > imgPos - windowHeight + windowHeight / $valPos) {
    			$(this).addClass("animated " + $ani);
    		} else {
    		$(this).removeClass("animated " + $ani);
    		}
    
    	});
    
    }
    
    jQuery(window).on('touchstart scroll', function(){
    
    	//timerID;
    	var timer = false;
    
    	if (timer !== false) {
    		clearTimeout(timer);
    	}
    
    	timer = setTimeout(function() {
    
    		animate('bounce','3');
    		animate('fadeIn','7');
    		animate('pulse','5');
    		animate('swing','5');
    
    	}, 100);
    
    });
    

    スクロール量に応じて引数に指定したanimate.cssのclassを付与したり外したりする関数animate()を、scroll(touchstart)イベントの中で呼び出しています。

    scrollイベントはブラウザに負荷がかかるってことで、setTimeout()を使って負荷を減らす処理を入れてみました。

    参考にさせていただきました。

    jQueryとCSSのtransitionで可視範囲に入ってからアニメーションさせる方法|Webpark
    [jQuery] ウインドウのリサイズ操作が終わった時にだけ処理を実行する | CreativeStyle

    [WordPress][wp_localize_script()]WordPressで外部JSファイルにテーマファイルのURLを渡す方法。

    WordPressで、ブラウザのウィンドウサイズを判定して、サイズにより異なるjsファイルをロードさせる方法を調べてみました。

    その際、wp_localize_script()という関数の存在を知り、調べたことをまとめてみます。

    global.js

    global.jsというjsファイルを作り、以下の記述でウィンドウサイズを判定してjsフィアイルをロードします。

    // global.js
    jQuery(function() {
    
        //PC環境の場合
        if (window.matchMedia( '(min-width: 769px)' ).matches) {
            jQuery.ajax({
                url: pc.url,
                dataType: 'script',
                cache: false
           });
    
        //モバイル環境の場合
        } else {
            jQuery.ajax({
                url: mobile.url,
                dataType: 'script',
                cache: false
            });
        }
    });
    

    参考:レスポンシブデザイン対策!デバイスのサイズに応じてjava scriptを呼び変える

    window.matchMedia()でウィンドウサイズを判定(IE9以下は未対応のようです)。

    ajax()を使い、ロードしたいjsファイルのパスを記述するのですが、WordPressだと通常はテーマフォルダの中にjsファイルを置くと思ので、動的に記述する必要がありますよね。

    jsファイルの中にパスを呼び出すテンプレートタグは記述できないので、以下のようにhead要素でwp_localize_script()を使ってglobal.jsに渡します。

    head要素

    <?php // ※head要素内に記述
        wp_deregister_script('jquery');
        wp_enqueue_script('jquery', get_template_directory_uri().'/js/jquery-2.1.1.min.js','2.1.1');
        wp_enqueue_script('global', get_template_directory_uri().'/js/global.js',array('jquery'));
        wp_localize_script('global', 'pc', array('url' => get_template_directory_uri().'/js/pc.js'));
        wp_localize_script('global', 'mobile', array('url' => get_template_directory_uri().'/js/mobile.js'));
    ?>
    

    参考:Javascriptでサイトのデータを扱う方法

    wp_localize_script()とは?

    WordPressには、PHPからJavaScriptにデータを渡すwp_localize_scriptという関数がある。元々は多言語対応のために、JavaScriptに翻訳文字列を渡すための関数だった。現在では翻訳文字列にかぎらず、さまざまなデータをPHPからJavaScriptに渡す用途で用いられる。

    引用:wp_localize_scriptの生成するJSONはHTMLエスケープされない

    今回初めて知りましたが、なかなか便利な関数ですね。JavaScriptに渡したいデータをJSON形式に変換するそうです。エスケープされていないので注意する必要があるようです。

    JSONはあまり使ったことが無いので詳しくないのですが、フォームから入力されたデータをJavaScriptに渡すような場合、変換されたJSONデータをを無害化した方がよい、ということでしょうか。