system.js について

なぜ Web サービスは終わるのか?なぜそんなサービスを使ってしまい、なぜ僕はまたコンテンツを失うのか?

使っていた Web サービスが終わり記録を失ってしまった。ちょうど切羽詰っているときで終了のアナウンスを見逃していた。バックアップも取れていない。僕は激しく憤りを覚えるけど、みんなはもう慣れっこなのだろうか?さすがにみんなが困るサービスは、つまりアクティブなユーザーが多いサービスはなくならないけど、僕は変わり者なのか?ユーザーの少ないサービスに当たってしまう。

いいや、アクテイブなユーザーが居なくなった時点でそこにある記録は失われていいのだろうか?そんなことは無い。僕らはピラミッド建設労働者のトイレの落書きの発見に悦ぶような、そんな生き物だから。

解決は難しいけど誰かが上手い手を用意してくれるまで、とりあえずそこまで生きていればいい。

単純にサービスの生存率を少し上げる程度の効果しかないけど、サービスが生き残るためにクライアント側にできることは?という観点で考えました。

  • コアでないサービスは外部 Web API を使って提供する。
    • 異なるドメインから提供されたデータを WebAPI 毎に書かれたドライバによってファイルシステムに蓄え、一元的に管理する.
  • サーバへのアクセスを極力減らす
    • 画像等のリソースのコントロール。まだ画面の外にあるうちは読み込まない。
    • ワンページアプリケーションにして Ajax を節約する。

デスクトップアプリのような Web アプリケーションのために

  • ひとつのページで複数のアプリケーションを起動・連携.
    • 実際には各アプリケーションは iframe 内に生成・表示して iframe の削除によるメモリ解放を使って長時間滞在してもメモリが増えないようにする.
  • DOM ツリーに依存しないイベント伝播を行う.PointingDeviceEventTree ClientEventTree

開発方針の変遷

当初は軽量なライブラリで、アプリケーション作者は自分の書きたいように画面を書けばいい、と思っていた. ところが、PointingDeviceEventTree の辺りから考え方が変わってきてしまっている.

画面・UI について

クロスブラウザな画面構築を目標にするなら、アプリケーションのコードは window や document といったブラウザオブジェクトには直接触るべきではなくて、system.js の用意する API から 画面( html ) をいじるべき. ほとんどのライブラリは DOM API レベルでのクロスブラウザしか行っていなくて Html+css レベルのクロスブラウザはプログラマー任せになっている。(jQuery の例では IE での opacity 時に zoom ハックをするなど一部にライブラリで行っているものもある)

また、これからの画面サイズや入力インターフェイスの多様化を考えると、今日正しくライブラリの作法に乗っ取って書いたマルチプラットフォームアプリケーションが 5 年後にも動いている保証は無い。

マルチプラットフォーム Web アプリケーションのためには、キーボード・マウス・タッチ といったイベントを抽象化する層が必要で、その上でアプリケーションは画面を描かずにロジックだけを記述し、描画はフレームワークがデバイス情報を収集して判断する。

というわけで、だんだんおせっかいなフレームワークになってきてしまっている、、、

開発中の画面

ホーム画面.ここにアプリケーションや(作品)データへのアクセスがタイル状に並ぶ.アプリケーションにオーバーレイして別のアプリケーションを起動できる.

パネルエディターを起動.

システムのテスト用に移植したゲームアプリの起動画面.マウスイベントはシステムのAPIから行っている.

概要

アプリケーションやファイルドライバーの登録を契機にシステムの関数を呼べるようになる.アプリケーションやドライバーを APIユーザー と呼ぶ.他にシステム内にあって API を利用するスーパーユーザーがある. Event や Ajax, Timer などは システムの関数から利用することがお行儀がよい.これは、KeyEvent で current なアプリケーションに対してだけイベントを流すように最適化が行われたり、shoutdown したアプリケーションの未解除のイベントや UI コンポーネントが解放されるため.また非同期なコールバックがアプリケーションの終了後に呼ばれることを防ぐ.

Class

Class を定義し システムの管理下に置く.参考 StudyOfJsClass

js には通常では備わっていない 継承、Abstract クラス、 プライベートなメソッド・値(プライベートクラスを定義して行う)などをそれっぽく行う.

全てのクラスと、全てのインスタンス( pool が有効の場合 )への参照が保持される.

  • create : function()
    • 1. Class.create( opt_displayName, opt_classSettings, opt_privateClass, props ) でクラスを登録.props 以外の前3つは省略が可能.
    • 2. コンストラクタ となるメソッドは、props の Constructor : function( arg ){ ... }, に書く.Constructor は無くてもいい.
    • 3. new で インスタンス生成.
    • 4. kill() でオブジェクトをクリーンして削除、pool が有効の場合はプールされる.
    • 5. オブジェクトがプールされている場合 new でプールされたインスタンスが返される.Class.POOL_OBJECT を指定.
    • 6. プライベートなデータ用のクラスとして定義したい場合は、Class.PRIVATE_DATA をあわせて指定する.
      • プライベートな値へのアクセスには TestClass.newPrivateData( instance )、TestClass.getPrivateData( instance ) で行う.このクラスは new できない.TestClass.newPrivateData( instance ) とする.
      • クラスを隠蔽することで、プライベートへアクセスができなくなる.つまり厳密なプライベートではない.但しライブラリの非公開なプロパティには触せない、という意図には十分.
      • プライベートなインスタンスを引いてくるにはインスタンス配列から探すことになるため、インスタンス数が増えると不利に.プライベートな値は、直接関連する他のプライベートな値にアクセスできる、という実装にすることで、このロスを緩和する.PointingDeviceEvetTree の node クラスが参考になる.
  1. var TestClass = Class.create(
  2. 'Test Class', Class.POOL_OBJECT | Class.FINAL {
  3. Constructor : function( msg ){
  4. this.msg = msg;
  5. },
  6. alert : function(){
  7. alert( this.msg );
  8. }
  9. });
  10. var test = new TestClass( 'hello' );
  11. test.alert();
  12. test.kill();
  13. var test = new TestClass( 'hello !!' );
  14. test.alert();

