プログラマーだけん、がんばる

神の国、島根のプログラマー。サーバ、Rubyまわりの技術(Ruby on Rails, Rhodes etc..)やiOS, Androidなどの開発を行っていくうえで、役だったことなどを共有できればいいなと思います。

新・3大JavaScriptフレームワークを使用して、Webアプリケーションを作る ~ knockout.js ~

Knockout.jsとは?

Knockout.jsとは3大フレームワークなんて呼ばれている?JavaScriptフレームワークです。
残りの2つは、Backbone.js, Angular.jsがあります。
その中で、今回取り上げるKnockout.jsは、MVMM(Model, View, ViewModel)のフレームワークで、双方向データバインディング、アイテムテンプレートなどの機能があります。
これを使用すると、RailsなどのWebアプリケーション開発では必須になってくるDOM要素の変更などのJavaScriptのコードを書くことがほぼ無くなります。
まぁ、用途にもよるのでこれ以上大きなことは言えません。

なので、データの更新などで発生してくるUIの更新などをKnockout.jsに任せるといった切り分けが可能になります。

導入方法

Railsでの導入を例にします。まずアプリケーションを作成します。今回はRuby 2.0.0, Rails 4.0.0で行っています。

$ rails new knockout --skip-bundle

次に、knockout.jsを導入します。
Railsに導入する場合には、Gemが用意されているので、Gemfileに追記してbundleを実行して下さい。

・Gemfile
gem 'knockoutjs-rails'

その後、application.jsでrequireします。

・application.js
//= require knockout

これで準備完了!では実際にknockout.jsを使用した、実装を行います。

Viewの実装

実際にViewに実装していきます。
application.jsを読み込んでいるViewを何でもいいので作成し、下記の用に実装していきます。
ここでは、top_pages#indexに実装します。

・top_pages/index.html.erb

<h1>Knockout.jsをRuby on Railsで動かすサンプル</h1>

<%= text_field_tag :input_text, '', placeholder: '入力された文字が下に表示されます', data: {bind: "value: message,  valueUpdate: 'afterkeydown'"} %>
<%= content_tag(:p, :result, data: {bind: "text: message"}) %>

<script>
  $(function(){
      // ViewModelを定義
      function ViewModel(){
          // はじめは空文字列を指定
          this.message = ko.observable('');
      }
      // バインディングスタート
      ko.applyBindings( new ViewModel() );
  });
</script>

これで完了。
テキストフィールに文字を入力してみましょう。
下のpタグにリアルタイムに表示されます。
f:id:famtom:20131012144024p:plain

簡単な例ですが、面白い例でもあります。
まず、Jqueryなどでの直接的なDOMの操作はおこなっていません。
これが双方向バインディングの機能です。
messageは常に双方向で監視されているため、HTML上のテキストフィールドの値が変更されると、ViewModelのmessageプロパティが変更され、その結果、下のpタグの内容が変わるという仕組みです。

では一行ずつ見ていきます。

<%= text_field_tag :input_text, '', placeholder: '入力された文字が下に表示されます', data: {bind: "value: message,  valueUpdate: 'afterkeydown'"} %>
<%# <input data-bind="value: message,  valueUpdate: 'afterkeydown'" id="input_text" name="input_text" placeholder="入力された文字が下に表示されます" type="text" value=""> %>

inputタグにあるdata-bind属性でバインディングの登録を行います。
valueで指定されているのが、今回監視するmessageです。
'afterkeydown'というのはVIewModelに変更の通知が届くタイミングです。
この場合だと、キーが押された後にViewModelに変更の通知が届きます。




ko.observable('');メソッドを使用することでバインディングの対象とみなし、messageを常に監視します。

this.message = ko.observable('');

最後に、ko.applyBindingsで対象のViewModeを渡せば、View-ViewModelの間でバインディングが開始されます。

ko.applyBindings( new ViewModel() );


以上が、簡単なknockout.jsを使用したサンプルです。
ここまで、Railsの機能を使用していないので、次は実際にRails機能も使用したタスク作成機能を作成していきます。

konockout.jsとRailsとでタスク作成機能を実装する

タスクの一覧画面に作成フォームを設置して、サブミットされた時にRailsにPOSTするのと同時にUIの更新を行うという仕様にします。
本来は、POSTした結果のstatusを見てUIの更新を行うべきなので、仕事などではそのまま使用しない方がいいです。
まず、おおまかな枠組みを作成するのでtaskという名前でscaffoldを作成します。

rails g scaffold task name:string

