2014年11月11日火曜日

Dart事始め

Dartの「GET STARTED」の一連のステップをやってみた。


「GET STARTED」で最終的にできあがるWebアプリの仕様

  • テキストフィールドへ名前が入力されると、右側のバッジ部分に「名前 the 称号」と海賊っぽく表示する

  • テキストフィールドが空の場合は、ボタンを押す度に「名前 the 称号」の組み合わせをランダムに表示する

  • テキストフィールドが空かどうかで、ボタンのラベルを変える
  • テキストフィールドへ入力すると、ボタンを無効化する
  • テキストフィールドへ入力する度に「名前 the 称号」の組み合わせをランダムに表示する
  • 「名前 the 称号」が変わる度に、名前と称号をローカルストレージへ保存する



Dartで書くときのポイントについて色々と気づき

  • importでライブラリ全体をインポート。特定のクラスをインポートしたい場合はshowで
  • final, staticなどはJavaと同じ意味で使用可能
  • privateキーワードは無い。_を付けた変数がprivate扱いになる
  • プログラムの実行主体はvoid main()
  • 入出力ストリームの監視はlisten()で
  • JavaScriptのPromisesと同じ扱いでFutureオブジェクトが使える。then()やcatchError()で、onSuccessとonErrorの扱いを切り分ける
  • カスケードオペレーター(..)で、あるオブジェクトの複数のプロパティへアクセスできる
  • window.localStorage['キー']でローカルストレージへアクセス
  • List<String>などGenericsが使用可能
  • getterは、戻り値 get プロパティと定義する。例えば、getPirateName()はString get pirateName
  • => はreturn expr;の省略形として使える
  • $は文字列内で変数を明示する時に使える(print('Error occurred: $error');)
  • コンストラクタも定義できるPirateName(){}だけでなく、PirateName.fromJSON(){}といった定義も可能
  • オプションの引数はcurly brackets({})で指定可能
  • 型変換はasで(e.target as InputElement)
  • querySelector()などでidを渡してDOM操作することになる。値のバインディングは Angularなどで別途自分でやる



「GET STARTED」で最終的にできあがるWebアプリのリソース

  • piratebadge.css
  • piratebadge.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Pirate badge</title>
    <meta name="viewport"
          content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="piratebadge.css">
  </head>
  <body>
    <h1>Pirate badge</h1>
    
    <div class="widgets">
      <div>
        <input type="text" id="inputName" maxlength="15" disabled>
      </div>
      <div>
        <button id="generateButton" disabled>
          Aye! Gimme a name!
        </button>
      </div>
    </div>
    <div class="badge">
      <div class="greeting">
        Arrr! Me name is
      </div>
      <div class="name">
        <span id="badgeName"> </span>
      </div>
    </div>
    <script type="application/dart" src="piratebadge.dart"></script>
    <script src="packages/browser/dart.js"></script>
  </body>
</html>

  • piratenames.json : 名前と称号のリストを含んだJSONファイル
{ "names": [ "Anne", "Bette", "Cate", "Dawn",
        "Elise", "Faye", "Ginger", "Harriot",
        "Izzy", "Jane", "Kaye", "Liz",
        "Maria", "Nell", "Olive", "Pat",
        "Queenie", "Rae", "Sal", "Tam",
        "Uma", "Violet", "Wilma", "Xana",
        "Yvonne", "Zelda",
        "Abe", "Billy", "Caleb", "Davie",
        "Eb", "Frank", "Gabe", "House",
        "Icarus", "Jack", "Kurt", "Larry",
        "Mike", "Nolan", "Oliver", "Pat",
        "Quib", "Roy", "Sal", "Tom",
        "Ube", "Val", "Walt", "Xavier",
        "Yvan", "Zeb"],
  "appellations": [ "Awesome", "Captain",
        "Even", "Fighter", "Great", "Hearty",
        "Jackal", "King", "Lord",
        "Mighty", "Noble", "Old", "Powerful",
        "Quick", "Red", "Stalwart", "Tank",
        "Ultimate", "Vicious", "Wily", "aXe", "Young",
        "Brave", "Eager",
        "Kind", "Sandy",
        "Xeric", "Yellow", "Zesty"]}

  • piratebadge.dart : Dartプログラム
// Import Dart Library.
import 'dart:html';
// 'show' keyword, which imports only the specified classes.
import 'dart:math' show Random;
import 'dart:convert' show JSON;
import 'dart:async' show Future;

