yasapp

yasが作ったJavaScript製クライアントアプリケーション。

ぺったんRのAPIのほとんどを網羅したシングルページアプリケーションのため、無駄な読み込みを抑えたストレスの少ない操作が可能なのが特徴。 APIで通信するので、単独で利用することもできる。現在はぺったんR本体にも組み込まれていて、ホームページを開くと自動的に起動する。ホームページ以外のリクエストでは、 yasappではなく、サーバが生成したページを返す。

フレームワークにbackboneを採用している。

シングルページアプリケーションの特性

JavaScript (ajax)によってダイナミックにページを更新できる。ページ遷移しないので、その分早い。

ページ遷移に関する問題点

あまりにもダイナミックにページを書き換えると、ブラウザ上のURLとページ上に表示されるコンテンツの内容が一致しなくなる。yasappはホームページへのリクエストで起動するので、起動時のURLは常にルートである。このままユーザが操作して漫画を閲覧し、気に入った漫画をブックマークしたとしても、記録されるのはルートのurlである。リロードしても同様で、先ほどまで閲覧していたコンテンツの位置を忘れてしまう。これではユーザの利便が台無しだし、ソーシャル面で利点を失ってしまう。

History API

それを解決するために、 html5ではhistory APIという機能が用意されている。これを利用することで、 JavaScript側からurlのドメインより下を柔軟に書き換えることができる。つまり、アプリケーション上に表示されるコンテンツとURLを完全に一致させることができる。当然ながら、 html5に対応していない古いブラウザでは利用できない。ぺったんRは個人向けのエンターテインメント系サービスなので、 yasappは保守的な環境で利用することを想定しない。ちなみにbackboneは古いブラウザに対して、 urlのハッシュ以下を書き換えるHash Changeで対応する

backbone history

ユーザーが行ったページ遷移をhistory APIを使ってurl管理するためのモデル。URLをpush stateすれば、 urlが書き換えられ、あたかもページ遷移したように振る舞える。 pop stateすれば、 urlが前のページに書き戻され、あたかも戻るボタンをクリックしたように振る舞える。ブラウザにはURLの履歴が残るので、戻るボタンや進むボタンが有効になる。

オプションでtriggerをonに設定しておけば、 URLに見合ったコールバックを実行してくれる。 戻るボタンをクリックした場合は、オプションにかかわらずコールバックを実行する。

Backbone router

アプリケーションとbackbone historyの間に入って、 URLに見合った処理を実行するためのモデル。 railsで言うconfigルートに相当する。

urlを解釈して、 urlが振る舞うべき関数と結びつける。そのurlと関数のペアをbackbone historyに登録して、ページ遷移が発生したときにコールバックを起動してもらう。

yasapp history

巨大なアプリケーションでは、ユーザが思うままにページ遷移してもらっては困るケースがある。特に、戻るボタンで移動されるのが厄介。パネルエディタで大規模なコマを編集している間に誤って戻るボタンをクリックしてしまい、編集データを失ってしまったら… 。これを防ぐために、 backbone historyを拡張している。詳しくはページ遷移のロックで。

yasapp router

backboneではアカウント管理を想定していないので、権限のないリクエストをフィルタリングできない。ぺったんRには、オペレーターという権限管理の概念があるので、 backbone routerを拡張している。

urlを解釈して処理を分配する機能はbackbone routerに含まれている。とは言え、何もかもが揃うわけでは無い。定義してあるルートのパターンリストのうち、どのパターンにマッチしたのか、 routerの外からは窺い知ることができない。これくらいわかるように作られていても不思議は無いのだが、なぜだか用意されていないので自前で作る必要があった。もちろんゼロから作るのは難しいので、既存のコードから切り貼りしたのだけど。

ルーティングに関する問題点

パターンマッチ

ページをブックマークしたりリロードされたりすることを考慮して、サーバと同じURL構成になってなければならない。サーバ側は/コントローラ/アクションのような規則なので、ルーターのパターンマッチも同様の引っかけ方をすることになる。

rest url

