2014年4月6日日曜日

Applying <dialog> polyfill from Dart

<dialog>要素をDartから使おうとしたときにちょっとハマったのでここでまとめておく。

<dialog>

dialog要素はhtml要素の一つで、名前の通り何らかのダイアログを示すものだ。今はclass定義やらで擬似的にダイアログを表示するのが主流(例:Bootstrap)ななか、汎用的に使われてるしいっそ専用のタグを用意すればいいんじゃない(ついでにブラウザ側でダイアログを特別扱いできればいろんな問題も解決するし)、という流れででてきたのがdialog。実際に動作する様子はここのデモサイトで見られる。

現時点でdialogを実装しているのはChromeのみで、そのChromeも明示的にフラグ(enable-experimental-web-platform-features)を有効にしないと使うことができない。ちなみにcaniuse.comで調べてみたらマイナーすぎて登録すらされてなかった

<dialog> polyfill from Dart

そんな状況のdialog要素をまともに使おうとすると当然他のブラウザ対応が問題になってくる。Googleの人もそのへんはちゃんとわかっていて、他ブラウザにも擬似的にdialogを表示できるようにするpolyfillを用意してある

このpolyfillをDartから適用しようとしたときに少しハマって、色々試した末なんとか上手く行ったのでここにサンプルを置いておく。

dialog_sample

Github上に置いたサンプルプロジェクトはここで、実際にDartiumから動作してる様子が下のキャプチャ。dart2jsでJavascriptに変換してFirefoxからも動くことは確認している。

中身は単にダイアログを出してるだけのなんてこと無いサンプルで、主要なコンポーネントのうちコード的に重要なのは次の部分。

  @override
  void enteredView() {
    super.enteredView();

    // Apply style from parent in order to use bootstrap
    shadowRoot.applyAuthorStyles = true;

    _dialog = shadowRoot.querySelector("#sampleDialog");
    if (_dialog is UnknownElement) {
      print("<dialog> not supported on this browser. Applying polyfill");
      var pf = context['dialogPolyfill'];
      pf.callMethod('registerDialog',[_dialog]);
    }
    _dialog.on["close"].listen((data) {
      print("Dialog closed");
    });
    _dialog.on["cancel"].listen((data) {
      print("Dialog cancelled");
    });
  }

  void openDialog(Event e, var detail, Node target) {
    if (_dialog is DialogElement) {
      var dlg = _dialog as DialogElement;
      dlg.showModal();
    } else {
      // Native dialog not available.
      // Call polyfilled function
      var obj = new JsObject.fromBrowserObject(_dialog);
      obj.callMethod("showModal",[]);
    }
  }

Dart自体にはDialogElementが既に定義されていて直接DialogのAPIが叩けるんだけど、ブラウザ側がDialogに対応していない場合はUnknownElementとして返ってくるようになっている(少なくとも今は)。なので最初にDialogElementが取得できるかを試すことでDialog対応しているかを確認している。

もしDialogに対応してる場合はそのまま使えばいいし、対応してない場合はdialogPolyfill.registerDialog関数を適用してdialogのpolyfillをすればいい(必要なスクリプトやスタイルは事前にindex.htmlで読み込み済みという前提)。polyfill自体はdialogPolyfill.registerDialogをdart:jsライブラリ経由で呼べばできる。あとdialog専用のcancel/closeイベントハンドラもいまあるAPIからそのままハンドラを登録できる。

最後にちょっとハマったのがpolyfillされた版のshowModal関数を呼ぶところ。UnknownElementとして見えている要素に対してshowModal関数を呼ぼうとすると「そんな関数知らない」と蹴られてしまう。これはpolyfillによってJavascript側へ追加された関数やらはDartからは見えないことに起因する。なので、やはりこちらもdart:jsライブラリのJsObject.fromBrowserObjectでJavascript側のオブジェクトへ取得し、直接showModal関数を呼び出すことで解決している。