ちょうど関西に戻る用事があったので、俺の話を聞け!2012に行ってきました。
この間FirebugオンリーでWEBデザインしたらいい感じだった話をさせていただきました。
俺の話を聞け に行ってきた
[Titanium]WebViewの遷移先を横取りする
最近まーったくTiを触れていないのですが。書くと役立ちそうな話題を見かけたのでちょっとだけ。
WebViewを利用したアプリケーションの場合によくあるのが、
特定のドメイン(URL)じゃないページへ遷移する場合は、通常のブラウザアプリ(Safari等)で開く
という要件です。
Ti.UI.WebView で、遷移先(遷移しようとしている)URLを知りたい ~ 'beforeload' イベントで url が反映されない件 #titaniumjp #titanium - harukazepcの日記
「世界を変えた6つの飲み物 - ビール、ワイン、蒸留酒、コーヒー、紅茶、コーラが語るもうひとつの歴史」
ビールやワイン、蒸留酒、コーヒー紅茶にコーラまで、その飲み物がどうやって開発されて、なぜヒットしたのか、社会にどんな影響を与えたのか、といった具合に、飲み物を通して歴史を語った本です。
例えば、古代メソポタミアで最初に一般化したアルコールはビールでした。
なぜかというと、穀物を水につけておけば勝手に出来上がったから。穀物を水につけておくと麦芽糖ができて甘くなります。これが空気中の酵母と偶然触れ合ってアルコールに変化したのです。
原理的には果物とか蜂蜜でも可能なはずだったのですが、当時穀物はたくさんあったので、人類はこの偶然に改良を繰り返してビールを造る方法を学習しました。
7000年くらい経って都市が成立すると、ビールは一種の通貨として使われるようになります。エジプトでピラミッド建築の労働者が一日働くと、パン3-4斤と4リットルのビールがもらえたのだそうです。
必需品だから誰でもほしがるし、たくさん蓄えておいて公平に分け与えることができるので、通貨としては便利だったのですね。
このあと出てくるワインとも共通する話なのですけど、アルコールはそれ自体が殺菌作用を持つし、製造過程で沸騰したりしているので、かなり長い間、下手な水より安全な飲み物でした。
そういうわけで、ギリシャ時代ローマ時代の間に、食事の時にはビールやワインを飲む習慣が一般化します。
古代ギリシャでは、神官といえども朝食から(!)ワインを飲んでいたので、ちゃんと仕事ができる状態に戻るのに時間がかかって苦労していたのだそうです。17世紀になって、アラビア生まれのコーヒーがヒットしたのは、酔っぱらわなくてもいい食事時の飲み物、という理由だったのだとか。
西ヨーロッパは、数世紀の間立ちこめていたアルコール性のもやのなかからようやく抜け出した。「このコーヒーという飲み物のおかげで」と1660年にあるイギリス人が書いている。
「諸国において、節酒の傾向がよりいっそう進んでいる。徒弟や助手らはかつて、朝にエール、ビール、ワインなど、脳にめまいを起こさせるものを飲んでいたため、多くのものは仕事に適さない状態だった。 だが彼らは今や、この目を醒ましてくれる文明的な飲み物のおかげで、まともに勤められるようになっている。」
一方お茶は、もともと中国で飲まれていた飲み物だったのですが、これも大航海時代にヨーロッパに渡って大ヒットしました。
とはいえ当時中国の技術力は圧倒的で、ヨーロッパの品物なんてあんまりほしい物がないよね、という状態。しょうがないのでヨーロッパ側は代金として銀を支払っていたのですけど、貴金属は限りがあるのでそうそう出せるものではありません。
あんまりにも高いので、「なんか安いお茶はない?」ということで輸入されたのが、うっかり放置して酸化させちゃった茶葉からできた安物のお茶「紅茶」だったのだそうです。
イギリスでは、苦い紅茶をおいしく飲むために牛乳を入れて飲むアイデアが発明されました。
ちなみに同じ頃日本では、高級品の茶を飲む作法にこだわった結果、世界に類を見ないほど複雑な「茶道」が生まれました。あんまり合理的じゃないところにこだわって、意味は分からないけどなんだか凄いものを作ってしまう国民性は、この頃から変わってないのかもしれないですね。
Amazonが導入するロボット倉庫の仕組み
Amazonが650億円掛けて導入する倉庫ロボットがすごいということで話題になっています。
すごいのはいいのですけど、動画貼って終わりの記事ばっかりで(みんなずるいよね!)、具体的に何をしているのかちっとも分からなかったので、がんばってヒアリングしてみました。想像以上に面白い仕組みだったので、説明します。
最初、ロボットが棚から商品を集めてくるのだと思っていたのですが、違いました。商品のある棚を従業員の所に持ってくるのです!
オレンジ色のロボットが商品の載った棚を持ち上げて運びます。
従業員の所に到着。
天井のカメラから光が当たってピックアップする商品をお知らせ。
#動画をよーく見ると、左側のカップのあたりに光が点滅しているのが分かります。たぶん、天井のカメラで棚に貼ってある二次元バーコードを認識して照射しているのでしょう。
従業員が指示された商品をピックアップしてバーコードを読ませると、役目を終えた棚(ロボット)は自動的に所定の位置に戻っていきます。
従業員は一歩も歩くことなく、次々にやってくる棚から商品をスキャンして箱に放り込むだけで、ユーザーの注文が完成していきます。
よく知られている話ですが、Amazonの倉庫はアルファベット順や商品順ではなくまったくランダムに配置されているので、似たような商品の一巻と二巻を取り違える、といった事件も起きません。
参考:これがアマゾン・コムの配送センターだ « WIRED.jp Archives
一方で、ロボットのほうもコンピュータの指示に従って棚を運ぶだけなので、商品を拾い集めるロボットを作るよりずっとシンプルなシステムにすることができます。
複雑なシステムにすると壊れた時の修理が大変になりがちですが、このシステムなら、同じロボットがたくさんいるだけなので、壊れた機体は修理送りにして予備のロボットを投入すれば簡単に復旧させることができます。
こういうロボットが1000台程度、バスケットボールコート数個分の倉庫を走り回るのだそうです。
なお、ロボットは必ずしも棚を元の位置に返すとは限らなくて、よく売れている商品が多い棚は近くに、あまり売れていない商品が多い棚は遠くに戻すことで出荷効率を改善するようになっています。
商品一個入れるたびに巨大な棚を持ち歩くのは燃費が悪そうですけど、こういう工夫によって補われているのでしょうね。
- Here Are The Amazing Kiva Robots That Amazon Just Bought For $775 Million
- 巨大倉庫で働く「自律型ネットワーク・ロボット」(動画) « WIRED.jp Archives
- これがアマゾン・コムの配送センターだ « WIRED.jp Archives
- アマゾン、440億円の大型買収へ。ベゾスも恐れるダイパーズ、驚異的成長の秘密:In the looop:ITmedia オルタナティブ・ブログ
2012-03-21 10:45:24追記
このロボット、Kiva Systemsっていう米国のベンチャー企業の物だったのですが、Amazonが買収することが決定したそうです。さすが。
Amazon、ロボットメーカーのKiva Systemsを7億7500万ドルで買収 - ITmedia ニュース
じゃらんで見た宿の情報が漏洩しまくっている
右上の(i)をクリック。
「あなたのブラウザでは、jalanjpのWebサイトで以下の商品をご覧になられています。」
ご覧になられています、じゃねー! どこだか分からない広告会社が何でそんなことを知っている!
これは、第三者クッキーと呼ばれる技術をつかったものです。つまり、CRITEOという広告会社が、Togetterにも じゃらんにも広告を配信しているので、じゃらんの閲覧履歴を元にTogetterに広告を出すことが出来ているわけですね。
じゃらんのプライバシーポリシーを見ると、たしかにこの広告が使われていることが説明されています。 この話題、気づくきっかけになったのはこのブログの記事でした。
バナー広告の下に「楽天」とあるので楽天の広告と思ったのですが,右クリックすると「Powerd by Criteo」とあるのでCriteoによる行動ターゲティング広告であることがわかりました.楽天のサイトにも、たしかにCRITEOの広告が使われていることが明示されています。 二匹いたらもっといると思え。 これは気持ち悪い。つまりCRITEO社は、僕が楽天で鳥かごを
(CRITEOの広告が楽天でチェックした商品を表示している件: 何の変哲もない福岡生活...)
ヨメと今度行こうかと思っていた旅行の行き先まで!
これは気持ち悪すぎる!断固抗議だ!
ふと振り返ると
ところで、自分の運営しているモバイラーズオアシス-街の電源検索サイトでは、Googleアドセンスを使って広告を表示しています。あと、このブログにも出ているはずです。このGoogleアドセンスも、行動ターゲティング広告をやっています。
Google Japan Blog: 新しい広告のプログラムのテストについてつまり、モバイラーズオアシスだって、Googleを通して上記の気持ち悪い状況をつくりだす一翼を担っていることになりますね。えらそうにリクルートさんを批判できる立場にはないわけです。
かといって、今モバイラーズオアシスからアドセンス広告を外すと、運営が干上がってしまいます。ううむ。
言い訳かもしれないのですけど、GoogleとCriteo社では気持ち悪さが全然違うと思うのは、僕の勝手でしょうか?
Googleであれば、ストリートビューの件を初めとして、これまでいろいろな事件が起こって、それに対してどんな対応をしてきたか一通り見てきています。あるいは、及川さんを初めとして、中の人も何人も知っています。そのGoogleなら、まあそんなに無茶はしないだろう、という気がするのです。
我が身かわいさ故の偏見かなぁ。
第三者クッキーを拒否する方法
罪滅ぼしになるかどうかは分かりませんけど、最後に、この手の追跡を回避するために、第三者クッキーを拒否する方法を紹介しておきます。お使いのブラウザによりますけど、 という感じです。safariは最初から拒否する設定になっているみたいですね。
気持ち悪いことに気づいたので広告を全廃しました!とか、リクルートにたいする抗議運動を開始します!とかかっこいい記事じゃなくて申し訳ないのですけど、とりあえず、これを見て気持ち悪いと感じた人が増えるだけでも、世の中なんか変わるのではないかな、と期待を込めて。
2012-05-13 10:25:20訂正
楽天で「買った」ものの情報は流れていないみたいですね。その前の閲覧履歴が渡っているので、これがいいかな、あれがいいかな、と思っている段階の情報が流れているだけのようです。
[Titanium] dpとpixelの変換
さて、そんなandroidでは、解像度の異なる端末に対応するため、数値はdip指定するのが原則です。
ところが、view.animateとか、scrollView.setContentOffsetのように構造体をとる関数はたいていdp表記に未対応で、pixel単位で指定してあげる必要があります。変換関数が必要!New Defaults for Android Layouts in 1.7 « Appcelerator Developer Center Y.A.M の 雑記帳: Android multi screen 対応
このあたりのことはこの記事で解説されていて、
TitaniumMobileのハマりポイントとお作法メモ at HouseTect, JavaScriptな情報をあなたにdpとpixclの変換関数なんかも提案されているのですけど、用途と合わなかったので、俺バージョンを作りました。
元コードとの違いは
- View.widthなどで取得できる '100dp'などの記述をそのまま渡せるようにした
- 同様に、view.widthにそのまま渡すことを意識して、dpは'100dp'みたいな文字列で返すようにした
[Titanium] webviewのリンク先をブラウザで開く
WebViewで開いたページに対して、リンクをクリックした時の挙動をコントロールしたい、というのは人類共通の願いで、過去にもたくさんの方がブログ記事を書かれています。
最初の2つは、webviewのbeforeLoadイベントを使う例で、あとの2つは(もぎゃろぐとひげろぐ。ぷぷぷっ)HTMLの中でリンククリックをハンドリングして、Ti.App.fireEventでアプリに飛ばしたイベントを使って何とかしようという例です。
前者は、iOSとAndroidの違いやTitaniumSDKのバージョンによって動いたり動かなかったりしているのが難点で、後者は、JavaScriptのコードがバグっているとデバッグが超大変(printfデバッグすらできない!)なのが難点です。
さて、ありがちな要求として、ランチャーがわりにローカルのHTMLファイルをwebviewで開いておいて、その後のリンク先は全部ブラウザで開いてほしい、という要望が考えられます。というかボクが欲しかったのにそういうサンプルが見当たらなかったので、自分で書きました。
ポイントとしては- beforeLoadは最初のローカルHTMLの読み込みに対しても呼び出されますので、urlが'file://'かどうか確認して、ローカルファイルじゃない時だけ処理するようにしています。
- beforeLoadでブラウザを開いたとしても、webview自体が次のページを読みに行ってしまうことは阻止できません。できるのかもしれないですけど、うまいやり方を思いつかなかったので、stopLoadingで阻止しつつ、念のため、loadイベントで戻れるときはgoBackさせて強引に最初のページを維持させています。
これをやっておかないと、特にAndroidでBACKボタンを押してアプリに戻ってきた場合に、次のページを開いているwebviewが見えてしまいます。 - 余談ですが、Ti.Platform.openURLって、Androidだとちゃんとインテントを聞いてくれるのですね。問答無用で標準ブラウザが開くのだと思っていました。
TitaniumSDK2.1.2GAだと、AndroidとiPhone両方で動作しています。
この例はbeforeLoadイベントを使っているので、将来的にまたSDKがバグったり直ったりすると、また動かなくなるかもしれませんが、その時は自力でがんばってくださいませ:-)
英語環境の人に機械翻訳を提案するスクリプトMayITranslate
テレビ東京HPが自動機械翻訳でめちゃくちゃな英語になっている。 - Togetter 禿頭帽子屋の独語妄言 side A: # 今度はテレビ東京のサイト - 機械翻訳システムの害はかなり広いのかも
「機械翻訳だからしかたないでしょう」
世の中的には、おおむねこれで済んでしまう話なのでしょうか。
実際、機械翻訳を表示するサイトにはたいてい免責条項が書いてありますよね。
(中略)
提供元は「システムを提供しているだけ」と言い、そのシステムを使うほうも「責任を負いません」と言い逃れている。
そんな情報、発信する意味があるのですか?
(禿頭帽子屋の独語妄言 side A: # 今度はテレビ東京のサイト - 機械翻訳システムの害はかなり広いのかも)
実際には、日本のコンテンツに興味がある人たちの中には、翻訳サイトを通して日本のWEBを見ている人が結構いるみたいなので、翻訳結果を提供することにまったく意味がないこともないとは思うのですけれど、テレビ局自身が提供する公式サイトの一部(に見える)からには、クオリティが低かったら文句言われても仕方ないですよね・・・
ところで、GoogleのChrome ブラウザには、母語以外のWEBサイトを見ると翻訳を申し出てくる機能がついています。
こんな機能があるのだったら、なおさらテレビ局が自前で機械翻訳結果を提供する必要なんてないような気がします。
実際には、特定のブラウザの機能をあてにすることは出来ないという理由で、自前提供することにしてしまったのだとは思いますが。
ということで、同様の機能をどのブラウザでも提供できるようになるスクリプトというのを作ってみました。WEBページ制作者がページに組み込んでおけば、ブラウザの言語設定が日本語以外になっている人が見に来た時だけ、Google翻訳の案内が表示されます。
「Translate」ボタンを押せば、「翻訳結果はGoogleが出しているものだから間違っているかもしれないよ」の旨のメッセージを出したあと、Google翻訳での検索結果ページに移動します。
これなら、誰が見たってGoogle翻訳の結果であることが明白なので、ちょっとくらい変であろうと、スポンサー名が間違っていようと、まあそんなもんだよね、Googleさんがんばれ、ですませることが出来ます。
英語圏の人も、翻訳したページは提供してないから機械翻訳をみてね、というメッセージを明快に理解することが出来ますよね。
maiitranslate.js
ダウンロードして自分で改造して使われる分には、パブリックドメインに寄贈されたスクリプトとして扱っていただいて結構です。
設置を代行してほしいとか、メッセージを差し替えたいとか、Google翻訳の代わりにExcite翻訳を使いたいとか、そういった対応は有料で承りますので、ご興味のある方はお気軽にお問い合わせくださいませ。
[Titanium]電源コンパス
Mashup Awards 8 (#MA8)にモバイラーズオアシス電源検索APIを提供しているので、API提供企業(!)として #MA8 東京CaravanVol.03に行ってきました。
APIの活用サンプルということで、つねに最寄りの電源スポットのほうを指し示すコンパスアプリというのをつくってみたので、ソースコードを公開します。
- JavaScriptでiPhone&Androidのアプリが作れちゃうTitanium Mobileというのを使っています。
- 上記ソースはAPIの利用サンプルとして自由にご利用くださいませ。ソースコードにも書かれていますが、角度計算の関数はロジカルアーツ研究所 » 緯度経度から距離と方位を求めるプログラムを使っています。
- 「つねに○○の方を指し示すコンパス」のアイデアは方位磁石の応用 « Titanium BBS(JP unofficial)からいただきました。朝この記事を見て、R25cafeに到着してからサクッと作りました。
[iPhone]現在地から目的地まで経路探索
iPhone上で、
http://maps.google.com/maps?saddr=My%20Location&daddr=[title]@[lat],[lng]&z=15という具合に、「My Location」から目的地までの経路を探索させると、GPSで現在位置を取得して現在位置から目的地までの経路を示してくれる、という小ネタがあります。例えば電源検索では、この挙動を利用して現在位置からカフェまでの経路を表示してもらっています。
iOS6でMapsアプリはAppleMapに置き換わってしまいましたが、この機能自体はブラウザ版のGoogleMapsでも動作するので、iPhone5でも使うことが出来ます。
ところが最近、なんか仕様変更があったらしく、「My Location」が現在位置に置き換わらなくなってしまいました。健気に「My Location」という地名を検索して無意味な経路を表示しようとしてしまいます。
ドキュメントに明示されていた機能じゃないので、なくなってもしょうがないのですが、同等の機能はどうにかして実現したいところです。
いろいろ試した結果、こうやると実現できるっぽいことがわかってきました。
http://maps.google.com/maps?daddr=[title]@[lat],[lng]&z=15saddr(出発地点)の指定を省略すると、勝手に現在位置で補完してくれるみたいです。
これもドキュメントに明示されていない挙動なのでいつまで動くか怪しいのですが、「My Location」を置き換えるよりは筋がいい動きだと思うので、自分のアプリに関しては、当面これで実装しておこうかなーと思っています。
[ruby]モバイラーズオアシスAPIの使用サンプル
Mashup Awards 8 (#MA8)に提供しているモバイラーズオアシスAPI。「Rubyでのサンプルが欲しい」との声をいただきました。
とっくに応募期間が終わって表彰式当日ではありますが、今後もAPIを利用していただく機会はあるだろうと思うので、一応サンプルを用意しました。
あまり上手い例を思いつかなかったので、ごくシンプルに、コマンドラインでもらった緯度経度周辺のお店をリストアップするコマンドです。
Ruby1.9を想定して書いてありますが、1.8でも、JSONライブラリを入れれば動くんじゃないかと思います。
[ruby]電源マップをメールで送信
モバイラーズオアシスAPIの使用サンプルをもう一つ。こちらは、モバイラーズオアシス 電源マップAPIを利用して、指定された位置の電源マップのURLをメールで送るサンプルです。
メールを送るためには、メールを送るためのSMTPサーバが必要になります。
とはいえgmail全盛の今の時代、メールサーバを立てるといわれてもピンとこない人も増えているので、そういう方はgmailのメールサーバを利用するのが良いかと思います。
「gmailのメールサーバを使う場合」のコメントを外してユーザー名とパスワードを設定すれば、自前のメールサーバの代わりにgmailのサーバを使うことが出来ます。
svgで画像ファイルを量産する
この記事は、Titanium mobile "early" Advent Calendar 2012の提供でお送りしております。
Titanium Mobileのおかげで、iPhoneとAndroid両にらみでアプリ開発することが現実的になったのはいいのですけど、どちらも機種が増える一方なので、画像ファイルの準備が洒落にならなくなりつつあります。
最低限必須となるアイコンとスプラッシュスクリーンだけで50種類!
Titaniumで用意する画像のサイズ - Sawalog
とはいえ、実際にはほとんど同じ画像がサイズの違うキャンバスに貼り付けてあるだけなので、自動化することにしました。
最大で1024pxの画像を57pxまで圧縮することになるので、ビットマップやpng画像をそのまま縮小すると目も当てられない画像になってしまいます。そこで、デザイナさんにはillustratorのベクター画像で納品をお願いすることにしました。
illustratorの「Webおよびデバイス用に保存」機能を使うと、作成していただいた画像をSVGフォーマットで出力することが出来ます。
SVGは、ベクターグラフィックスなので縮小してもつぶれない、たいていのブラウザでも開くことが出来るなど、期待される要素がたくさんある割にはちっとも日の目を見ないフォーマットですが、それはさておき、プログラマ御用達の画像処理ツールImageMagickやそのRubyバインディングであるRMagickで扱うことが出来ます。
というわけでRubyで書いたコードがこちら。icon.svgとsplash_screen.svgを元に、必要なサイズに拡大縮小、背景は指定した色で塗りつぶした画像を一気に生成します。
こういうのを用意しておけば、「iPhone6が発売されたので新しい解像度の画像が必要」というときでも、プログラマレベルで片付けることが出来るので便利ですよね。
ただこの手法にはちょっと難点があって。ドロップシャドウとか非表示レイヤーなんかはImageMagickがついてこれないのでぐちゃぐちゃになってしまうことがあります。パスで描かれた単純な画像しか適用できないのが辛いので、最近はPhotoShopのアクション機能で出力するほうがいいのかなーとか考えています。そのへんも手法が確立したらそのうちブログで書こうと思います。
[TitaniumMobile]いろいろ便利関数集
何となく間に合わない予感がしていたので先送りしていたTitanium mobile Advent Calendar 2012、前倒しで参加表明したら、案の定ネタが思い浮かばないうちに当日になってしまいました。
大丈夫。こういうときは、自作便利関数ライブラリを公開するといいっておじいちゃんが言ってたので、ボクのutil.jsの中身を公開します。
文字列をあれこれする便利関数
前後の空白を削除するとか、改行を落とすとか。ちなみに、見落としがちですが、printfはGlobal.Stringとしてあらかじめ定義されています。
var_dump
PHPでいうところのvar_dump、オブジェクトの中身が見たいときは、
Ti.API.debug(JSON.stringify(obj));という具合にすれば見られる。
Ti.API.debug(obj); で見られた牧歌的な時代もあったらしいけど、今試してみたらやっぱりダメだった。
速度の計測
できあがったアプリがなんか遅いのでチューニングが必要だ、となった場合、「推測するな,計測せよ」の原則に従って、まずどこに時間がかかっているのが鉄則です。
ということで、処理時間を計測するためのコード。
そうすると、こういうログが得られます。var trace = new util.TraceLog; trace.start('hoge handler'); //時間がかかってそうな処理1 trace.lap('hoge handler 1'); //時間がかかってそうな処理2 trace.lap('hoge handler 2'); //時間がかかってそうな処理3 trace.stop('hoge handler');
12-08 02:07:14.035: D/TiAPI(25391): [hoge handler] has started 12-08 02:07:14.039: D/TiAPI(25391): [hoge handler 1] has passed at 2ms 12-08 02:07:14.137: D/TiAPI(25391): [hoge handler 2] has passed at 100ms 12-08 02:07:14.137: D/TiAPI(25391): [hoge handler] has finished at 100ms
処理に時間がかかっているのは「時間がかかってそうな処理2」の部分だ!ということが一目瞭然になります。
さらにtrace.lapをつっこんで犯人を絞り込むもよし、十分にわかったのであれば腕をふるって高速化に着手するもよしですね。
明日は@tady_jpさんですね。よろしくお願いしますー。
[GoogleMapsAPI] APIと組み合わせる時のイベントはidleを使う
四隅の緯度経度を渡したら範囲内のスポット情報が得られる、というAPIは最近そこら中にあって、 これとGoogleMapsAPIを組み合わせて地図上に何らかのスポットを表示する、というのもそこら中で行われています。
こういうサービスを実現するためには、ユーザーが地図の表示位置を変更した時、新しい位置のスポット情報を取得して表示するためにAPI呼び出しをする必要があります。
この実装例をググると、bounds_changed(領域が変化した時)やdragned(ドラッグが終了した時)イベントのタイミングでAPIを呼び出すようにしている実装が多いのですけど、idleイベントを使うとすごい綺麗に実装することが出来ます。
以下、bounds_changedやdragnedを使った場合に発生する問題と、idleイベントだと何がうれしいのかをまとめました。
bounds_changedを使った時の問題
bounds_changedイベントは、「This event is fired when the viewport bounds have changed.」と定義されていて、地図の表示領域が変更になった場合に発生するイベントです。
このタイミングでAPIを呼び出すようにすれば、ユーザーが地図をドラッグした時も、ズームが変更された時もイベントが発生するので、わりといい感じに動くように思えます。
bounds_changedイベントの問題は、発生頻度が多すぎることです。bounds_changedは領域が変わるたびに呼ばれるので、ユーザーが地図をドラッグすると、一秒に数回の勢いでbounds_changedイベントが発生し続けます。
つまり、単純に実装すると、秒間数回のペースでAPIを呼び出し続けることになります。サーバ負荷の観点から絶対止めてほしいですし、ブラウザ側だって、そんな頻度で結果が帰ってきたら処理しきれなくてブラウザが固まってしまうことが予想されます。
カウンタをもうけて10回に一回だけAPIを呼び出すとか、タイマーを使って何秒に一回だけAPIを呼び出すとか、やってやれないことはないのですが、ずいぶん複雑なコードになってしまいます。
dragendを使った場合の問題
dragendイベントでAPIを呼び出すという実装も考えられます。ドラッグ終了時に一回だけ発生するので、その点はいいのですけど、今度はイベントの発生が少なすぎて弊害が発生します。ズーム変更の時スポットが更新されない
さすがにこれは誰でも気づくので、dragendとzoom_changedでAPIを呼び出す実装にする人が多いと思います。
ドラッグが終わるまでAPIが呼び出せない
dragnedイベントはマウスを放すまで発生しないので、地図をドラッグしながら「この辺かなー?」って考えてもスポットが更新されません。ダメじゃないですけど、ユーザーフレンドリーじゃないですよね。
キーボードで地図を移動させた時APIが呼び出せない
そんな人はあまりいないと思うけど、GoogleMapsはキーボードでも操作ができるようになっています。この場合dragendイベントは発生しません。
また、最近のGoogleMapsは駅などをクリックするとその部分が中央に移動して詳細がポップアップするようになっていますが、この移動の場合もdragendイベントは発生しません。
自分でsetCenterイベントを呼んだ時も発生しない
これが最大の問題で、例えば検索フィールドを作って入力された住所の位置に移動するような実装をする場合、ジオコーディングして得られた緯度経度にmap.setCenterすることになると思うのですけど、この場合もdragendイベントは発生しません。 setCenterを呼び出す時は自前でAPI呼び出しをするコードを追加して・・・(後日APIが変更になった時、片方だけ変更しわすれてバグを埋め込むパターン)
dragendを使った場合の問題
こんな具合にして、起こりうる各種問題に場当たりの対応を繰り返すと、複数のイベントを組み合わせてフラグ管理をすることになったり、コード中のあちこちからAPIを呼び出すことになったり、どうしても複雑なコードになってしまいます。
実際僕もそうやって書いていたのですけど、ある時、idleイベントで全部カバーできることに気づきました。
idleイベントは「This event is fired when the map becomes idle after panning or zooming.」と定義されています。
実際ログを入れて試してみるとわかりますが、- Mapをnewすると、地図の表示が終わった時点で一回発生します。つまりここで、最初の一回目のAPI呼び出しを行うことが出来ます
- 地図をドラッグしおわった時に一回発生。ドラッグ中も、ちょっと手を止めるとそこで発生。idleにならないと発生しないので、API呼び出しが多くなりすぎる心配がありません。
- ズーム変更の時も発生
- キーボードで地図を動かした時も発生
- setCenterやsetZoomを呼び出した時も移動直後に発生
としておくだけで意図した動作をさせることが出来ます。google.maps.event.addListener(self.gmap, 'idle', function() { //ここでAPI呼び出し });
[iOS] タブを残さずURLスキームを開く
ChatWork の謎 | Don't Fall - Titanium Mobileユーザー会サポートBBS
ChatWorkアプリでは、facebook認証でアプリを使うことが出来るのですが、これまでよく使われていたWebView上での認証ではなくて、Ti.Platform.openURL() を使ってブラウザを立ち上げてfacebook認証を行っています。
WebViewを使わない理由は、「OAuthの認証にWebViewを使うのはやめよう - Shogo's Blog」に書かれているとおりだと思います。具体的な実装としてChatWorkアプリはとても参考になりますね。
さて、ブラウザ上でfacebook認証が成功すると、ChatWorkサーバはURLスキームを使ってChatWorkアプリケーションを起動させます。ここがマニアックに興味深いところです。
- ユーザーはリンクをクリックしていないのに、なぜURLスキームが起動したのか?
- ChatWorkからブラウザに戻ったとき、URLスキームを起動したWEBページが残っていないのは何故?他のアプリだと残っちゃうよね?
ページを読み込むと同時にURLスキームを起動させる
URLスキームは普通、aタグのhrefに書いて、ユーザーさんにクリックしてもらって使います。例えば、<a href="sms:///">sms</a>と書かれたHTMLのリンクをユーザーさんがクリックすると、メッセージアプリが立ち上がります。でも、facebook認証が済んだら、ユーザーさんの手を煩わせることなくアプリケーションに戻ってほしいですよね。
location.hrefで強制移動とか、aタグを書いてjQuery(hoge).click();とか、いろいろ試してみたのですけど、そういうのは駄目なようです。
ページ内に動的にiFrameを生成してURLスキームを持たせることで上手く起動させることが出来ました。
URLスキームを起動したあと自分を閉じる
URLスキームでアプリを起動したのはいいものの、そのHTMLがブラウザ上に残ったままになっていると、あとでブラウザを開き直した時に変なページが見えてしまったり、場合によっては勝手にアプリが立ち上がってイラっとさせられることがあります。結構有名なアプリでもこの現象起こしますよね。JavaScriptで自分を表示しているタブを閉じる場合、理屈上はwindow.close()で出来るのですけど、safariのwindow.closeはjavascriptで開いたページしかclose出来ないので、このようなごまかしテクニックを使うことで自分をcloseすることが出来ます。
window.opener = window;URLスキームとwindow.close、どっちかが他方を妨害しそうな気がしますけど、実際には両方上手く動くみたいです。
var win = window.open(location.href,"_self");
win.close();
P.S 1:自分でJavaScript書いて同様の挙動を再現させることに成功しただけなので、ChatWorkさんのアプリと厳密には一致しないかも知れません。
P.S 2:[iOS]って書いてますが、URLスキームをintentに変えればAndroidでもそのまま動作しました。
P.S 3:ChatWorkの中の方、出来るだけ迷惑かけないように調査したつもりですけど、不審なログとして調査対象に上がっていたりしたらごめんなさい。
cakePHPでFacebookログイン
前の記事で予想がついている方もおられるかと思いますが、facebookログインするサイトを作っています。
RubyじゃなくてPHPで。人生何が起こるかわからないものですね。
さすがに生のPHPは書いてられないので、CakePHPを使っています。
ということで、今日の記事は、CakePHP+FacebookSDKです。
ぐぐれば似たような記事はいくつか出てくるのですが、いずれも記述が古くて動かなかったので、僕バージョンをまとめました。これも半年位すると動かなくなるのかもしれないですが。
Login for Server-side Apps - Facebook DevelopersただこれだとAPI呼び出しが面倒なので、Facebook公式のfacebook-php-sdkをつかいます。
facebook-php-sdkの利用サンプルも、ぐぐればたくさん出てきますが、その大半は古い内容で現在のSDKでは動きません(getAccessToken()をわざわざ呼び出しているのは古いコードです。)
Migrating to OAuth 2.0 update: PHP SDK v.3.1.1- Facebook Developersを参考に書き直したのがこちら。 要するに、$facebook->getUser()して取れたらログイン成功、取れなかったらログインURLにリダイレクトすればOKです。昔のSDKで必要だったgetAccessTokenの呼び出しは、SDKが内部で処理してくれるようになりました。
コールバック引数を見てアレコレする部分もSDKが面倒を見てくれるので、login.phpとcallback.phpのように二つのファイルを作る必要すらありません。PHP用のSDKらしい実装ですね。
これを踏まえて、CakePHPでFacebookContollerを書いたのがこちら。
- facebook SDKは、/app/Vendor/facebook/の下に配置。
- appidとsecretは、bootstrap.phpに
Configure::write('facebook',array( 'appid'=>'***', 'secret'=>'***' ));
という具合に。
もっといえば、FacebookControllerというクラスを用意しなくても、ほかのControllerのbeforeFilterに書いてしまってもかまわないかも知れないですね。
FacebookSDKがCakePHPのSessionを使うようにする
FacebookSDKは、この簡単実装を実現するために、内部で$_SESSIONを呼び出して、accessTokenや CSRF対策のstate変数を保存しています。 この実装は、単純なWEBサーバであれば困らないのですが、複数のWEBサーバで分散処理するためにセッションをDBに保存する、というような設定をしている場合、 このままだと動かないことが予想されます。
CakePHPは、そういうときのためにセッションをデータベースに保存したりすることができるようになっていますので、FacebookSDKの変数も、CakePHPのセッションを使ってもらうようにしたいところです。
facebookのエンジニアの人たちは優秀なので、こういう用途も想定してSDKを書かれたようです。
facebookSDKのFacebookクラスは、ほとんどの実装がbase_facebook.phpのBaseFacebookクラスで実装されて、上記セッション変数の処理だけが、facebook.phpのFacebookクラスで実装されています。
なので、facebook.phpをコピーしたcakeFacebook.phpを作って、$_SESSIONの部分をcakePHPのCakeSessionに書き換えたCakeFacebookクラスを用意すれば、FacebookSDKもcakePHPの設定に従ってくれるようになります。
ということで出来上がったのがこちらになります。 facebook.phpと同じディレクトリに配置して
App::import('Vendor', 'facebook/cakeFacebook');という具合にして使います。あとは通常のFacebookクラスと同じ使い方でOKです。
$this->facebook = new CakeFacebook(array(
'appId' => Configure::read('facebook.appid'),
'secret' => Configure::read('facebook.secret'),
));
[JavaScript]変数の存在確認
・同じ比較方法の場合、if文よりも三項演算子の方がやや速い ・もっとも高速な方法は論理和を使う方法 ・もっとも時間がかかる方法は型比較をif文で行う方法 ・null比較、型比較で三項演算子を利用すると真偽によって速度が(割と)変わる ( 変数の存在を確認する方法と速度比較(その2) - blog.katsuma.tv)
五年前の記事なのだけれど、関数に渡ってきた引数を存在チェックするとき、どう書くのが早いだろう?という実験レポート。
このレポートによると、
var ret; if(arg) { ret = arg; } else { ret = 'bar'; }という比較的普通のコードと比べて
var ret = (arg!=null)? arg : 'bar';三項演算子を使うと倍近く早いらしい。
でも、変数の初期化だったら僕はこう書くことが多い。
if(!arg) { arg = 'bar';}ということで、五年経った現状と、僕のコードどうなんだろう、というのを見たいと思ったので試してみた。
Google Chrome 26.0.1410.65
[checkStrTernary] 1786 [checkStrIf] 1786 [checkArgsLogicalAdd] 49 [checkArgsTernary] 45 [checkArgsIf] 51 [checkArgsIf2] 48 [checkNullTernary] 74 [checkNullIf] 59 [checkTypeTernary] 61 [checkTypeIf] 48
待てw
やるんじゃないかとは思っていましたが、実質的に何もしていないことを見ぬいて最適化された結果、Date()の分解能テストと化しています。たぶんループが丸ごと削除されていますね。
もちろん、checkStrTernaryとcheckStrIf以外の順位は実施のたびに入れ替わりますので、個別のコードの有利不利を判定することはできません。
FireFox 17.0.1
[checkStrTernary] 14555 [checkStrIf] 14263 [checkArgsLogicalAdd] 10572 [checkArgsTernary] 10674 [checkArgsIf] 11215 [checkArgsIf2] 11258 [checkNullTernary] 10659 [checkNullIf] 10750 [checkTypeTernary] 11102 [checkTypeIf] 11714
うむ。さすが高機能JavaScript開発環境(僕命名)。
これを見ていると、やっぱりcheckStrTernaryとcheckStrIfが遅いものの、あとはそれほど大きな差じゃない程度になっています。checkArgsIfとcheckArgsIf2などは実施のたびに早いほうが入れ替わるので、誤差の範囲内です。
InternetExplorer 9.0.8112.16421
ログ: [checkStrTernary] 11868 ログ: [checkStrIf] 11766 ログ: [checkArgsLogicalAdd] 8618 ログ: [checkArgsTernary] 8525 ログ: [checkArgsIf] 10521 ログ: [checkArgsIf2] 10767 ログ: [checkNullTernary] 10777 ログ: [checkNullIf] 10655 ログ: [checkTypeTernary] 11507 ログ: [checkTypeIf] 10584
おおむねFireFoxと同じ傾向が出ています。よほど変な書き方をしない限り大差ないということですね。
所感
近年のブラウザにおいては、最適化が進んだ結果、以前ほど三項演算子有利ではなくなってきました。
それでも1割程度の差がつくので、シビアな場面ではトライする価値があるかもしれないですが、普通にコード書くときは、読みやすいほうを使うのでいいんじゃないかなーと思いました。
コード:
[cakePHP]findの戻り値からモデル名を除去する
この結果を
という具合にパラメタにセットして、$this->set('results',$this->Model->find('all')); $this->set('status','ok');
というような関数を呼び出してあげれば、検索結果をJSONで返すWebAPI的なものを作ることができる。protected function return_as_json(){ $this->header('Content-Type: text/html'); echo $this->indent( json_encode($this->viewVars) ); exit(); }
ところがこれがちょっと変で。JavaScriptからみた戻り値はこんなふうに見える。
普通APIで検索結果を取得する時って、普通こうなってません?{ "results": [ { "Model": { "id": "5", "user_id": "8", :(略) } }, { "Model": { "id": "4", "user_id": "8", :(略) } }, ], "status": "ok" }
{ "results": [ { "id": "5", "user_id": "8", :(略) }, { "id": "4", "user_id": "8", :(略) }, ], "status": "ok" }
呼び出し側のJavaScriptから見たら、
って謎の型名をつけるよりも、function(res){//success var models = res['results']; for (var i = 0; i
のほうが自然ですよね。function(res){//success var models = res['results']; for (var i = 0; i
前置きが長くなったけれど、そういうわけで型名を付けないで検索結果を返すようにする実装。
本当はjson_encodeするときに型名を落とすのが適切だと思うのだけれど、うまいやり方を思いつかなかったので、modelのカスタムfindとして定義してみた。
連想配列のまま構造を変えるのではなくて、一旦フラットにしてテキストとしてモデル名を除去しています。
allの代わりにnoModelNameを使えば、モデル名部分のないハッシュを得ることができます。
$this->set('results', $this->Model->find('noModelName') ); $this->return_as_json();