donejsをQuick Startをやってみる

donejs:https://donejs.com/
JavaScriptフレームワークの寿命:
http://postd.cc/longevity-or-lack-thereof-in-javascript-frameworks/

少し古い記事ですが、ここで紹介されていたdonejsのQuick Startを試してみたいと思います。
わたしも常々このフレームワークいつまで使えるんだろうという危機感とともに開発をしておりました。
長持ちするならそれに越したことはありません。
そんな寿命が長いうえに、Virtual Domなどの技術も積極的に取り込んでいくという姿勢を見せるdonejsに今回挑戦です。内容のわりに結構長いです。

なお本チュートリアルではREST Serverといてdonejsが用意しているものを使用します。実用に際してはREST Serverは別のフレームワークを使うなどして用意する必要がありそうです。

https://donejs.com/Guide.html

#概要
* これからリアルタイムチャットサービスをつくります：http://chat.donejs.com/
* まずインストールとアプリケーションの雛形作成を行います
* つぎにブートストラップのインストール・カスタムタグの作成・ルーティングの設定を行います
* 最後にタブ表示とメッセージ送信機能をつけて完成です
* 最終パートではモバイル版とデスクトップ版のアプリケーションを作成します（今回はやりません）

#Setup
##Install DoneJS
本体をいれます。

```
~/workspace/donejs $ npm install -g donejs
/home/ubuntu/.nvm/versions/node/v4.2.4/bin/donejs -> /home/ubuntu/.nvm/versions/node/v4.2.4/lib/node_modules/donejs/bin/donejs
donejs@0.7.0-pre.1 /home/ubuntu/.nvm/versions/node/v4.2.4/lib/node_modules/donejs
├── q@1.4.1
├── commander@2.9.0 (graceful-readlink@1.0.1)
└── cross-spawn-async@2.1.8 (lru-cache@4.0.0, which@1.2.4)
~/workspace/donejs $ 
```

##Generate the application
```
~/workspace/donejs $donejs init donejs-chat
```
かなり時間がかかります。あと途中で質問が入ります。
試しにやるだけなので全部エンターでいいです。

###Turn on development mode
アプリケーションフォルダに移動します

```
~/workspace/donejs $ cd donejs-chat
~/workspace/donejs/donejs-chat $
```

サーバを起動します

```
~/workspace/donejs/donejs-chat $ donejs develop

> donejs-chat@0.0.0 develop /home/ubuntu/workspace/donejs/donejs-chat
> can-serve --develop --port 8080

can-serve starting on http://localhost:8080
Received client connection
Live-reload server listening on port 8012
```
いまさらですがcloud9上で動作確認してます。デフォルトポートが8080のため変更の必要はありません。必要な人はpackage.jsonのこのへんを変更してください。

```
  "scripts": {
    "test": "testee src/test.html --browsers firefox --reporter Spec",
    "start": "can-serve --port 8080",
    "develop": "can-serve --develop --port 8080",
    "document": "documentjs",
    "build": "node build"
  }
```

コンソール見る限り、ライブリローディングのポートが8012らしいんですが、cloud9、そのポート空いてないんで使えませんね・・・　素直にローカルでやったほうがいいかもしれません。

それはさておきアクセスするとHallo Worldが表示されているはずです。次に行きます。

#Adding Bootstrap
##Install the NPM package
ブートストラップをインストールします。

```
~/workspace/donejs/donejs-chat $ npm install bootstrap --save
npm WARN package.json donejs-chat@0.0.0 No repository field.
npm WARN package.json donejs-chat@0.0.0 No license field.
bootstrap@3.3.6 node_modules/bootstrap
:~/workspace/donejs/donejs-chat $ 
```

##Add it to the page
ページの内容を編集します。原文のほうがわかりやすいと思いますので、そちらを参考に修正してください。
https://donejs.com/Guide.html#section=section_Addittothepage

