Marionette.js(Backbone.js)のチュートリアル with yeoman その3(Marionette.View実装編)
チュートリアルの続き。
さんざん伸ばした挙げ句、ようやくここからが、本番。
MarionetteのViewを使う。
また、長い記事になったので、先にソースあげておく。
これから作るもの
前回、新規作成formを作ったので、
新規作成したtodoを表示するViewを作る。
- liタグで、一つずつのTaskを表示する
- ulタグで、それらを束ねる
liタグ用のView(1つ1つのTask)
generatorをまた使う。
$ yo marionette:itemview task --create-all
--create-allも付けると、templateも作ってくれる。
出来上がったソースに対して
- tagName: liを紐づける
liをクリックしたらfinished:trueにして、線を引きたいので、
- clickされたら、finished:true/falseを切り替える
- model変更したら、finishedに応じたtrue/falseを切り替える
この辺りはいつものbackboneのソース。
define([ 'backbone', 'hbs!tmpl/item/task_tmpl' ], function( Backbone, TaskTmpl ) { 'use strict'; /* Return a ItemView class definition */ return Backbone.Marionette.ItemView.extend({ tagName: "li", initialize: function() { this.changeFinishedStyle(); this.model.on('sync', this.changeFinishedStyle, this); }, template: TaskTmpl, /* Ui events hash */ events: { "click" : "onClickTask" }, onClickTask: function(event) { if(this.model.get('finished')){ this.model.save({"finished": false}) } else { this.model.save({"finished" : true}); } }, changeFinishedStyle: function() { if(this.model.get('finished')){ this.$el.attr('style', 'text-decoration: line-through;') } else { this.$el.attr('style', 'text-decoration: none;') } }, }); });
あわせて、templateもいじっておく。
//templates/item/task_tmpl.hbs'
<span>{{title}}</span>
ul側(親側)のView
次に、collectionViewを準備する。
$ yo marionette:collectionview tasks
出来たソースにitemviewとして、
- tagName:ulタグを指定
- さっき作ったviewを指定する.
define([ 'backbone', 'views/item/task' ], function( Backbone, Task ) { 'use strict'; /* Return a ItemView class definition */ return Backbone.Marionette.CollectionView.extend({ itemView: Task, tagName: "ul" }); });
めっちゃ少なくて済む。
よく書くcollectionに同期してrenderとか書かなくて済むとこんだけで済む。
collectionの追加
またまたgenerator使う
$ yo marinoette:collection tasks
url/modelだけ、指定しておこう。
url: "/api/v1/tasks", model: Task,
htmlの準備
ここでsubmitしたtasksを表示するためのdiv領域を準備する
※この下に、ul > liを広げて行く。まあ、ここをulにしても良いけど、まあ、areaという事で...
diff --git a/app/index.html b/app/index.html @@ -29,6 +29,8 @@ </div> + <div id="tasks"> + </div> <!-- build:js scripts/main.js -->
次に、application.jsでこのdiv領域にtasksViewを紐づける。
- /app/scripts/application.js
App.addRegions({ newTodo : "#new-task", tasks : "#tasks" }); App.addInitializer(function() { this.collection = new TasksCollection(); this.collection.fetch(); App.newTodo.attachView(new NewTaskView({el: App.newTodo.el, collection: this.collection})); App.tasks.show(new TasksCollectionView({collection: this.collection})); }); App.addInitializer( function () { Communicator.mediator.trigger("APP:START"); });
えーっと、ここまでで、多分、その1で保存したデータが初期表示されてるはず。
form送信viewと繋げる
form送信用のviewからは、taskモデルをsaveしてcollectionに追加する
define([ 'backbone', 'communicator', 'models/task' ], function(Backbone, Communicator, Task){ 'use strict'; return Backbone.View.extend({ events: { "keypress": "postTask" }, postTask: function(event){ if(event.keyCode == 13){ var task = new Task({title: this.$el.val()}); task.save(); this.collection.add(task); this.$el.val(''); } } }); });
ここまでで、多分、formに入れると追加/保存が出来てるはず。多分。
filtering
ここからrouterを使って、all/finishedだけを絞り込む画面を作って行く。
画面filter用のaタグを追記する
<div class="hero-unit"> <input type="TEXT" id="new-task" placeholder="ここに何か書いてEnter"></input> <div> <a href="#all">all</a> | <a href="#finished">finished</a> </div> </div>
router
ここから、routerを作る。
$ yo marionette:router tasks
defaultで出てくるのが、Backboneのrouter。
大規模になってからAppRouter使えという事であえて生routerなのだろう。
せっかくなので、今回は、AppRouterに上書きして、Controllerと繋ぐ。
define([ 'backbone' ], function(Backbone){ 'use strict'; return Backbone.Marionette.AppRouter.extend({ /* Backbone routes hash */ appRoutes: { "all" : "all", "finished" : "finished" }, }); });
controller
AppRouterはcontrollerを使うので、controllerを作る
$ yo marionette:controller tasks
allの時は、全部fetch
finishedの時は、finished:trueの物だけfetch。
define([ 'backbone' ], function( Backbone ) { 'use strict'; return Backbone.Marionette.Controller.extend({ initialize: function( options ) { this.collection = options.collection }, all : function(){ this.collection.fetch(); }, finished : function(){ this.collection.fetch({data:{conditions:'{"finished":true}'}}); } }); });
最後に、Appにrouter追加する
App.addInitializer( function () { new TasksRouter({controller: new TasksController({collection: App.collection })}); Backbone.history.start(); });
これで、動いた?
github
ここに、置いた
https://github.com/lxyuma/todo-mario
marionetteのsampleも少ないので、参考迄に。
※marionetteのgeneratorの作者もsampleあるので、参考にすると良い
思った事。
こうして見ると、やっぱり
- CollectionViewのソースの少なさが嬉しい。
生Backboneだと、色々書き方もあったりするので、
そこに時間かかったりするのが、marionetteだと基本的な汎用的な所は書かないで済んだ。
- ItemViewのrender
render書かなくて済む=marionetteのtemplate * renderの仕組みに沿う=迷い無い。
renderの中、ごちゃごちゃ書き換える必要が無いのが良い
- Applicationの中のregion manager
el要素をあえて、Backbone.Viewも外出しにしたので、
こうしてみると、とりあえず、Applicationのregionを見ると、
どこにmarionette適用しているのかが、ぱっと見で判然。素晴らしい。
ViewでもAreaに埋め込んだ方が良いと思う。
- AppRouter
今回のような小さなアプリはBackbone.routerをそのまま使っても良い。
沢山routesが増えてきたら、AppRouterとControllerに分けて、
更にもっと増えたら、複数のAppRouterとControllerに分けるべき。
普通のrailsアプリにbackbone適用して思った事その2
普通のrailsアプリにbackbone適用して思った事の続き
6)深い階層のView
BackboneのView書いてると、どうしても、親子階層が必要になってくるのは、前回の記事で書いた通り。
で、更に、書いてると、親子の子の中に更に別のViewクラスとか、親子Viewとか埋めたくなってくる。
画面次第なのだが、Viewの作りとして、あるべき構成だし、その方が正しい。のだが、
最終的に、振り返ると、どこにどのViewがあるのか、全体像が掴みにくくなる。
辿るには、親から確認していかないといけない。
うーん。
イマイチである。
こういうの、Marionetteのlayoutあると助かる。
layout/Region見れば、全体像が分かるし、layoutの中にlayout組めるのも、
こういった階層化がよくあるからなのだろう。
7)誰かと一緒に作業するには、やっぱり、template必要
初めに何度か書いた通り、当初気軽に使う予定で、
ちょっとしたUIはrender内で$('<div>')で作って貼付ければ良いや!と思ってた。
が、これ書いてると、あれもこれも追加してしまい、結論、renderが長くなり、
最終的な画面imageが分かり辛く、誰かと共同作業やりづらくなる。
templateにすると、第3者でも最終系がimageし易く、編集もし易い。
とにかく、template積極的に使うべきという話。
8)逆引きは公式pageでなくて、生ソース追跡
開発していると、Backboneの動きで分からない所、
意図通りに行かない所が出て来る。
Backboneの公式ページは簡易すぎて細かな動きがよくわからない。
Backboneのソースは1fileで1500行しかないので、
doc読むより本体のソースをgrepして確かめるのが、一番早くて正確。
9)生産性は短期的には高まらない。
元々書いていたCoffeeのclass + jQueryダラダラソースと比べて、短期的な開発スピードという意味では落ちた。
これは、構成考えないといけなかったり、幾つか要因があると思うが。
なので、例えば、ちょっとしたHP作りでちょっとしたjs程度ならBackbone使わなくて良い。使っても単にスピード落ちるだけ。
じゃあ、いつBackboneが必要なのか?というと、
- ある程度js書かないといけなくて、
- 今後も保守改修していかなくてはいけない時。
その条件満たしているのであれば、
module毎に分かれて整理されるので、
- 改修し易くなり、
- テストも書き易くなり、
改修時の工数も含めた全体を見た時に生産性上がる。
例えば、開発してても仕様変更が幾つかあったが、これも気軽に実施できた。
一度js書いてもう触れない見たくないみたいな状態にはならない。
テストもしっかり書けるので、衛生的で品質も良い状態になってくれる。
こんな感じで、条件を満たして、改修も含め全体を見たら生産性やら品質やら上がってると思う。
ただ、とにかく、初めに作ってる分には、勿論、jqueryダラダラ書くよりは時間かかるので、ご注意を。
結論
- 現場適用:今時のjsガリガリ書くwebサービスならBackbone付けた方が良い
- Marionette?:いきなり、初めからBackbone+Marionette使うべき。Backboneだけだと管理大変。
- View親子:Mとelを繋げる為に親子階層が必要。
- renderの自由さ:振り回されるので、なるべくMarionetteの標準renderをメインで使って自分で書かない事。結局そこに行き着くし、これに沿うと構成が決まる。
- 偽物render:を作らない事
- 深い階層View:marionetteのlayout/Region使え。
- template:なるべく使うべき。render内でごにょごにょ書かない。
- 逆引き:Backbone本体をgrepが早くて正確。
- 生産性:短期的にはダラダラjQueryより時間かかる。その後の改修時も含めて全体見ると生産性高まるが。
とにかく、marionette使うべきという話。
追記
この続きを書いた
普通のrailsアプリにbackbone適用して思った事
普通のrailsのwebアプリにbackboneを埋め込んでみて思った事等を書く。
賛否あるかもだが、あくまで個人的な意見。
1)ウチの現場でBackboneを使うべきか否か?
どこも初めに考える事。
大規模になったら使えとか、Single Page Applicationなら使えとか、色々意見あると思うが、
今時の普通のwebアプリならjsガリガリ動くので、迷わずBackbone使っていいと思う。
※単調なB向けの基幹システム作ってるとかは例外として。
どこで使う?
別に全てのpageでBackbone使う必要は無いけど、
フォーム送信系で色々js動かす所とか、検索条件指定、絞り込みとか、
画像/詳細表示、LightBoxとか、
こういう所でjsガリガリ書かないと行けなくなったら
Backboneでモジュールを整理していけばいいと思う。
実際使ってみてどうだったか?
before
元々、自分たちの現場では、coffeescript + classで画面毎にjsファイル区切って、
(要はrails generatorがはいてくれるfile内で) jQueryをダラダラ書いていた。
別にsingle page applicationでもない普通のwebアプリなのだが、
非常に長いjs(coffee)ファイルが複数あり、
作ってる時は良いのだが、改修する時が非常に大変だった。
最悪、触れない。(特に人の作った物で、癖が強い物)
触れなければ作り直せば良いのだが、そうすると、次に、
古いソースを気軽に削除できない問題が出て来る。(どこまで消せるか分からない)
恥ずかしい話だが、しばらく経つと、jsのゴミコードが結構残ってる。
after
これらが、Backboneにする事で、
- View構成要素毎に処理が分かれて分かりやすくなって
- イベントの動きが分かり易い(Events見れば分かる
- 画面描写何やってるかもパッと見で理解できる(template + render())
- 気軽にajax書くようになって、usability向上に貢献!
そう、ハッピーになれた! ※beforeに対してね。新たな悩みが出てきたが。
2)BackboneかMarionetteか?
Backbone単体か、Marionetteまで使おうか?
どうしようかなーと初めは悩んだ。
marionetteとは?
これね。
Marionetteの美味しい所
Marionetteの美味しい所を端的に言うと、
- Viewの階層管理(親子、tree)
- layout管理
- メモリ管理
で、自分のアプリがどうなるのか?考えれば良い。(と初め思ってた)
※後述するが、もう一つ、重要な事があった。
当初考えていた事
Marionetteの美味しい所に対して当初考えていたのは
- Viewの階層管理
- 階層作る程大掛かりな作りにしたくない
- TODOMVCのソースみたいに、なるべく少ないViewクラスの中で多くのUIを扱うような気軽な作りにしようと思ってた
- layout管理
- そんなに大掛かりに作らない。Event紐付けして少しrenderする位。全体的な管理要らない気がしてた。
- メモリ管理
- 厳密なsingle page appliじゃないからメモリ管理そこまで気にしない。
結論。これらの美味しい所は要らないと思ってた。
なので、Backboneで良いと思ってた。
今振り返れば。
Marionetteの美味しい所に対して今振り返れば
- Viewの階層管理
- 後述するが、結局、楽しようとすると、色々遠回りになるので、結局、モジュール分割して階層化が必要だった。
- layout管理
- 上の話の続きで、結局一画面の中での構成要素が分かり辛いので、layout管理が有った方が良い。
- メモリ管理
- ここは、とりあえず困ってない。普通のwebアプリならあまり気にしなくて良い。(作り次第だが)
あと、重要なのが、Marionetteはrenderを初めから決めている所。これも後述する。
3)Viewの階層について
初めに上記に書いた通り、少ないViewで気軽に使いたかった。
1画面1viewとまで行かないが、1画面内で意味上で分けた少ないViewがあって、
この中にEventsまとめて画面描写が必要な時だけrenderするような軽いイメージだった。
が、なんだかんだ言って、button押す=>データを”複数”取得=>表示する
という箇所が非常に多く、ここで、結局、VIewを親子で分けざるを得なかった。
Viewの親子階層
なんで、親のulタグと子供のliタグみたいなのを分けないといけないのか?というと、
親子を一つのviewで管理すると、個別のmodelとviewのマッピングが毎回の処理毎に必要で、
これがスゲー汚い。DRYじゃない。
viewでelとmodel準備しているのは、このmappingを楽にする為?なのかもしれない。
という事で、親子で画面描写しているような所は初めから親子クラスで書いてしまった方が良い。
marionetteで言うCollectionViewとItemView。初めから、これ使えば良かったんじゃんか。
4)renderの自由っぷり
renderがとにかく自由に書けるのがBackboneの良い所であり悪い所でもある。
自由で何でも書けるので、どんなアプリでも柔軟に対応でき、
気づけばダラダラ汚いソースを書いてしまったりする。
初めは気軽に使うつもりだったのだが、上記親子階層の件等から、結局viewが構成要素毎に分かれた。
色々render書いたが、構成見直しに伴い、結構書き直して時間食ってしまった。
そして、「あれ?この要素全部templateに入れれるな?」とか色々気づいて行く。
そして、気づければ、それぞれtemplateもばっちり持ってしまった。
あれ?
結局、最終系がmarionetteのrenderと変わんなくねーか?と。
自由に書いて、行き着く先がsimpleな形で、これがFWとして外出しされているなら、
初めから、このFWに沿っていれば、余計な悩みが無かったと思う。
つまり、初めからMarionette使ってrender楽したら、逆にそこから今の構成が決まってたんじゃねーかと思う。
5)なんちゃってrender
よくBackboneのsampleでイベントの実行する関数の中で画面描写系の処理書いている物があるが、
これ、なんか間違ってると思う。
render()が画面描写という共通認識があるから、
何も知らないviewクラス読む時でも、render見ればそのViewの描写処理が全部分かる。
それを破ると、どんな画面が出来るのかimageが湧かない。
だから、とにかくrenderに画面描写を書くべき。
例外
でも、どんな事にも例外はあって、どうしても、そのviewのrenderとは別の描写させたい事がある。
この場合、そのviewがstatusとなる変数を持って、
renderの中で、そのstatusを分岐して、あくまで、renderの中だけで全部書くべきだと思う。
この処理が複雑になったら、Viewクラスを分けた方が良いと思う。
っていうか、todomvcのtodoも、renderの中で、分岐がある。
(このソースは少しこのブログと文脈違うのだが、)このimageでいいと思う。
※ちなみに、ある要素をshowするhideするみたいな事は、普通の関数でもやっていいと思う。とにかく、renderにどんな画面が出来るかのimageを残してあげるのが、次の誰かに仕様を伝える上で大切だと思う。
一旦、まとめ
何か長くなったので、ここまで。
とにかく、ここまでをまとめると、
気軽にBackbone使おうとしても、結局行き着くのは、marionetteっぽい作りなので、
初めからMarionette使ってしまっていいと思う。
逆に決まったruleに沿う事で構成が出来上がってくると思う。
※気に要らない所/無理のある所は自分でオーバーライドか生Backbone書けばいいし。
この方が悩んだり、書き直したりする時間削減できると思う。
また、この続きをいつか書く。
parse.comで始めるbackbone.js入門のslideをUPした
今年5月頃jsCafeで実施したBackbone.jsの入門slideをslideshareにUPした。
よくある、todoを作っていく内容なのだが、Parse.comというクラウドサービスを使う所が特徴。
これからBackboneの勉強始める方等、良ければ、どうぞ。
Marionette.js(Backbone.js)のチュートリアル with yeoman その2(Modelと基本View側実装編)
marionette.jsのチュートリアルの続き。
ここから、クライアント側の話。
先に言うと、メインのmarionetteは、この記事でなく、その3なので読んで飽きてきたら、その3に飛ぶべし。
前回に引き続き、gruntを起動している状態で進める事。
もし、起動していなければ、gruntを起動する。
model
初めにmodel を作る。
railsみたいに、MVC,routerそれぞれのgeneratorが準備されているので、勿論それを使う。
$ yo marionette:model task create app/scripts/models/task.js invoke mocha-amd:unitTest create test/spec/models/task.js conflict test/spec/testSuite.js [?] Overwrite test/spec/testSuite.js? overwrite force test/spec/testSuite.js
上書き聞かれたらenter!
これで、基本的なmodel + testのひな形が出来ている。
気になる人は、以下のファイルを見てみよう。
- app/scripts/models/task.js
- test/spec/models/task.js
ここから、ようやく、プログラミングしていく。
- さっき作ったAPIのendpointと繋げる
//app/scripts/models/task.js urlRoot: "/api/v1/tasks",
それと、重要なのは、mongodbはidが「_id」なのに、その設定が今のgeneratorに無いので、付ける
- ※近く、pull reqしておきます。
idAttribute: "_id";
画面作成
ここから画面を作る。
まずは、taskを新規登録するためのinputタグを作る。
//app/index.html <div class="hero-unit"> <input type="TEXT" id="new-todo" placeholder="ここに何か書いてEnter"></input> </div>
あと、余計なsample描写処理を消しておく
diff --git a/app/scripts/application.js b/app/scripts/application.js @@ -16,7 +16,6 @@ - document.body.innerHTML = welcomeTmpl({ success: "CONGRATS!" });
ここまでで、基本的な画面がブラウザに出来てるはず。
View
さて、このinputタグにtask追加用のイベントを
登録させたいので、Viewを追加する。
ここで、そろそろ、marionetteのItemViewを使いたい所だが、
この場合、単に追加イベントを登録したいだけで、
render処理やtemplateも要らないので、BackboneのViewで十分。
なので、BackboneのViewを使う。これもgenerator使う。
$ yo marionette:view new_task
注意:ハイフン区切りでなく、アンスコ区切りで。
ちなみに、ここで、元のgenerator-marionetteのページのように、new-task(ハイフン区切り)と書くと、こんなエラーになる。
Running "exec:mocha" (exec) task SyntaxError: Parse error >> Exited with code: 1.
これは、今の作りだと、そのままmochaのテストの変数もハイフンはいってしまいエラーになってるため。
なので、ハイフン区切り(とかラクダとか)でgenerator使った方がいい。※せっかく、generator使ってるのに一々直したら面倒臭いしね。文句がある人は、pull reqを...
Eventを追加する。
new_taskのviewに、eventを追加して、
enterボタン押された時のメソッドを準備する。
diff --git a/app/scripts/application.js b/app/scripts/application.js @@ -1,23 +1,26 @@ define([ 'backbone', 'communicator', - 'hbs!tmpl/welcome' + 'views/new_task' ], -function( Backbone, Communicator, Welcome_tmpl ) { +function( Backbone, Communicator, NewTaskView ) { 'use strict'; - var welcomeTmpl = Welcome_tmpl; - var App = new Backbone.Marionette.Application(); /* Add application regions here */ - App.addRegions({}); + App.addRegions({ + newTask: "#new-todo" + }); /* Add initializers here */ App.addInitializer( function () { Communicator.mediator.trigger("APP:START"); + App.newTask.attachView(new NewTaskView({el: App.newTask.el})); }); return App; }); diff --git a/app/scripts/views/new_task.js b/app/scripts/views/new_task.js @@ -5,8 +5,17 @@ function(Backbone){ 'use strict'; return Backbone.View.extend({ + el: "#new-todo", + events: { + "keypress": "postTask" + }, initialize: function() { console.log("initialize a NewTask View"); - } + }, + postTask: function(event){ + if(event.keyCode == 13){ + console.log("ENTERED!"); + } + }
- ItemViewではなく、ただの生のViewなので、自分でelとか追加しないといけない
- それでも、あくまで、regionにelを外出ししておくと、後で、自分のソースが読み易い。
- (全体として、どういう画面でできているのか、region見れば分かる状態になる。)
ここから
さて、これで、Modelと新規作成できるViewができた。
ここで、メインディッシュのMarionetteの
ItemViewとCollectionViewが出てくるのだが...
長くなったので、ここで、また一旦切る。
続き
GooglePlaceApiを使ってみるメモ。
GooglePlaceApi使ってみた。
ちょっと、まとまりなく、断片的な内容で、本当にメモ程度の記事。
GooglePlaceApiとは
GoogleMapでもできるじゃんと思うかもしれないが、
GoogleMapの検索でひっかかる場所は殆ど地名。
細かな店舗/有名スポット等々はGoogleMapではひっかからない。
そこでGooglePlaceAPIを使う。
2013年10月現在、まだ試験版なので注意。
参考
- 日本語公式ページ(但し、一部しかない)
- 英語公式
手順
概要
- プレイス検索:要するに4sqみたいな、場所×キーワードで検索
- テキスト検索:Googleで「新宿 ラーメン」でスポットがひっかかるようなイメージ。キーワードだけでスポットを検索する。コレが出来るのがこのAPIの強み。
- プレイス詳細:その通り、プレイスの詳細
勿論、他にスポット追加等色々あった。
ここでは、この3つ試してみる。
プレイス検索
とりあえず、プレイス検索してみる。
緯度経度と食べ物屋で検索してみる。
- 渋谷周辺の検索
- 上記のデータよりphoto/referenceを取得してみる
テキスト検索
Googleで「新宿 ラーメン」で検索しているのと同じイメージ
詳細検索
使ってみて気になった事
検索APIで、nameを指定しても、nameにひっかからない事が多数ある様子(特に全角が怪しい)
代わりに、keywordで同じ語を指定すると、精度が上がった。(こちらは全角でもちゃんとひっかかる)
ただ、それでも、1件目に本当に欲しい店舗がひっかかるかと言うと、そうでもないみたい。
例えば、渋谷の松屋を検索してるのに、ファミレスが一件目にひっかかったりする。
ruby gem
rubyの方はこちら。作ってる人がいた。
https://github.com/marceldegraaf/google_places
@client = GooglePlaces::Client.new(XXXXXXXXXXXXXXX) # XXXXXXXXXXXXXXXX = your key! @client.spots_by_query("渋谷 ラーメン") @client.spots(reference)
レスポンス
text searchの結果はこんな感じ。
データは名前・緯度・住所・ジャンル(type)等。
ここまでは普通だが、面白いのは、評価と価格LVがある。
※以下、面倒くさそうな所は******で伏せてる
{ "debug_info" : [], "html_attributions" : [], "next_page_token" : "*********************************", "results" : [ { "formatted_address" : "日本, 東京都渋谷区神南1丁目*****", "geometry" : { "location" : { "lat" : 35.663574, "lng" : 139.699362 } }, "icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/cafe-71.png", "id" : "c8f2e67090179c886318c8439990d828b18c3872", "name" : "カフェ ********", "price_level" : 2, "rating" : 3.9, "reference" : "CoQBeQAAACpzvbxMwKDC1NKwRAD3HVSw0nTReNSFwJgkCjkbjwOS5hXy-7WVzDHlxAbdIkHQIVwWHt22YRDKJAuk_zABPZMKXvC2-g16j3z4KVYPuJ7xgZuDdezT9si0TKBeZ2lS7wNT4dXeJ1abt-oOv06w1YlscMKig3L4G6Vbwz9ehaY7EhA9yXxRw_Fyx3Iv9y4ryXJYGhTJUN7n61MzhJmyqnkOw2x7U6F-yw", "types" : [ "cafe", "food", "establishment" ] }, { "formatted_address" : "日本, 東京都渋谷区宇田川町**********", "geometry" : { "location" : { "lat" : 35.662249, "lng" : 139.69551 } }, "icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/cafe-71.png", "id" : "54a323cfcaab07e75269324942da33604434d1b2", "name" : "カフエ ***** 渋谷店", "opening_hours" : { "open_now" : true }, "photos" : [ { "height" : 853, "html_attributions" : [], "photo_reference" : "CoQC-wAAANaUZDFBM1vcgeD7Nz3Qm668CFbQwbsxHe4TOlKipHCTrCc-e0Uu6eELescVdV57X5KhuL6n5s-BH3ViF4-KL7V_C5VW-4mpfNU-swDQpvPDkgvfFzKCKvrBF8_8rcL6wkHv2bC8E7nN6O0mYhZpjXyO5fxNLsHKJmjEBS0YnqFGKJKY04lsnvOHKhXBzNGVWZpf1QM4seRsV97o4EuINKQ_TZ5Hx-rPS4sIt-7K7yis0AGJvGj8EHI_48mTjvmCKcZuH6AhXiDsbxqu2j2t8i0hvPmHYOXcTL17-Q40rRiIAiQFIvavK2IIa2NVCCQVe_Hj65ATLfpZuOcSQltuAUwSEKNi8fLqeiFWtgEbqmHk0_saFBclFLUDPM2yWQt6i57yzm_slVmL", "width" : 1280 } ], "rating" : 4.1, "reference" : "CoQBfQAAAGkUh8-vFldUs6xEA1tFyuF9sk49Xxos_h7FqARyVaBJ1gtiwRyA-wfDxNJVgN0pmOHkNe1LgJq4Tx-8Q-1duO5zz3E8YWCtNGxPuMwKxrVPYczzEEXWYu46Z0r2KfncdxF-UFu_ej1w24MW9vX7LGHZv3VDwy0CsOMs9MMOOVvlEhClHH6RaYTNHngU3_fZ0SBTGhRXY5c2vjgHze0Kk3KSoz36FqTaAw", "types" : [ "cafe", "food", "establishment" ] },
詳細
以下が、スポット詳細のレスポンス。
結構、口コミが入ってる。
{ "debug_info" : [], "html_attributions" : [], "result" : { "address_components" : [ { "long_name" : "************", "short_name" : "****************", "types" : [ "street_number" ] }, { "long_name" : "吉祥寺本町", "short_name" : "吉祥寺本町", "types" : [ "sublocality", "political" ] }, { "long_name" : "武蔵野市", "short_name" : "武蔵野市", "types" : [ "locality", "political" ] }, { "long_name" : "東京都", "short_name" : "東京都", "types" : [ "administrative_area_level_1", "political" ] }, { "long_name" : "JP", "short_name" : "JP", "types" : [ "country", "political" ] }, { "long_name" : "180-0004", "short_name" : "180-0004", "types" : [ "postal_code" ] } ], "formatted_address" : "日本, 東京都武蔵野市吉祥寺本町******* 吉祥寺*************ビルB1", "formatted_phone_number" : "****-**-****", "geometry" : { "location" : { "lat" : 35.704155, "lng" : 139.580957 } }, "icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png", "id" : "6db8f62cc9dc7dba17f116dc2d4e293287d1c9ad", "international_phone_number" : "+81 422-23-8186", "name" : "******* 吉祥寺店", "opening_hours" : { "open_now" : true, "periods" : [ { "close" : { "day" : 1, "time" : "0400" }, "open" : { "day" : 0, "time" : "1100" } }, ・・・略・・・・ ] }, "photos" : [ { "height" : 1280, "html_attributions" : [], "photo_reference" : "CvQB5gAAADqSlV9t5qcineEXNlM_l9g8QL2pXytf04kS_39AUmWyv07OI7dPwJMiW-2kLU6JuDRJRn1v4sOGOL8bVeVxs1gVssLDxL5G7XR3c9wNBGR2aUlJqZ6WqZUZDZDhZ-f57GnfvzHrZVofHH80rvXsz-DXwO-8EfVYHgx1peO5MVakwtktLDqjo0ZziTeeXdmxdBHrbdxDp9PdPnTxEqrn36I2pxP0SK6L0sB1j-wUxQKRDbQz_Pd9AuG141lc8_ripH38CcltIeMN7OYkHaMduMsdpF9zrUSidukRnsIzaPS7wQvm_6UcpvrsUiqkwbb6HhIQzdIh8EwdByLz8Sekmp3wYxoUyiywg67HD-XRIDvHoNNQRPUD22Y", "width" : 853 }, ・・・略・・・・ ], "price_level" : 1, "rating" : 3.7, "reference" : "CoQBdAAAAFiU51mR7ZPeCxa_13OUoLtgW49WW6MH81a-4UoTLK6hexnEuoaKqYjGosDIHgyvaiewSd5csp73yn7Bzln5ZQsvgrJw6prhvYw9shgak1lqDrn59KgP8YUhIy3u9ONnL2ULlD1omomYJ9l7DYtF4hAdoxsO2bPrIq-hizNWqcYgEhC_t8Q99QQ39iC61-TetPiIGhSPJvCtv3lJCqYEKVMBBOYXSUhxvA", "reviews" : [ { "aspects" : [ { "rating" : 3, "type" : "food" }, { "rating" : 2, "type" : "decor" }, { "rating" : 2, "type" : "service" } ], "author_name" : "*********", "author_url" : "https://plus.google.com/********************", "text" : "*************************このお店のラーメンはハマる。", "time" : 1370669370 }, ・・・略・・・・ ], "types" : [ "restaurant", "food", "establishment" ], "url" : "https://plus.google.com/**************************/about?hl=ja", "utc_offset" : 540, "vicinity" : "武蔵野市吉祥寺本町*************** 吉祥寺**********ビルB1", "website" : "http://*******************" }, "status" : "OK"
casperjsのtestが通らない。
今、casperjsを公式docの通り、testすると、うまく動かない。
FAIL TypeError: 'undefined' is not a function (evaluating 'casper.test.begin') # type: uncaughtError # error: "TypeError: 'undefined' is not a function (evaluating 'casper.test.begin')" TypeError: 'undefined' is not a function (evaluating 'casper.test.begin')
なんかと思ったら、brewで普通に入れるstableの1.0系は、
test周りのmoduleが開発版の1.1と違うらしい。
公式docも開発版ベース(?)っぽい。とにかく、upgradeしろという事。
$ brew uninstall casperjs $ brew install casperjs --devel
これで、1.1入る。
テストも通った。