Posted at

donejsをQuick Startをやってみる

More than 3 years have passed since last update.

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はそこまで複雑ではないため1ファイルで作成します。

~/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使ってるところも

新しい技術を積極的に取り入れていく姿勢が見れると思います。

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

なかなか面白いと思います。

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