``` src/index.stache
<html>
  <head>
    <title>{{title}}</title>
    {{asset "css"}}
    {{asset "html5shiv"}}
  </head>
  <body>
    <can-import from="bootstrap/less/bootstrap.less!" />
    <can-import from="donejs-chat/styles.less!" />
    <can-import from="donejs-chat/app" export-as="viewModel" />
    
    <div class="container">
      <div class="row">
        <div class="col-sm-8 col-sm-offset-2">
          <h1 class="page-header text-center">
            <img src="http://donejs.com/static/img/donejs-logo-white.svg"
                alt="DoneJS logo" style="width: 100%;" />
            <br>Chat
          </h1>
        </div>
      </div>
    </div>

    {{asset "inline-cache"}}

    {{#switch env.NODE_ENV}}
      {{#case "production"}}
        <script src="{{joinBase 'node_modules/steal/steal.production.js'}}"  main="donejs-chat/index.stache!done-autorender"></script>
      {{/case}}
      {{#default}}
        <script src="/node_modules/steal/steal.js"></script>
      {{/default}}
    {{/switch}}
  </body>
</html>
```

オートリローディングが生きていれば、変更と同時に画面に反映されるようですが、cloud9上では死んでいるので、F5で更新します。サーバ落としてる人は再起動してください。
donejsのロゴがでかでかと表示されているはずです。

#Routing and components
##Generate custom elements
カスタムタグを作っていきます。
chat-homeというタグを作るには次のようにします。
chat-homeはそこまで複雑ではないため１ファイルで作成します。

```
~/workspace/donejs/donejs-chat $ donejs add component home.component chat-home
   create src/home.component
~/workspace/donejs/donejs-chat $ 
```

つぎにchat-messagesタグをつくります。こちらは少し複雑なため複数ファイルにわけてつくります。

```
~/workspace/donejs/donejs-chat $ donejs add component messages chat-messages
   create src/messages/messages.html
   create src/messages/messages.js
   create src/messages/messages.md
   create src/messages/messages.less
   create src/messages/messages.stache
   create src/messages/messages_test.js
   create src/messages/test.html
~/workspace/donejs/donejs-chat $ 
```

拡張子componentを指定するとファイルが、指定しないとフォルダが作成されると覚えておけばいいでしょう。

##Navigate between pages
まずhome.componentをリンク先に従って編集してください。
https://donejs.com/Guide.html#section=section_Navigatebetweenpages

``` src/home.component
<can-component tag="chat-home">
  <style type="less">
    display: block;
    
    p { font-weight: bold; }
    h1.page-header { margin-top: 0; }
  </style>
  <template>
    <h1 class="page-header text-center">
      <img src="http://donejs.com/static/img/donejs-logo-white.svg"
           alt="DoneJS logo" style="width: 100%;" />
      <br>Chat
    </h1>

    <a href="{{routeUrl page='chat' }}"
       class="btn btn-primary btn-block btn-lg">
      Start chat
    </a>
  </template>
</can-component>
```

routeUrl はパスを取得するhelperだそうです。
次にmessage.stacheを変更します。

``` src/message/message.stache
<h5><a href="{{routeUrl page='home'}}">Home</a></h5>
<p>{{message}}</p>
```

そしてapp.jsにルーティングを追加します。

```
route('/:page', { page: 'home' });
```

``` src/app.js
import AppMap from "can-ssr/app-map";
import route from "can/route/";
import 'can/map/define/';
import 'can/route/pushstate/';

const AppViewModel = AppMap.extend({
  define: {
    message: {
      value: 'Hello World!',
      serialize: false
    },
    title: {
      value: 'donejs-chat',
      serialize: false
    }
  }
});

route('/:page', { page: 'home' });

export default AppViewModel;

```

##Switch between pages
最後にインデックスページの表示をパスによって切り替えます。
修正内容はhttps://donejs.com/Guide.html#section=section_Switchbetweenpages
を見てください。

あんまり説明ないんですが、たぶんさっきのrouteメソッドによって/chatにアクセスするとpage変数に文字列chatが入るようになり、それをもとに表示を切り替えているんだと思います。


``` src/index.stache
<html>
  <head>
    <title>{{title}}</title>
    {{asset "css"}}
    {{asset "html5shiv"}}
  </head>
  <body>
    <can-import from="bootstrap/less/bootstrap.less!" />
    <can-import from="donejs-chat/styles.less!" />
    <can-import from="donejs-chat/app" export-as="viewModel" />
    
    <div class="container">
      <div class="row">
        <div class="col-sm-8 col-sm-offset-2">
          {{#eq page 'chat'}}
            <can-import from="donejs-chat/messages/">
              {{#if isPending}}
                Loading...
              {{else}}
                <chat-messages/>
              {{/if}}
            </can-import>
          {{else}}
            <can-import from="donejs-chat/home.component!">
              {{#if isPending}}
                Loading...
              {{else}}
                <chat-home/>
              {{/if}}
            </can-import>
          {{/eq}}
        </div>
      </div>
    </div>

    {{asset "inline-cache"}}

    {{#switch env.NODE_ENV}}
      {{#case "production"}}
        <script src="{{joinBase 'node_modules/steal/steal.production.js'}}"  main="donejs-chat/index.stache!done-autorender"></script>
      {{/case}}
      {{#default}}
        <script src="/node_modules/steal/steal.js"></script>
      {{/default}}
    {{/switch}}
  </body>
</html>
```
ここまでくるとさっきのロゴの下にstart chatボタンが表示されていると思います。押すと"This is the chat-messages component"と表示されたページに移動します。