SystemTimer

  • add: function( _apiuser, _callback, _time, _once )
  • remove: function( _apiuser, _callback )

setInterval 相当の関数呼び出し.タイマーを大量に使う場合、window.setInterval() を使うより最適化されている、はず.参考 http://sawat.jf.land.to/round_circles/

AsyncCall

  • add: function( _apiuser, _callback, _argments )
  • remove: function( _apiuser, _callback )

setTimeout を使った非同期で関数を呼び出す. apiuser を保持していて( apiuser は主にアプリケーションのこと )アプリケーションがシャットダウンした場合は自動で remove される. addEventListener などで関数を非同期で呼び出したいときに使用する.( ie でのローカルリソースへの $.ajax() ではその関数内で完了イベントが呼び出される挙動になる.) さらに処理のこぼれが防げる.( Opera での 通信・flash 連携などで.setTimeout で負荷が下がるから? )

File

File API:Directories and System とメソッドをあわせる.いずれ.

Application

current な Application の管理.ブラウザのリサイズベントを current なアプリケーションに流す. マウス(等の)イベントや、ローカルからブラウザ画面への ファイルのドロップイベント、を受け付ける レイヤー を最前面に配置して、イベントの独自伝播を行う.

アプリケーションの起動プロセス

登録された アプリケーションクラス の prototype に AbstractApplication インスタンスが追加される. その アプリケーションクラスが new される.この時点で application.rootElement に div 要素が作られる.

  1. appClass.prototype = new !AbstractApplication( appClass, isOverlay );
  2. var application = new appClass();
rootElement が body に追加され init() が呼ばれる.
init 内で onInit() が呼ばれる.
  1. body.appendChild( application.rootElement );
  2. application.init();
setTimeout のタイマーを挟んで、.open() が呼ばれる. open 内で onOpen() が呼ばれる.但し、

  • onInit() 内で application.fetchCSS( url ) をしていた場合、 stylesheet の取得を待ってから .onOpen() が呼ばれる.
  • rootElement.innerHTML がセットされている場合、rootElement.firstChild にアクセスできるようになった後に onOpen() が呼ばれる.
注意
  • IE では document に appendChild( elm ) する前に、elm の属性をいじるとメモリリークしてしまう.そこで、init() 前には rootElement ができて追加されている.Internet Explorer リーク パターンを理解して解決する http://msdn.microsoft.com/ja-jp/library/bb250448
  • rootElement.innerHTML = '<div>~~'; した直後に、新たに追加した要素のサイズが取得できないかもしれない.タイマーを挟むのが安全.onInit() で rootElement.innerHTML にセットし、onOpen() 以降で要素にアクセスするのが安全(っぽい.)
  • elmRoot.innerHTML = "<p>段落</p>" では 文字列の変更記録のみ行われて、実際に文字列が dom に変換されるのは、1/60 秒とかのタイミングでやってくる 再描画のとき.そのため、innerHTML で追加した要素にアクセスするには !!elmRoot.firstChild が true になるまで待つのが良いはず.

Home

ホーム画面を表示するアプリケーションをシステムに登録する.ホーム画面への掲示物(タイル)を受け付ける.

Page

静的な html タグのノードリストを記録して ページアプリケーションとして登録.静的 html の表示 ⇔ ホーム画面 の切り替えができるようにする.

Event

ReadyEvent

DOMContentLoaded イベントをキャッチ.

ResizeEvent

ブラウザの表示エリアの変更を通知.

MouseEvent

  • add: function( _application, _element, _eventType, _handler )
  • remove: function( _application, _element, _eventType, _handler )

クリックイベントを正しく発行するためのヘルパーを持つ.

KeyEvent

KeyEvent は document 及び テキスト入力フォーム部品 に対してしかまともに働かない( ie ) そこで document への キーイベントを current な application に対してだけ流すようにする.

PointigDeviceEventTree

イベント伝播ツリーを定義して、DOM 順に拘束されないイベント伝播を行う.ClientEventTree

Css

link タグの追加による動的な stylesheet の追加.

Overlay

current な Application にオーバーレイさせて application を起動する.

UI

  • createUI( _apiuser )

テキストinput や コンボボックスなどのフォーム部品を発行する.フォーカスやキー操作を管理する.

Finder

  • createFinder( _apiuser, _tree, _elmRoot, ... )

ファイルのエクスプローラ