パネルエディタ

パネルエディタは大きく分けて三つのブロックで構成されている。コマを表示するためのパネルボディー、 Dockと呼ばれる入力データを取り扱うコンポーネント、使用画像のクレジットである。

パネルエディタが管理するデータは、ルートアイテムとルートにぶら下がるエレメントの最下層である。両者はサーバから見ればどちらも同じアイテムであるものの、ルートとエレメントで名称が明確に分けられている。両者を合わせた(パネルエディタが管理するすべてのアイテム)をコマデータと呼ぶことにする。

理解しがたい前提

スキルが未熟であるが故に直感的には理解できない設計・実装となっている。

モデルとイベントの取り扱い

パネルエディタでは、ユーザからの操作に従って、パネルボディーと、 Dockの間で双方向にメッセージを通信する必要がある。例えば、コマ上のコマ絵をリサイズしたときには、 Dockのコマ絵の入力フォームにその結果が反映される。 Dockからフキダシのセリフを入力したときには、コマ上のフキダシが更新される。

イベントはモデルを監視することで実現できる。モデルをマスターデータとし、パネルボディーとDockは、パネルエディタから渡されたアイテムのモデルをイベント監視することでモデルの状態を各自に反映させることとする。パネルボディーをマウス操作で修正した場合、モデルをモディファイした後にマウス操作イベントを伝達する。入力フォームにキーボード操作(あるいはヘルパーの操作)で変更した場合、モデルをモディファイした後に入力イベントを伝達する。こうすることで必須イベントの無限呼び出しを防いでいる(つもり) 。

このとき、 JavaScript上で保持されているモデルが常に一つであれば、パネルボディーとDockで同じものを監視すれば良い。ところがコントローラから受け取れるモデルはwith_elementsオプションで取得したものなので、ハッシュから生成したその場限りのモデルである。コマのモデルに備わったエレメントリスト取得関数は常にこいつを参照するので、このままではメッセージ通信が不調に終わる。

なんでそんなややこしいことになってるの?

簡単に言うと、 has many関係のモデルを掘り下げていく機能を実装していないから。なぜ実装しないかといえば、すべてのエレメントをfetchできたことを示すイベントを発火させる方法がよくわからないから。非同期通信では、ぶら下がるエレメントをどこまで取得できたかを上位のコンポーネントが把握することが難しい(単なる勉強不足・本当にどうやるの?) 。パネルエディタは特殊な機能なので、ここだけ同期通信を受け入れても悪くは無いのだが…とりあえず回避している。

そこで

パネルに直接ぶら下がっているエレメントについて、 fetchしながら掘り下げていく処理をパネルエディタ上に独自に実装。パネルエディタは、パネルボディーとDockのインスタンスを把握しているので、ここで両者にfetch済のモデルを渡している。これをきっかけに、パネルボディーはそれぞれのエレメント独自のレンダリングとイベント追加処理を行う。 Dockはエレメントタブに対して追加処理を行う。どちらもパネルエディタから与えられた同一のモデルなので問題なく通信できる。ただし、エレメント追加指示は非同期なfetch処理で発生するので、順番は保証されていない。

メッセージ通信の実際

パネルボディーからDockに向けて送られるメッセージは次の通り。

  • resize(サイズ調整つまみをドラッグした)
  • move(ドラッグで移動された)

Dockからパネルボディーに向けて送られるメッセージは次の通り。

  • input:--column name--(inputタグの値が変更された)

パネルボディー

描画部分は通常のプレイヤーと同じなので、その処理だけは共有している。再生専用の処理はwith_elementsで取得したものをそのまま使っている。エディット対応版は、パネルエディタからadd_elementを通じて転送されてくる。エディットモードでは様々なイベントによって書き換えが発生するので、それなりに細かく処理が分割されている。

描画

エレメントの表示は、各エレメントが責任持って自身のエレメントを描画しなければならない。コマ絵のエレメントを描画するには、紐付けられた実素材を取得して描画しなければならないのは当たり前のこと。特にモデルがネスト構造になっているエレメントについては注意しなければならない。

エディットモードで気をつけること

厄介なのがネストしたモデルの描画。パネルエディタが各エレメントのツリー構造を把握(IDをキーにして構成)することが難しいので、エレメントは責任持って自分の子供たちをツリー構造に管理しなければならない。

現在のところ、深いネストを持ったエレメントはないし、リスト状になった孫エレメントを持つエレメントも存在しないので、この方式には不備があるかもしれない。フキダシがバルーンとセリフを持つだけの構造については、これで実装できている。

提供されるユーザインタフェース

jquery UIで提供されている各種コンポーネントを使って実装している。基本的にはサイズ変更と移動を行なえる。

jquery UIによるもの

サイズ変更と移動を両者ともにできるものもあり、できないものもある。例を挙げると、パネルはサイズ変更だけができる。コマ絵やフキダシは両方できる。絵地や色地はどちらもできない。 jquery UIにはタグにラッパーを追加する性質のものもあるので、再生時とは若干構造が変わることもある。

その他

マウスポインタを編集可能なエレメントの上に乗せたとき、対応するエレメントがフォーカスされる。ユーザには、エレメントが枠線で囲まれることによって周知される。これはjquery UIに備わった機能であるが、エレメントの構造上、この機能が有効に機能しないケースもある。このため、個別にイベントを監視して対応することもある。具体的に言うとこのケースは、フキダシのセリフで発生する。

監視するべきイベント

概ね二種類。一つはエレメントを表示するために必要な(画像などの)データを取得して準備が完了したことを示すイベント。ユーザが入力フォームで更新を行ったことを示すイベント。

Dock