backbone routerでは、メソッドの区別がないので削除APIをdeleteメソッドにはできないのであった。/items/1/destroyの形式で処理するので、ちょっとダサイかもしれない。もしどうしてもやりたいなら、 deleteメソッド用のルーターを作ってルーティングする。

ルータとコントローラの連携

backboneにはコントローラという概念は無い。ルーターから呼び出す。

Router内部でコントローラやアクションなどのリクエストに関する情報(railsサーバと同様なparams )を起こしてから生成する。関数呼び出しよりはイベント通知の方が結合度が下がるので、イベント方式を採用。fireイベントから起動する。

アクセスフィルター

SNSモードで動く場合、ゲストはコンテンツを見ることができない。このとき、アカウントによるsign inを促さなければならないので、ユーザが期待するリクエストとは違うページが返る。

sign inしてあっても、絵師登録が行われていなければ、素材公開できないので、こちらも登録を促すような入力フォームを表示しなければならない。

普通にアプリケーションを使っている分には、このようなフィルタが必要になるような状況にはでくわさない。画面にはそんなリンクが表示されないから。ところが、サインアウトした後にブラウザで戻るボタンを押していくと、以前にアクセスしたURLに到達してしまうので、なんだかんだでブロックしなければならない。

ルータに機能を拡張

各APIに、アカウント権限、読者権限、素材読者権限の三種類と、作家および絵師登録状況に関するアクセスフィルタを用意してある。 ルーターがparamsを引き出し、そこからアクセスしようとしているアクションを割り出す。アクセスフィルタリストの各権限を現在のオペレーターが満たしているか、チェックする。 アクセスフィルタにブロックされると、リダイレクトすべきurlが返る。これはsignフォームなどの回避ページのURLである。 アクセスフィルタを通過したなら、nullが返る。

未解決問題

ページ回避はサーバほど性能がよくなく、会員登録などで問題を解決しても元のページに戻ることができない。そのためにURLをストックできていれば話は別だが… 。

コントローラ

backbone routerのfireイベントを監視して起動させる。リクエストで指定されたコントローラのアクションをkickしたら仕込みは完了。あとは処理結果にふさわしい画面データがreadyイベントを通じて送られてくるので、それを待つ。コントローラのreadyイベントを監視してビューを受け取り画面を更新するのをお忘れなく。リクエストによってはページタイトルを書き換えたくなることもある。その場合はリタイトルイベントで通知する。

ビュー

rails流のMVCでは、コントローラがビューを用意してレスポンス(レンダリング)するが、 yasappもその流れに従う。ただし、サーバは一度のリクエストに対して、一箇所(ページ全体)のビューを用意すれば良いのに対し、 yasappでは複数の更新対象を持つケースも考慮しなければならない。

レイアウト

yasappの起動時に初期画面を描画する機能。デフォルトでは、アプリケーションレイアウトが利用される。アプリケーションレイアウトは、ヘッダ・ヒストリー ・ボディー・フッタの四つのブロックから構成されている。

サーバでは一つのリクエストに対して画面全体を返すので難しいことを考えなくて良い。しかし、 JavaScriptアプリケーションでは、必要な部分だけを書き換える。レイアウトレベルで柔軟に書き換えてしまうと破綻してしまうので、ダイナミックに書き変わるのは、アプリケーションボディーだけに限定する。

アプリケーションヘッダ

主にアカウント情報が表示される。ゲスト向けのヘッダと、ユーザ向けのヘッダがある。 sign in状態の変化によって書き変わる。ロゴだけは常に表示されて、これをクリックすることで、アプリケーションボディーをホームページに切り替えることができる。

アプリケーションヒストリー

ユーザのページ遷移を管理する。過去に閲覧したページをアイコンクリックだけで移動できる。

アプリケーションボディー

ユーザが活動するためのブロック。ほとんどのコンテンツがここに表示される。基本的にブラウザのURLはここに表示されている内容を取得するためのURLである。

アプリケーションフッタ

運営に関する情報を表示するためのブロック。動的に変化することはないと思われる。

Layout