まぁこれでタスク作成出来るんですが、knockout.jsを使用していないので無視して下さい。

index.html.erbでformを設置するために、form_forメソッドを使用したいので、controllerのindexアクションで新しいTaskオブジェクトを作成して、@taskインスタンスへ格納します。

・tasks#index

@task = Task.new

次に、index.html.erbの内容を以下の用に書き換えます。

・taks/index.html.erb

<h1>Listing tasks</h1>

<%= form_for(@task, html: {data: {bind: 'submit: addTask'}}, remote: true) do |f| %>
  <%= f.label :name %>
  <%= f.text_field :name, data: {bind: 'value: name'} %>
  <%= f.submit %>
<% end %>

<ul data-bind='foreach: taskList'>
  <li data-bind='text: name'></li>
</ul>

<script>
  $(function(){
    function ViewModel() {
      this.taskList = ko.observableArray(<%= raw @tasks.to_json %>);
      this.name = ko.observable('');

      this.addTask = function () {
        var task = {
          name: this.name()
        };
        this.taskList.push(task);
        this.name('');
      };
    }

    ko.applyBindings(new ViewModel());
  })
</script>

これで仕様を満たすので、実装は終了です。
ブラウザから「http://localhost:3000/tasks」にアクセスすると、最初はまだタスクを登録していないので作成フォームのみが表示されているはずです。
f:id:famtom:20131012165807p:plain
このフォームにタスク名を入力して、サブミットを押下すると、......!!!!
f:id:famtom:20131012165822p:plain
新しいタスクが作成されたはずです。
と、同時RailsのログにPOSTのログも吐かれているはずです。
f:id:famtom:20131012161207p:plain
ポイントはUIの更新はknockout.jsに切り分けているので、リダイレクトやDOM操作を行わなくても、
UIが更新されていることです。かつ、ajaxRailsにアクセスが飛んでいるので、DBへの登録も行われています。
では解説していきます。ここでのポイントは、まずこの部分です。

<%= form_for(@task, html: {data: {bind: 'submit: addTask'}}, remote: true) do |f| %>

form要素にたいして、サブミットが行われたらViewModel内のaddTaskというイベントが発生するようにバインディングしています。
ここで、remote: trueを指定しなければPOSTは飛びません。恐らく内部でデフォルトの動作を止めているはずです。
ですので、デフォルトの動作ではないajaxのアクセスにしてやります。そうすると、ajaxでアクセスが飛びます。
今回はサンプルなのでいいのですが、実際に使用する場合は、remote: trueは指定せずaddTaskに設定されている関数の中でPOSTをして、その結果を見てUIを更新するというやり方がいいかもしれません。



次に、この部分です。

<ul data-bind='foreach: taskList'>

ここではforeachバインディングというバインディングを使用しています。
このバインディング方法では、バインディングした配列と同じ長さ分だけ、子の要素を繰り返し生成するというバインディングです。

最後にViewModelの定義です。

一番最初の例では使用しませんでしたが、複数データをバインディングする場合には、
observableArray()を使用します。引数は配列になります。

    function ViewModel() {
      //taskListに配列データをバインディングする。初期値は、DBから取得した全タスク
      this.taskList = ko.observableArray(<%= raw @tasks.to_json %>);
      this.name = ko.observable('');

      //フォームがサブミットされた時に発火する関数
      this.addTask = function () {
        var task = {
          name: this.name()
        };
        //バインディングされている配列データに追加。
        // この際に通知が発火しforeachバインディングで子要素が追加されます。
        this.taskList.push(task);
        //テキストフィールドを空にする
        this.name('');
      };
    }

以上が、knockout.jsとRuby on Railsをしようしたタスク作成機能です。
ここで紹介したバインディング方法以外にも強力なバインディング方法が他にも沢山ありますので、
詳しくは公式ドキュメントを参照して下さい。
knockout.js => http://knockoutjs.com

今回作成したサンプルプログラムをGitHubにあげていますので、ご参考までに。
GitHub => https://github.com/tomomura/knockout-sample

最後に

ここまでしれーっと記事を書いてきましたが、初記事です。
僕は、怠惰で、同じことの繰り返しが嫌いで、そのくせかっこいいことが好きな、へたれプログラマーです。
せっかくなので、僕と同じようなプログラマーの方にも情報共有をしたくて記事を書きました。
他にもこんな便利なものあるよー、作ったよーとい方がおられましたら教えて下さい。

ちなみに島根は今、「神在月(かみありづき)」で、神様が沢山います。