#Homepage
##Install bit-tabs
ホームページにタブを導入します。

```
~/workspace/donejs/donejs-chat $ npm install bit-tabs --save
npm WARN package.json donejs-chat@0.0.0 No repository field.
npm WARN package.json donejs-chat@0.0.0 No license field.
bit-tabs@0.2.0 node_modules/bit-tabs
└── cssify@0.6.0 (through@2.3.8)
~/workspace/donejs/donejs-chat $ 
```
これで \<bit-tabs> と \<bit-panel> を使ってタブが実現できます。

##Update the page
では実際にタブを使ってみます。
https://donejs.com/Guide.html#section=section_Updatethepage

``` src/home.component
<can-component tag="chat-home">
  <style type="less">
    display: block;
    
    bit-panel p {
      padding: 10px;
    }
  </style>
  <template>
    <can-import from="bit-tabs/unstyled" />
    <h1 class="page-header text-center">
      <img src="http://donejs.com/static/img/donejs-logo-white.svg"
           alt="DoneJS logo" style="width: 100%;" />
      <br>Chat
    </h1>
    <bit-tabs tabs-class="nav nav-tabs">
      <bit-panel title="CanJS">
        <p>CanJS provides the MV*</p>
      </bit-panel>
      <bit-panel title="StealJS">
        <p>StealJS provides the infrastructure.</p>
      </bit-panel>
    </bit-tabs>
    
    <a href="{{routeUrl page='chat' }}"
       class="btn btn-primary btn-block btn-lg">
      Start chat
    </a>
  </template>
</can-component>
```
正直チャット的には何の意味もありません。たぶんタブを使いたかっただけでしょう。
これでトップページはデモ画面と同じになったと思います。

#Messages page
ではチャット機能を作っていきます。

* REST APIへのリクエスト作成
* チャットメッセージ作成・取得機能
* リアルタイムアップデート

##Generate Message model
ここでmessageモデルにcan-connect's supermodel というものを追加します。最初にREST APIの接続先URLを聞かれますので、今回は http://chat.donejs.com/api/messages を指定します。これでREST通信が可能になります。それ以外はenterで問題ありません。

```
~/workspace/donejs/donejs-chat $ donejs add supermodel message
? What is the URL endpoint? http://chat.donejs.com/api/messages
? What is the property name of the id? id
   create src/models/fixtures/message.js
   create src/models/message.js
   create src/models/message_test.js
~/workspace/donejs/donejs-chat $ 
```

##Use the connection
では追加したREST API Interfaceを使用しましょう。
https://donejs.com/Guide.html#section=section_Usetheconnection

``` src/message/message.stache
<can-import from="donejs-chat/models/message" />
<h5><a href="{{routeUrl page='home'}}">Home</a></h5>

<message-model get-list="{}" class="list-group">
  {{#each ./value}}
    <div class="list-group-item">
      <h4 class="list-group-item-heading">{{name}}</h4>
      <p class="list-group-item-text">{{body}}</p>
    </div>
  {{else}}
    <div class="list-group-item">
      <h4 class="list-group-item-heading">No messages</h4>
    </div>
  {{/each}}
</message-model>
```
これでチャットページにそれっぽいものが表示されるはずです。RESTサーバが共有のため、以前だれかが入力したものが表示されます。

##Create messages
では送信ボタンを作りましょう。
まずmessage.stacheを編集します。
https://donejs.com/Guide.html#section=section_Createmessages