次のものを内包する。

  • operators: 利用中のオペレータ。 sign in 、 sign outによって、初期化やreplaceされる。
  • global_router: 閲覧履歴を取るためのグローバルルータ。レイアウトが抑えていないと戻るボタンのイベントを監視できないので、寿命が尽きることがない。

テスト

Yasappを起動したときに、アカウントがsign in状態にあるかどうかを調査するための機能。 yasappがサーバ上で動いている場合、 sign inの必要なく利用できることもある(むしろ開発中はほとんどその状態)ので、操作の手間を減らすためにも用意した。

test_ok,test_ng,test_redirect

アプリケーションレイアウトをスタートさせると、プロクシを利用して独自にデータを送信する。その結果の受け取りはアプリケーションボディーのpostと同等に見えるが、起動時だけは履歴を残したくないので、独自にメッセージを受け取って処理しなければならない。

header

プロクシにヘッダのページの取得を依頼する。ただし、ヘッダの表示をいくら切り替えてもタイトルには影響はないのでメッセージは無視する。履歴管理の範囲外なので、完了メッセージも無視する。純粋に、ビューを切り替えるだけ。ヘッダの中で、ユーザのクリック操作があっても影響を受けるのはボディーの方である。

body

本体なので、プロクシのメッセージは全て正規通りに処理する。 historyに対する操作は常識で言えばアクション完了メッセージをもって追加されるべきだが、 historyのアイコンはページタイトルを必要とするので、タイトルデータを含むという事情から、タイトルメッセージで追加される。

履歴の操作

影響範囲がページ全体に及ぶので、レイアウトヘッダとレイアウトボディーを共に書き換えなければならないが… 。レイアウトヘッダはsign in 、 sign out以外に書き変わることがない。その上、これを描画しようと思うとsign in testをリクエストしなければならず、毎回サーバに送信してしまう。無駄なコストが高そうなので、書き換えはレイアウトボディーだけでもよさそうな気がするが… 。

ページ遷移のロック

ユーザが(後戻り不能な)不本意な遷移をしてしまわないように、ロックがかかっている。一つはyasappの外に出ないようにするロック。もう一つは、エディタで編集中のデータを失わないようにするロック。

yasapp内ロック

yasappの管理外に遷移してしまうと、これまでのキャッシュが失われ、 yasappも再ロードしなければならなくなる。何とかしてユーザに同意を求めたい。

BeforeOnLoadイベントを監視して、ダイアログで問い合わせることで対応できる。これ一つで、アプリケーション全体をカバーできる。

エディタ内ロック

パネルエディタでいじりまわしているところで、うっかりブラウザをクリックししまうと、編集中のデータが捨てられて、うげっとなる。何とかしてユーザに同意を求めたい。

ページ内のリンクをクリックした時の対応と、ブラウザから履歴をいじった時の対応の二つのきっかけがある。

リンククリックの対応

アプリケーションヘッダに表示されているリンクは大変危険だ。ここをクリックするだけで、編集中のデータを飛ばしてしまう。パネルエディタが管理しているリンクはページ遷移を発生させないものの、今後にわたって例外がないとは言えない。とにかく、yasappが管理するすべてのリンクにロックをかけないと、ページが切り替わってしまう。

とは言え、ページ内のすべてのアンカーにロックをかけてしまうのはあまりにも乱暴なので、グローバルルーターのナビゲートで介入する。backbone historyにロックurlを設定しておくと、ナビゲートが発生したときに、ダイアログで問い合わせる。ユーザがページにとどまるのを希望すれば、ナビゲートは実行されない。ユーザが遷移を希望するなら、ロックurlを解除してナビゲートする。もちろんのこと、パネルエディタの寿命は尽きた時には、ロックurlを解除する。

履歴の対応

うっかりブラウザのバックボタンをクリックししまうと、編集中のデータが捨てられる。history APIで操作している間は、before unloadイベントが発生せず、 pop stateイベントでとらえるしかない。しかし、 pop stateイベントは、受信した時点でurlは書き換えられており、キャンセルができず、ページ遷移してしまう。仕方ないので、ロックurlをそしらぬ顔で上書きしてしまうことにする。一時的にurlが書き換えられてしまうのは諦める。実処理のイベントさえ発生しなければよかろう。

