KnockoutHxを使って小規模SPA


knockoutjsとhaxeを組み合わせて小規模なSPAを作成してみるサンプルです。

knockoutjsをHaxeで利用するためのexternクラス

pushstateに対応したフロントエンドルーティングライブラリ

サンプルコード

タブメニューをクリックするとページコンテンツが切り替わります。

<head>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css"/>
</head>
<body>
  <div class="jumbotron">
    <section id="header">
        <h1>KnockoutHx Sample</h1>
        <p>Knockoutjs + Haxe sample application.</p>
    </section>
  </div>

  <div class="container">
    <div class="row">
      <div class="col-md-12">
        <section id="menu">
          <ul class="nav nav-tabs nav-justified" data-bind="foreach: menus">
            <li role="presentation" data-bind="css: { active: is_active }">
              <a data-bind="text: name, attr: { href: url }, click: movePage">Top</a>
            </li>
          </ul>
        </section>
        <section id="contents">
          <h1 data-bind="text: title"></h1>
          <p data-bind="text: body"></p>
        </section>
      </div>
    </div>
  </div>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.1.0/knockout-min.js"></script>
  <script src="app.js"></script>
</body>
import js.Browser;
import knockout.Knockout;
import knockout.Observable;
import routerhx.Router;

typedef Menu = {
  name: String,
  url: String,
  is_active: Observable<Bool>,
  movePage: Void -> Void
}

typedef Contens = {
  title: Observable<String>,
  body: Observable<String>
}

class Main {
  static var menus: Array<Menu> = [
    { name: "Top", url: "/", is_active: Knockout.observable(true), movePage: topPage },
    { name: "Outline", url: "/outline", is_active: Knockout.observable(false), movePage: outlinePage },
  ];

  static var contents: Contens = {
    title: Knockout.observable(""),
    body: Knockout.observable("")
  }

  static function main() {
    var menu_dom = Browser.document.getElementById("menu");
    var contents_dom = Browser.document.getElementById("contents");

    Knockout.applyBindings({ menus: menus }, menu_dom);
    Knockout.applyBindings(contents, contents_dom);

    var r = new Router();
    for (menu in menus) {
      r.addCb(menu.url, menu.movePage);
    }
    r.raisePushState("a", "click", "href", menu_dom);
    r.run();
  }

  public static function topPage(): Void {
    switchActiveMenu("/");
    contents.title.set("トップページ");
    contents.body.set("トップページっぽいテキスト");
  }

  public static function outlinePage(): Void {
    switchActiveMenu("/outline");
    contents.title.set("概要");
    contents.body.set("概要ページっぽいテキスト");
  }

  static function switchActiveMenu(target_url: String): Void {
    for (menu in menus) {
      if (menu.url == target_url && menu.is_active.get() == false) {
        menu.is_active.set(true);
      } else if (menu.url != target_url && menu.is_active.get() == true) {
        menu.is_active.set(false);
      }
    }
  }
}

Haxeで書く場合のポイント

  • obserbしたプロパティへのアクセスはgetsetのアクセサを通して行う。
  • ViewModelになる部分は予めtypedefで構造を定義しておく(規模が大きいならクラスを定義する)
  • あとはリンクを踏んだ時の処理を好きなように書けばOK

昨日までに間に合いませんでした、スミマセンでした。