実のところかなり複雑な構成になっている。基本的にはjquery UIのタブコンポーネントを必要に応じてネストしているだけなのだが、マニフェストに則って動作させるため、いろいろややこしい。

Dockが直接管理するタブのことをbayと言う。大きなbayとして三つの機能がある。パネル全体の状態を管理するためのパネルbay、エレメントを管理するためのエレメントbay、脚本を管理するためのシナリオbayである。エディタ起動時には、パネルbayが選択されている。

パネルbay

パネルエディタでは、ルートアイテムとなるパネルとエレメントは別の入力系統として扱う。したがって、パネルとエレメントは別のbayで管理する。 パネルbayでは、パネルの入力フォームがsubmitボタン付きで表示される。エディタで編集した結果は、このボタンをクリックすることで、サーバに送信される。

エレメントbay

ルートアイテムに直接ぶら下がるエレメントを管理するためのbay 。複数のエレメントを扱うのでタブを使って管理する。このタブをboardという。ボードのラベルにはエレメントのシンボルが表示される。ボディーには入力フォームが表示される。 submitボタンは表示されない。

入力フォームの切り替え

ユーザはボードのラベル(シンボル)を見て、修正したいエレメントをクリックで選択する。そして、ボードのボディーに切替表示された入力フォームで修正を行う。

優先順位の入れ替え

ラベルはドラッグ操作で順序を入れ替えることができる。エレメントはZ軸順で表示されるので、奥にあるエレメントほど上に、手前にあるエレメントほど下に入れ替えていく。

Jquery UIのソート処理は自前で順序を管理しなければならない。基本は入れ替えイベント発生を監視して、 htmlタグのIDをもとに順序割り出すのだが、将来的にパネルエディタを複数起動するようなこともあるかもしれないので、 IDで管理するのはよろしくない。そこでHTMLタグの属性を拡張してZ軸データを埋め込むことにした。

エレメントのリムーブ

ボードのラベルにはリムーブボタンも表示される。ユーザーこのボタンで不要なエレメントを取り除くことができる。

エレメントのリムーブは、モデルに削除フラグを反映した後、イベントでパネルボディーに伝達される。サーバに保管されているエレメントをリムーブする際には、サーバにも削除されたことを伝えなければならないので、これをパネルエディタの管轄からはずしてはならない。サーバに保存されていないエレメントについては削除フラグに関係なく、表示上に反映させるだけで良い。

エレメントの新規作成

エレメントボードの他に新規エレメントボードが表示される。ユーザはこのボードから、新しいエレメントをパネルに追加することができる。このボードは通常のエレメントボードとは全く違う特殊な性質のボードである。複雑な機能なので、詳しくは後ほど行う。

初期化

エレメントのソート パネルエディタは起動したとき、ボードタブをZ軸順に並べ直す。パネルエディタはDockに対して、 fetchが完了したエレメントからボードに追加する。非同期によるものなので、順序保証されていない。よって、エレメントボードが追加される都度、ソートし直す。

初期表示

本当は一番奥のエレメント(一番上のラベル)の入力フォームを表示させたいのだが、やり方がよくわからないので何も表示していない。

新規エレメントボード

追加できるエレメント一覧がタブで表示される。このタブをリソースボードと言う。ここには、コマ絵・フキダシ・絵地・色地などのエレメントがラベル部分にアイコンで表示されている。ユーザはアイコンをクリックすることで、追加したいエレメント種別を選ぶ。それによって、ボディーにダイアログが表示される。

エレメントの追加順位

新規エレメントボードは、他のエレメントボードと同様にラベルのドラッグで順序を入れ替えることができる。これは、追加されるエレメントをこの順位で挿入することを意味する。

対応エレメントの確認

エレメントのラベルにマウスポインタをオーバーさせることで、パネルボディー上で対応するエレメントをハイライトすることができる。パネルボディー上のエレメントにマウスポインタをオーバーさせると、エレメントボードのラベルをハイライトすることができる。パネル(ルートアイテム)はこのUIには対応しない。

入力イベント

フィールドを更新した時に入力イベントを発火する。更新の検出はinputタグをjqueryのchangeイベントで監視する。イベントではフィールドに紐付けられたモデルをサイレントオプション付きで更新した後に、モデルに対して通達する。パネルボディーはこのイベントを監視してエレメントを再描画する。

入力ヘルパー

ヘルパーはエレメントをサイレントオプション付きで更新した後、入力イベントを発火させる。

入力フォーム

ルート、エレメントともにフォームのマニフェストに従って動作する。

入力値の検証

入力時に不正があるときは、入力フィールドをハイライトする。タブを切り替えた時に見落とさないように、ベイのラベルとエレメントのラベルにも警告マークをつける。

サーバへの送信

パネルエディタ(のDock)が管理しているコマデータを送信する。パネルエディタはネストしたモデルを編集するのだが、backboneはそこには対応しない。よって、railsフレームワークに準拠したデータを送信しなければならない。

使えそうなプラグインを見つけた。

https://github.com/dtmtec/backbone-nested-attributes

ぺったんRはいろいろややこしいので、これをそのまま使えるかどうかは不明。少しだけコードを追ってみる。どうやら、toJSONメソッドを書き換えることで対応しているみたい。 saveメソッドが呼び出されたとき、Backbone.syncメソッドが呼び出されるが、これがattributesのハッシュデータをjson文字列に変換している。

最終的には、Backbone.syncがajax送信を行うが、この時送信されるデータ(入力データ)はjson文字列に変換されたものである。

データに不備があり、サーバがエラーを返したときは、コンソールにレスポンスを表示する。エラーメッセージを解析すれば、どのエレメントの値に問題があるのかを追跡できるだろうが、大分手間がかかりそう。

コンソールbay

サーバからのレスポンスなどを表示するためのタブ。開発者向け。