backbone historyにロックURLが設定されていれば、ダイアログで問い合わせる。ユーザがページにとどまるのを希望すれば、ルーター業務は実行されない。ロックurlで上書きして、留意したように振る舞う。ユーザが遷移を希望するなら、ロックurlを解除してhistoryの処理を続行する。

proxy

ぺったんRサーバーと通信するための代理人。 APIのURLを投げれば、対応するビューが返ってくる。

用途

yasappでは、ページ内の小さなブロックに、 APIで取得した画面を表示するケースが頻出する。例えばファイラーをダイアログで表示したり、入力フォームを小さい窓で開いたり… 。サーバでは一つのリクエストに対し、一枚の画面が返るので、このような仕組みは必要ない。そのようなサーバの構造を真似して実装してしまうと、クリックする度にページ遷移して画面全体を更新してしまうような仕組みになってしまう。それを避けるために用意した。

ローカルルータにルーティングを依頼してコントローラとアクションの形式に変更してもらい、アクションを呼び出す。アクションにはget post put deleteの四種類のメソッドがあるが、Getメソッドとpostメソッドの二種類で対応する。

コントローラのアクションはサーバの応答に従って次のメッセージを送ってくるので、そのまま転送する。 proxy利用者はこれらのメッセージを監視して対応する。

取得処理

  • http_get(url, form)
  • get(params, form)

コントローラのアクションのうち、 getメソッドアクションを実行する。http_getでurlから指定した場合は、アクセスフィルタとページ遷移のロックも適用されるので、アクセス拒否とキャンセルのメッセージも送信される。

ready(view)

ページの描画が完成した。

title(params, str)

ページのタイトルが確定した。

done(params)

アクションが完了した。 paramsには、実行したアクションが入っている。

deny(params, form, safe_url)

オペレーターが権限を満たさなかったのでアクセスを拒否された。 safe_urlでは、権限不足を解消するためのURLが送られてくるので、リダイレクトする。

cancel()

ユーザによってリクエストはキャンセルされた。編集中のデータを失いたくないなどの理由でキャンセルすることがある。

更新処理

  • http_post(url, form)
  • post(params, form)

コントローラのアクションのうち、更新アクション(post put deleteの各メソッドのリクエスト)をサーバに投げる。http_postでurlから指定した場合は、アクセスフィルタとページ遷移のロックも適用されるので、アクセス拒否とキャンセルのメッセージも送信される。

success

リクエストは成功した

fail

リクエストは失敗した

redirect

リダイレクトが発生した。これは、新規作成が成功したときに、作成されたアイテムを詳細表示するためのページにリダイレクトさせるためのメッセージ。基本的にアクションは、更新に成功したときにリダイレクトを発生させる。更新アクションは操作履歴に残らないので、リダイレクトしないとurlの遷移が正しく管理されない。

sign in

sign inアクションも更新処理の一つであるが、 sign inの結果は、アプリケーションレイアウト全体に及ぶので、個別にメッセージを設けてある。

sign out

sign in同様にレイアウト全体を書き換えるので用意されている。

deny(params, form, safe_url)

オペレーターが権限を満たさなかったのでアクセスを拒否された。 safe_urlでは、権限不足を解消するためのURLが送られてくるので、リダイレクトする。

cancel()

ユーザによってリクエストはキャンセルされた。編集中のデータを失いたくないなどの理由でキャンセルすることがある。

ダイアログ

パネルエディタでエレメントを選択するためのファイラーを表示するためのもの。

編集の禁止

ダイアログから入力フォームを開けてしまうと、二重に編集できてしまう。

入力画面には、履歴(ブラウザの戻るボタン)操作を禁止するためのロックURLが用意されているので、これを編集中フラグとして利用すればダイアログ内での編集を禁止できる。とは言え、原画を投稿して公開できるようにはしたいのだが… 。