Dart
WebComponents
DartDay 24

DartでCustom Elements

More than 3 years have passed since last update.

こんにちは、らこです。Web ComponentsのCustom Elementsでアレする話をします。

Polymer is large

Custom ElementsといえばPolymerみたいな風潮出来始めてますよね。Polymerはほんとうに良く出来てると思いますし、Polyfillに関しては言うこと無いと思います。ですが、本質的に自分が何を達成したいのか考えるとPolymerは大きすぎるような気がしました。

やりたいこと=HTMLの分割

Custom Elementsを使って何をしたいかというと私はHTMLを分割したいだけです。独自タグを目印に別のHTMLを注入できればそれで満足です。そこにPolymerは必要なくて、ただWeb Componentsのpolyfillがあればいいだけだと気づいたので自分で作ることにしました。

生のDartで実装する

というわけで、Custom ElementsとHTML Importsもどきを実装してみました。
エントリポイントはこちら

web/index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>custom_elements_dart</title>
  <script src="packages/web_components/webcomponents.min.js"></script>
</head>
<body>
  <sample-element>
  </sample-element>
  <script type="application/dart">
    import 'dart:html';
    import 'package:custom_elements_dart/sample_element.dart';

    void main() {
      document.registerElement("sample-element", SampleElement);
    }
  </script>
  <script src="packages/browser/dart.js"></script>
</body>
</html>

エントリポイントでやってるのは、sample-elementタグの登録だけです。

  document.registerElement(String tagName, Type htmlElementType);

を呼び出すことで、タグ名と、それに対応するHtmlElement型のクラスをバインドします。

lib/sample_element.html
<div>
  <h1 id="greeting"></h1>

  <p>This is sample element.</p>

  <input id="nameInput" type="text"/>
</div>

読み込まれるHTMLです。#greeting#nameInputを定義してます

lib/sample_element
library sample_element;

import "dart:html";
import 'dart:async';
import "package:mustache/mustache.dart" as mustache;

class SampleElement extends HtmlElement {

  SampleElement.created() :super.created(){    
    onInput.matches("#nameInput").listen((e) {
      var name = (e.matchingTarget as TextInputElement).value;
      _greet(name);
    });

    getTemplate().then((template) {
      appendHtml(template);
      _greet("World");
    });
  }

  String _template;

  Future<String> getTemplate() {
    if (_template != null) {
      return new Future.value(_template);
    }
    return HttpRequest.getString("packages/custom_elements_dart/sample_element.html")
    .then((v) => _template = v);
  }

  _greet(String name) {
    if (name == null) {
      name = "World";
    }
    var greeter = mustache.parse("Hello {{name}}!");
    var output = greeter.renderString(
      {
        "name": name
      });
    this.querySelector("#greeting").innerHtml = output;
  }
}

追加した<sample-element>の中身です。

  SampleElement.created() :super.created(){

    onInput.matches("#nameInput").listen((e) {
      var name = (e.matchingTarget as TextInputElement).value;
      _greet(name);
    });

    getTemplate().then((template) {
      appendHtml(template);
      _greet("World");
    });
  }

HtmlElement.created()HtmlElementクラスのコンストラクタで、継承する際に必ず実装しないといけません。まず#nameInput要素からonInputイベントを拾って、ビューを更新するリスナを登録してます。

次にテンプレートHTMLを読み込んで、自身にappendした後に最初のビュー更新をかけています。

  String _template;

  Future<String> getTemplate() {
    if (_template != null) {
      return new Future.value(_template);
    }
    return HttpRequest.getString("packages/custom_elements_dart/sample_element.html")
    .then((v) => _template = v);
  }

ここがHTML Importもどきの部分です。Getリクエストで非同期にテンプレートHTMLを取得します。取得後はキャッシュしています。今思うとこれstaticにしたほうがいいですね。

  _greet(String name) {
    if (name == null) {
      name = "World";
    }
    var greeter = mustache.parse("Hello {{name}}!");
    var output = greeter.renderString(
      {
        "name": name
      });
    this.querySelector("#greeting").innerHtml = output;
  }

ビュー更新部分です。あいさつのテンプレートをMustacheでパースして、与えたnameを食わせています。文字列が出来上がったら#greeting要素に流し込んでいます。

こんな感じ

テキストボックスの変更が反映されます。MV*っぽい動きです。

customelement.gif

Polymer無くてもこれくらいはできる

結局Polymerもやってることはこれの応用というか、Dartでできることしかやってないので、当然ですがこのくらいはできます。ライブラリに頼るのもいいですが、自分でやるのも楽しいです。

めちゃくちゃ簡単

見てもらってわかるようにDartの場合、HTML要素とクラスが1:1対応してるので、めちゃくちゃ簡単に、わかりやすくCustom Elements使えます。HtmlElementを継承したクラスをregisterElementするだけです。冗談みたいに簡単に使えるのでJSerの人もCustom Elementsで遊ぶためだけにDartやってほしいくらいです。


以上DartでCustom Elements作る方法でした。Polymerもいいけど自作もね。

いよいよ次で最後ですね。最終日も私です。よろしくお願いします。