過去2回で、HTMLベースのページに、Backbone.jsを導入していく手順を中心にして、Backboneの主機能であるBackbone.ViewとBackbone.Model、Backbone.Collectionについて学んできました。
最終回は、いままで2回で学んだことを組み合わせ、さらにできるだけBackbone.jsの他の機能(Backbone.Sync、Backbone.Events)JavaScriptで動作するまとめエディタ的なものを作っていきましょう。
JavaScirptで動作するまとめエディタのできあがりイメージ 最初に、タイトル・概要を入れて、その後に、段落を追加していき、その中に文章を入れていけるようなNAVERまとめのエディタのテキストのみが入力できることを想定します。 今回の考え方で、テキスト入力の部分を地図や、Facebookのリンク取得モジュール等に置き換えていくことで、リッチなまとめエディタが作成できます。
できあがりイメージは下記のような形となります。
手順としては、まずこのエディタを管理するデータ構造を作成します。 サーバーから、そのデータ構造クラスを利用して初期データの取得や、変更時の保存を行えるようにします。 その後、そのデータを描画するビューを作成して、実際の画面から変更・追加などができる様にしましょう。
今回のワークで書いたサンプルコードはダウンロードからダウンロードできます。
エディタを構築するための準備
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <!DOCTYPE html> < html lang = "en" > < head > < meta http-equiv = "content-type" content = "text/html; charset=UTF-8" /> < title >まとめエディタ</ title > <!--jQueryも併用できるので読み込む--> < script src = "lib/js/jquery-1.10.2.min.js" ></ script > <!--Backboneの読み込み / underscore.jsに依存--> < script src = "lib/js/underscore-min.js" ></ script > < script src = "lib/js/backbone-min.js" ></ script > <!--Twitter Bootstrapを読み込む--> < link href = "lib/bootstrap/css/bootstrap.min.css" rel = "stylesheet" media = "screen" /> < script src = "lib/bootstrap/js/bootstrap.min.js" ></ script > <!-- 2. --> </ head > < body > <!-- 1. HTMLを書いていきます --> </ body > </ html > |
HTMLの宣言については、前回とほとんど同じですので、前回のソースコードをコピーしてきてもらってもかまいません。
では、body要素の中に、エディタのルート要素を書きましょう。1の部分に下記を記述します。
1 | < div id = "editor" ></ div > |
1. まずはデータ構造を作っていきましょう
Backbone.CollectionとBackbone.Modelでスキーマを作る
前回のBackbone.CollectionとBackbone.Modelの使い方を少しおさらいします。 Modelは構造そのもので、いわばテーブルのレコード単位の情報を管理するものであると話をしました。Collectionは、そのModelを1つ以上持つもので、テーブル自体であるということでしたね。
今回の場合は、Collection自体が、エディタの1コンテンツを管理するものであり、段落ごとに、Modelが生成されて、Collectionの中で管理されるのが自然ですね。
必要であれば、ModelがSaveされた時点で、サーバーと同期するなどの仕組みは、Backbone.Syncを利用して、作りあげることができます。
1.の部分にModel用とCollection用のJSを読み込みます。
1 | < script src = "js/app.js" ></ script > |
記述が終わったら、早速中身を書いていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | var app = app || {}; app.model = app.model || {}; app.model.paragraph = Backbone.Model.extend({ defaults: { id: null , type: 'text' , value: null }, validate: function (attrs, options){ //paragraphの型を指定 switch (attrs.type){ case 'text' : break ; default : return '指定されていない型です' ; break ; } //文字数の制限を加える if (attrs.length >= 100){ return '段落の文字数は100文字以下である必要があります' ; } } }); |
今回は初めて、validateという、メソッドを追加してみました。これで、modelにaddする時に値を検証して追加するなどの操作をBackbone管理下で行うことができる様になります。
さらに、コレクションも併せて書いていきます。
1 2 3 4 5 6 7 | var app = app || {}; app.collection = app.collection || {}; app.collection.paragraph = Backbone.Collection.extend({ //4. model: app.model.paragraph }); |
ここまでは、前回のワークでもやりましたので、スムーズに定義ができるのではないかと思います。
fetchを利用して、サーバーから直接値を取得する
fetchメソッドは、Collection、Model両方共に用意されており、モデル単体、コレクションそれぞれへのデータ投入をサーバーから直接行うことができます。
では、早速書いていきましょう。 2.の部分に引き続き、下記のプログラムを記述していきます。
1 2 3 4 5 6 7 8 9 10 11 | < script type = "text/javascript" > $(document).ready(function(){ var m = new app.collection.paragraph; m.fetch({ reset: true //3. }); return; }); </ script > |
DOMが準備された後に、paragraphコレクション(app.collection.paragraph)を起動し、次に、fetch()オブジェクトでサーバーからデータを取得し、collectionにデータを投入しています。
読み込むJSONデータを用意します。
data.json(index.htmlと同じディレクトリ)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | { "success" : true , "result" : [ { “id”: 1, "text" : "1つめの段落です" , "type" : "text" }, { "id" : 2, "text" : "2つめの段落です" , "type" : "text" } ] } |
最後に読み込むJSONのURLとパースの仕方を書いていきます。
リファレンスはわかりにくいので、ここで説明をしていきます。
Backbone.collection.urlについて
読み込み先のURLを書いていくことができます。
2つの書き方がありますが、まずは簡単な方から
1 2 3 4 5 | app.collection.paragraph = Backbone.Collection.extend({ //4. url: 'data.json' , model: app.model.paragraph }); |
今回はこの書き方でデータを読み込むことができます。
URLをグローバル変数等に格納して、設定を読み込んで使うような場合には、
1 2 3 4 5 6 7 | app.collection.paragraph = Backbone.Collection.extend({ //4. url: function (){ return app.config.apiurl + '/data.json' ; }, model: app.model.paragraph }); |
と言うような形で、URLに関数を指定して、取得したいURLが返却されるように書くこともできます。
Backbone.collection.parseについて
実際にデータを読み込む配列があるプロパティを指定するために、parseを使います。
第1引数として、サーバーからのレスポンスがオブジェクト化されたものが渡ってきますので、実際に読み込む配列を戻り値として返すようにすれば、collection.modelsに自動的に投入してくれるようになっています。
1 2 3 | parse: function (res){ return res.result; }, |
簡単ですね。 これで、データの初期化が完了しました。
引き続き、collectionに任意のデータを投入してみましょう。 これは、後にエディターとして、画面操作した値を受け付けて、このプログラムを通して、サーバーに同期されることになります。
3の部分に、下記のプログラムを記述していきます。
1 2 3 4 | var res = m.create({ text: "新しい段落を追加しました" , type: "text" }); |
エントリーポイント部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | < script type = "text/javascript" > $(document).ready(function(){ var m = new app.collection.paragraph; m.fetch({ reset: true //3. success: function(){ var res = m.create({ text: "新しい段落を追加しました", type: "text" }); return; } }); return; }); </ script > |
fetch関数には、オプションとして、reset, removeなどの追加・削除を制御するオプションと、success, complete, errorなどjQueryが持っているコールバックオプションを指定することが可能です。
基本的にjQueryと同じ感覚で利用ができるので、大変覚えやすいですね。
上の例では、m.fetchで、サーバーからJSONを取得して、2件のデータを挿入した後に、successが実行されて、新しい段落が追加されるという流れになります。
データ構造の完成
ここまでで、データ構造が完成しました。 これに画面をつけていくと、いよいよエディターとしてデータ操作できることが想像できますね。
ここまで、合計でまだ、30行も書いていないわけですが、これだけでも立派なアプリケーションの制御が体系的にできるところが、Backbone.jsを利用するメリットではないかと思います。
2. 画面を作っていきましょう
今度はHTML部分を書いていきます。
前回から、Bootstrapを利用していますので、それを前提に画面を作っていきましょう。
1 2 3 4 5 6 7 | <!-- 2. HTMLを書いていきます --> < div id = "editor" class = "container" > < div id = "paragraph" style = "min-height: 100px; background-color: #EEE;" ></ div > < div class = "text-center" style = "margin-top: 10px;" > < div id = "add-btn" class = "btn btn-success" >段落を追加</ div > </ div > </ div > |
少しデザインを整えるためのCSSも書いておきました。
段落追加時のイベント発火を仕掛けよう
では、Backbone.Viewを使って、ボタンの追加がクリックされたときに、イベントが発火されるように仕掛けていきましょう。
app.js 上部に、名前空間の宣言をしておきます。
1 | app.view = app.view || {}; |
さらに、Backbone.Viewを拡張したapp.view.editorを作っていきます。 app.jsの下部で、app.collection.paragraphの直後に書いてきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | app.view.editor = Backbone.View.extend({ el: '#editor' , events: { 'click #add-btn' : 'onAdd' }, onAdd: function (){ console.debug( 'onAdd' ); return ; } }); |
エントリーポイントに下記を追加して、viewを起動します。
1 | var v = new app.view.editor(); |
ここで、段落を追加ボタンをクリックすると、onAddとコンソールに表示されれば成功です。
実際に段落が表示される仕組みについては、前回・前々回のワークを応用すればできるので、ワークでやることにして、先ほどのfetchと連動して、初期の段落表示をする仕掛けをみていきましょう。
読み込んだ情報を表示してみよう
先ほどcollectionを作成したところで、fetchした後に、コールバックを実行できるという話をしましたが、そこで、Viewと連動して画面とmodelが同期、さらに通信がはしり、サーバー側に登録されるようにしていきます。
fetchのコールバックとして下記のように書いていきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 | m.fetch({ reset: true , success: function (m){ _.each(m.models, function (v){ editor.$( '#paragraph' ).append( new app.view.paragraph({ model: v }).render().$el); }, this ); return ; } }); |
先ほど記述したsuccessのところをcreateを削って記述していきます。
successの第一引数で、collectionが渡ってきますので、m.models(コレクション一覧)をループさせて、app.view.paragraphを起動して、描画します。
描画したら、editorの子要素である#paragraphに追加するようにします。
これで、テンプレートを通してテキストエリアが描画されて、下記のような画面が表示されます。
この書き方に関してですが、app.view.editor側にメソッドを作成して、View配下のメソッドとして実行することで、処理の隠蔽をすることもできますが、ViewとCollectionの関係を疎にするために今回はこのような書き方をしています。
状況に応じて、どこに書くかということを意識してみるといいと思います。
テキストの内容を変更したら自動的にサーバーと同期してみよう
最後に、描画されたテキストボックスの内容を変更する度にサーバーと同期するようにしてみましょう。
サーバーとの同期は、modelのsaveメソッドで行います。
app.model.paragraph
1 2 3 4 5 | initialize: function (){ this .on( 'change' , function (){ this .save(); }, this ); }, |
initializeメソッドを追加して、changeイベントをリッスンするようにします。 モデルの内容が変更された時点で、saveメソッドが実行され、JSONを含んだリクエストがサーバー側に投げられれば成功です。
リクエスト先は path/editor/data.json/1
と言うような形になっているかと思います。最後の1は、そのレコードのidプロパティが利用されたことが分かります。この様にURLは自動的に決定されますので、サーバー側がこの通り設計されてれば、なにも考えなくても、自動的に同期する仕掛けが完成できます。
リクエストをカスタマイズする場合には、先ほど利用したModel.urlやModel.syncをカスタマイズしていきます。
いかがでしたでしょうか?
今回は、Backbone.Collection、Backbone.ModelとBackbone.Syncを中心にViewとの組み合わせで簡単なアプリが完成しました。
全2回では、HTMLが書かれている前提でのBackbone.jsの導入の仕方を説明してきました。第3回では、ウェブアプリを作るための基礎と、Backbone.jsが持っている通信周りの機能を一通り説明しました。
Backbone.jsはまだまだ若いフレームワークですので、これから仕様も変化していくと思いますが、基本的なクライアントMVCモデルの考え方や、今回のView、Collection、Modelの組み合わせによるデータとビューの疎結合を実現した形のでの実装は、どのフレームワークでも考え方としては共通しています。
JavaScriptでの画面作りに関しては、今後ますます重要になってきますし、さらに大規模なシステムで使われていきます。今回の取り上げたようなフレームワークを通じて、シンプルにアプリケーションを書いていくことを心がけてもらえればと思います。