``` src/messages/messages.stache
<can-import from="donejs-chat/models/message" />
<h5><a href="{{routeUrl page='home'}}">Home</a></h5>

<message-model get-list="{}" class="list-group">
  {{#each ./value}}
    <div class="list-group-item">
      <h4 class="list-group-item-heading">{{name}}</h4>
      <p class="list-group-item-text">{{body}}</p>
    </div>
  {{else}}
    <div class="list-group-item">
      <h4 class="list-group-item-heading">No messages</h4>
    </div>
  {{/each}}
</message-model>

<form class="row" ($submit)="send(%event)">
  <div class="col-sm-3">
    <input type="text" class="form-control" placeholder="Your name"
           {($value)}="name"/>
  </div>
  <div class="col-sm-6">
    <input type="text" class="form-control" placeholder="Your message"
           {($value)}="body"/>
  </div>
  <div class="col-sm-3">
    <input type="submit" class="btn btn-primary btn-block" value="Send"/>
  </div>
</form>
```
これでフォームがname,bodyプロパティおよびsendメソッドとバインディングされました。
ではsendメソッドを作成します。message.jsを編集します。

``` src/message/message.js
import Component from 'can/component/';
import Map from 'can/map/';
import 'can/map/define/';
import './messages.less!';
import template from './messages.stache!';
import Message from '../models/message';

export const ViewModel = Map.extend({
  send(event) {
    event.preventDefault();

    new Message({
      name: this.attr('name'),
      body: this.attr('body')
    }).save().then(msg => this.attr('body', ''));
  }
});

export default Component.extend({
  tag: 'chat-messages',
  viewModel: ViewModel,
  template
});
```
new Message(~).save()でRESTリクエストが行われているようです。これでメッセージが保存されます。

がcloud9を使っていたため、ちょっと問題が発生しました。cloud9のページは基本httpsなのでhttpのサーバにポストするとエラーになります。httpでcloud9のページにアクセスし直してください。

##Enable a real-time connection
ではようやく最後のリアルタイム同期です。
ライブラリをインストールします。

```
/workspace/donejs/donejs-chat $ npm install steal-socket.io --save
npm WARN package.json donejs-chat@0.0.0 No repository field.
npm WARN package.json donejs-chat@0.0.0 No license field.
steal-socket.io@1.0.1 node_modules/steal-socket.io
└── socket.io-client@1.4.5 (to-array@0.1.4, component-emitter@1.2.0, indexof@0.0.1, component-bind@1.0.0, backo2@1.0.2, object-component@0.0.3, has-binary@0.1.7, debug@2.2.0, socket.io-parser@2.2.6, parseuri@0.0.4, engine.io-client@1.6.8)
~/workspace/donejs/donejs-chat $ 
```

つぎにsrc/models/message.jsを更新します。
modelsのほうです。注意してください。
https://donejs.com/Guide.html#section=section_Enableareal_timeconnection

``` src/models/message.js
import can from 'can';
import superMap from 'can-connect/can/super-map/';
import tag from 'can-connect/can/tag/';
import 'can/map/define/define';
import io from 'steal-socket.io';

export const Message = can.Map.extend({
  define: {}
});

Message.List = can.List.extend({
  Map: Message
}, {});

export const messageConnection = superMap({
  url: 'http://chat.donejs.com/api/messages',
  idProp: 'id',
  Map: Message,
  List: Message.List,
  name: 'message'
});

tag('message-model', messageConnection);

const socket = io('http://chat.donejs.com');

socket.on('messages created',
  message => messageConnection.createInstance(message));
socket.on('messages updated',
  message => messageConnection.updateInstance(message));
socket.on('messages removed',
  message => messageConnection.destroyInstance(message));

export default Message;
```

まあ見たまんまなんですが、
socket.on('messages \<event> ')でリスナーを登録して同期をとってますね。
ブラウザを二つ開けばリアルタイムに同期がとれていることが確認できると思います。

#終わりに
このあとデプロイ方法とかモバイル、デスクトップアプリへの変換方法が乗っているのですが割愛します。

ちょっとチュートリアルだけだと評価しづらいですね。
とくにルーティング回りにとりあえず感があるので別途調査したいです。

とりあえず学習コストは高めかなと思いましたが、
寿命の長いフレームワークと付き合っていきたいなら
必要コストかなとも思います。
どうせAngularとかも学習コスト高いですし。

当然のようにECMA6Script使ってるところも
新しい技術を積極的に取り入れていく姿勢が見れると思います。

今回は割愛しましたがマルチデバイス対応にも取り組んでいるようですし
なかなか面白いと思います。

思ったより長くなりましたが、この辺で。