// Declare button element as a global variable.
ButtonElement genButton;

// Declare span element as a global variable.
SpanElement badgeNameElement;

// key-value pairs string save to local storage.
final String TREASURE_KEY = 'pirateName';

// App starts here.
void main() {
  // Stash the input element in a local variable.
  InputElement inputField = querySelector('#inputName');
  // Listen input streams.
  inputField.onInput.listen(updateBadge);
  genButton = querySelector('#generateButton');
  genButton.onClick.listen(generateBadge);
  badgeNameElement = querySelector('#badgeName');

  // Call the function, which returns a Future(silimar to JavaScript Promises).
  // Using underscore (_) as a parameter name indicates that the parameter is ignored.
  PirateName.readyThePirates().then((_) {
    // on success
    inputField.disabled = false; // enable
    genButton.disabled = false; // enable
    // Retrieve the name from local storage.
    setBadgeName(getBadgeNameFromStorage());
  }).catchError((error) {
    // '$' indicates that this is a variable, not a string.
    print('Error initializing pirate names: $error');
    badgeNameElement.text = 'Arrrr! No names.';
  });
}

void updateBadge(Event e) {
  // use 'as' to type casting
  String inputName = (e.target as InputElement).value;
  setBadgeName(new PirateName(firstName: inputName));
  if (inputName.trim().isEmpty) {
    // The cascade operator (..) allows you to perform multiple
    // operations on the members of a single object.
    genButton
        ..disabled = false
        ..text = 'Aye! Gimme a name!';
  } else {
    genButton
        ..disabled = true
        ..text = 'Arrr! Write yer name!';
  }
}

void setBadgeName(PirateName newName) {
  if (newName == null) {
    return;
  }
  querySelector('#badgeName').text = newName.pirateName;
  // Save the name to local storage.
  window.localStorage[TREASURE_KEY] = newName.jsonString;
}

void generateBadge(Event e) {
  setBadgeName(new PirateName());
}

// The function retrieves the name from local storage and
// creates a PirateName object from it.
PirateName getBadgeNameFromStorage() {
  String storedName = window.localStorage[TREASURE_KEY];
  if (storedName != null) {
    return new PirateName.fromJSON(storedName);
  } else {
    return null;
  }
}

// declare class
class PirateName {
  // final variables cannot change.
  static final Random indexGen = new Random();
  // Declare generic type—List.
  static List<String> names = [];
  static List<String> appellations = [];

  // Private variables start with underscore (_).
  // Dart has no private keyword.
  String _firstName;
  String _appellation;

  // Provide a constructor for the class.
  // curly brackets{} indicates optional parameters.
  PirateName({String firstName, String appellation}) {
    if (firstName == null) {
      _firstName = names[indexGen.nextInt(names.length)];
    } else {
      _firstName = firstName;
    }
    if (appellation == null) {
      _appellation = appellations[indexGen.nextInt(appellations.length)];
    } else {
      _appellation = appellation;
    }
  }

  // The constructor creates a new PirateName instance
  // from a JSON-encoded string.
  PirateName.fromJSON(String jsonString) {
    Map storedName = JSON.decode(jsonString);
    _firstName = storedName['f'];
    _appellation = storedName['a'];
  }

  // Declare a class level method.
  static Future readyThePirates() {
    // If you spell miss the json file name,
    // executes PirateName.readyThePirates().catchError() in main().
    var path = 'piratenames.json';
    // getString() returns Future is used as arguments
    // to _parsePirateNamesFromJSON().
    // then() is a callback function is called when the Future completes successfully.
    return HttpRequest.getString(path).then(_parsePirateNamesFromJSON);
  }

  // Declare a instance and private method.
  static _parsePirateNamesFromJSON(String jsonString) {
    Map pirateNames = JSON.decode(jsonString);
    names = pirateNames['names'];
    appellations = pirateNames['appellations'];
  }

  // Provide a getter for the private variables
  // The fat arrow ( => expr; ) syntax is a shorthand for { return expr; }.
  String get pirateName => _firstName.isEmpty ? '' : '$_firstName the $_appellation';

  // Add a getter to the PirateName class that encodes a pirate name in a JSON string.
  String get jsonString => JSON.encode({
    "f": _firstName,
    "a": _appellation
  });
}


実行は以下から確認できる。

Step6 Run the app.