ラベル coffeescript の投稿を表示しています。 すべての投稿を表示
ラベル coffeescript の投稿を表示しています。 すべての投稿を表示

2012年2月13日月曜日

Backbone.jsのTodoアプリを読む。



クライアントサイドMVCフレームワークをもっと勉強したい。


今僕は#Favtileというサービスを作っています。


Twitterのお気に入り機能のためのツールです。


そこではクライアントサイド・サーバーサイド共にCoffeeScriptで書いており、Spine.jsを使っています。


Spine.jsを使いはじめた理由? CoffeeScriptが好きだからです。Spine.jsはCoffeeScriptで書かれていますから。


でも他にもJavaScriptのMVCフレームワークはいっぱいあります。


有名だと思うのでBackbone.jsをやります。


有名だと情報が多いので無名のフレームワークよりも勉強しやすいという考えです。


Backbone.jsのサンプルのTodoアプリを読みながらドキュメントを引いて調べていきます。


Backbone Demo: Todos


Todo Model


Todoモデルはtext、order、doneの3つの属性を持つようです。


属性のデフォルト値がある場合はdefaultsに指定する。


toggleはTodoが完了した/してないを切り替えるメソッドですね。


textの属性持つとコメントには書いてるけどModelの中には「text」が見当たらない。そんなものか。



// Our basic **Todo** model has `text`, `order`, and `done` attributes.
window.Todo = Backbone.Model.extend({

// Default attributes for a todo item.
defaults: function() {
return {
done: false,
order: Todos.nextOrder()
};
},

// Toggle the `done` state of this todo item.
toggle: function() {
this.save({done: !this.get("done")});
}


Backbone.jsのドキュメントを読みながらもっと詳しく読む。


defaults

Modelをインスタンス化するときに属性の値が指定されてなかった時はデフォルト値としてdefaultsの内容が使われる。


ここでdefaultsに関数を指定するとその関数の返り値をデフォルト値として使う、と。


デフォルト値が毎回変わるような場合に関数を指定するといい感じなのかなぁ。


save

Modelのsaveメソッドは、内部でBackbone.syncを使っていて、Backbone.syncではRESTでサーバーにアクセスする。


LocalStorage adapterを使ってるとここが置き換わるかんじか。


saveメソッドに変更したい属性達を{}でまとめて渡すか、save("key", "value")のような感じで呼び出して使う、と。


validateメソッドを実装してvalidateとおらないと保存出来ないようにすることも出来るみたい。


isNewっていうので新規作成されたものか判定してるのかな? 新しく作成されるときはcreateがよばれ、更新の時はupdateが呼ばれるよう。


saveには{success:成功時の処理, error:失敗時の処理}のような感じで成功時・失敗時のコールバック関数を渡せるらしい。


Todo Collection


modelプロパティにCollectionが持ってるモデルのクラスを指定する、Collection.addとかcreateするときにmodelを使うのかなぁ。


Storeっていうのはローカルストレージに読み書きするためのクラスかな?


collectionの中からdoneなやつだけ取るのがdoneメソッドで、remainingはコレクションからdoneなやつを除いたもの、か。読みやすい。


nextOrderでTodoの順番を作ってる。


comparetorプロパティを定義するとcollectionを並べるときにその値が使われるようです。


今のだと追加した順に並ぶようになってますね。



// Todo Collection
// ---------------

// The collection of todos is backed by *localStorage* instead of a remote
// server.
window.TodoList = Backbone.Collection.extend({

// Reference to this collection's model.
model: Todo,

// Save all of the todo items under the `"todos"` namespace.
localStorage: new Store("todos"),

// Filter down the list of all todo items that are finished.
done: function() {
return this.filter(function(todo){ return todo.get('done'); });
},

// Filter down the list to only todo items that are still not finished.
remaining: function() {
return this.without.apply(this, this.done());
},

// We keep the Todos in sequential order, despite being saved by unordered
// GUID in the database. This generates the next order number for new items.
nextOrder: function() {
if (!this.length) return 1;
return this.last().get('order') + 1;
},

// Todos are sorted by their original insertion order.
comparator: function(todo) {
return todo.get('order');
}

});

// Create our global collection of **Todos**.
window.Todos = new TodoList;


TodoView


SpineでいうとControllerにあたる部分。


Backbone.Viewを継承してる。


はtagName、className、id、attributesを指定することで表示用のエレメントが作られ、elプロパティに保持されるようで。


_.template便利だなぁ。クライアントサイドのテンプレートっていっぱいあるのね…


eventsっていうプロパティに{"eventName selector":"methodName"}の形式で書くっぽい。


内部的にはjQueryのdelegateが呼ばれてイベントが設定される。(設定するときには名前空間もくっつけてるみたい)


clickしたときはtoggleDoneがよばれて、その中でmodelのtoggleが呼ばれてdoneの値が更新されて、changeイベントが発行されてレンダリングし直されるのかな。


dbclickではeditが呼ばれて、その中で編集中の見た目になるようクラスを追加して、入力欄にフォーカス当ててるみたい。


ではフォーカスはずれた時はどうなるかというと


renderの中でsetTextをよんでいて、その中で、フォーカスが外れたとき(blur)はcloseを呼ぶように設定してる。


closeの中ではモデルの値を保存したあと、編集中の見た目を取り除いてる感じ。


enterが押された時にもcloseは呼ばれるみたいだ。なるほど。


initializeが初期化する時によばれるものかな?




  • モデルが変更されたらレンダリングしなおす

  • モデルが捨てられたら要素を取り除く


みたいな設定をしてるみたい。



window.TodoView = Backbone.View.extend({

//... is a list tag.
tagName: "li",

// Cache the template function for a single item.
template: _.template($('#item-template').html()),

// The DOM events specific to an item.
events: {
"click .check" : "toggleDone",
"dblclick div.todo-text" : "edit",
"click span.todo-destroy" : "clear",
"keypress .todo-input" : "updateOnEnter"
},

// The TodoView listens for changes to its model, re-rendering.
initialize: function() {
this.model.bind('change', this.render, this);
this.model.bind('destroy', this.remove, this);
},

// Re-render the contents of the todo item.
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
this.setText();
return this;
},

// To avoid XSS (not that it would be harmful in this particular app),
// we use `jQuery.text` to set the contents of the todo item.
setText: function() {
var text = this.model.get('text');
this.$('.todo-text').text(text);
this.input = this.$('.todo-input');
this.input.bind('blur', _.bind(this.close, this)).val(text);
},

// Toggle the `"done"` state of the model.
toggleDone: function() {
this.model.toggle();
},

// Switch this view into `"editing"` mode, displaying the input field.
edit: function() {
$(this.el).addClass("editing");
this.input.focus();
},

// Close the `"editing"` mode, saving changes to the todo.
close: function() {
this.model.save({text: this.input.val()});
$(this.el).removeClass("editing");
},

// If you hit `enter`, we're through editing the item.
updateOnEnter: function(e) {
if (e.keyCode == 13) this.close();
},

// Remove this view from the DOM.
remove: function() {
$(this.el).remove();
},

// Remove the item, destroy the model.
clear: function() {
this.model.destroy();
}

});


AppView


なるほど。既にある要素をelにバインドする事も出来る、と。


addとかresetとかそこらへんのイベントはFAQの中にCatalog of Eventsで載ってる。



// The Application
// ---------------

// Our overall **AppView** is the top-level piece of UI.
window.AppView = Backbone.View.extend({

// Instead of generating a new element, bind to the existing skeleton of
// the App already present in the HTML.
el: $("#todoapp"),

// Our template for the line of statistics at the bottom of the app.
statsTemplate: _.template($('#stats-template').html()),

// Delegated events for creating new items, and clearing completed ones.
events: {
"keypress #new-todo": "createOnEnter",
"keyup #new-todo": "showTooltip",
"click .todo-clear a": "clearCompleted"
},

// At initialization we bind to the relevant events on the `Todos`
// collection, when items are added or changed. Kick things off by
// loading any preexisting todos that might be saved in *localStorage*.
initialize: function() {
this.input = this.$("#new-todo");

Todos.bind('add', this.addOne, this);
Todos.bind('reset', this.addAll, this);
Todos.bind('all', this.render, this);

Todos.fetch();
},

// Re-rendering the App just means refreshing the statistics -- the rest
// of the app doesn't change.
render: function() {
this.$('#todo-stats').html(this.statsTemplate({
total: Todos.length,
done: Todos.done().length,
remaining: Todos.remaining().length
}));
},

// Add a single todo item to the list by creating a view for it, and
// appending its element to the `<ul>`.
addOne: function(todo) {
var view = new TodoView({model: todo});
$("#todo-list").append(view.render().el);
},

// Add all items in the **Todos** collection at once.
addAll: function() {
Todos.each(this.addOne);
},

// If you hit return in the main input field, and there is text to save,
// create new **Todo** model persisting it to *localStorage*.
createOnEnter: function(e) {
var text = this.input.val();
if (!text || e.keyCode != 13) return;
Todos.create({text: text});
this.input.val('');
},

// Clear all done todo items, destroying their models.
clearCompleted: function() {
_.each(Todos.done(), function(todo){ todo.destroy(); });
return false;
},

// Lazily show the tooltip that tells you to press `enter` to save
// a new todo item, after one second.
showTooltip: function(e) {
var tooltip = this.$(".ui-tooltip-top");
var val = this.input.val();
tooltip.fadeOut();
if (this.tooltipTimeout) clearTimeout(this.tooltipTimeout);
if (val == '' || val == this.input.attr('placeholder')) return;
var show = function(){ tooltip.show().fadeIn(); };
this.tooltipTimeout = _.delay(show, 1000);
}

});

// Finally, we kick things off by creating the **App**.
window.App = new AppView;

});


まとめ


だらだらと書いた割に内容のない文章になってしまった。


実際に自分で何か作ってみる方が理解が進むかも。(あるいはBackbone.jsのソースを読むとか)





2012年1月28日土曜日

Twitterのお気に入りをもっと使いやすくするツール・Favtileをリリースしました。



Favtile


f:id:h6n:20120129043756p:image:w360


Twitterのお気に入りをタイル状に美しく表示、そこから人のお気に入りを見たり、お気に入りを追加出来る、Twitterのお気に入り機能に最適化したツールです。


特徴としては




  • タイル状に表示することで情報量を多く、かつ美しく表示します。

  • ある人のお気に入りから他の人のお気に入りへワンクリックで飛べます。


実際の表示例: masasonさんのお気に入り


http://favtile.com/masason


またハッシュタグの検索結果を表示することも可能です。


http://favtile.com/#gigazine


ソースコードはGitHubで公開しています。


https://github.com/hanachin/favtile


サーバーサイド・クライアントサイドともにCoffeeScriptで書きました。


クライアントサイドはSpine.jsとjQuery、サーバーサイドはzappaというフレームワークを使用しています。


内部の実装について下北沢オープンソースカフェさんで開かれた勉強会で語った動画があります。


http://www.ustream.tv/recorded/20015536





2011年12月16日金曜日

node.js + socket.io + Arduinoでイイネ!



eXtreme Hagoで使われていた、リアルタイムに「いいね」と出すあのシステムをパクりました。


node.js + CoffeeScript + express + node-arduino + MongoDBで出来てます。


デモ動画



動機


ITfrogsとLexuesAcademyの懇親会でくまさんがいいねと同じの200分で作った!と言ってたので僕も作りたくなった。


これが@さんがつくったやつ。


higumachan/iine ? GitHub


本家のイイネに加え、グラフ機能つき。


開始時間と終了時間にぽちっとボタン押すとグラフが表示され、どこで盛り上がったか一目瞭然。


こんなすごいの「200分で作った」なんて言われたら僕も作りたくなっちゃうでしょ!!


僕が何か1つ機能を付け加えるなら何がいいか?


「そうだ、リアルにボタン押す機能でも作るか」


出来ました。


f:id:h6n:20111216035307j:image:w360


ソースコード


hanachin/iine_duino ? GitHub


※何分で出来たかは秘密です


解説


僕はCoffeeScriptにハマっています。ちゃんと使ってみたい。


CoffeeScriptが書ければJavaScriptも書ける。


JavaScriptが書ければnode.jsが使える。


なので、CoffeeScript + node.jsで作成することに決めました。


ウェブサイトを表示する

ウェブ側はexpressを使って表示することにした。


理由:




  • 直接書くのは多分面倒

  • Sinatra-likeで学習コストが低そう

  • 持ってる本(パーフェクトJavaScript)に例がのってる


本のサンプルを動かしながら使い方を覚えさくっと開発環境を整える。


リアルタイムにイイネを出すために

Nodeが使えればsocket.ioが使える!!


ということでsocket.io使えば楽勝でした。


ただし実際にコード書くよりも使い方を調べる方が時間かかった。


socket.ioを使う理由:




  • リアルタイムに通信したいから

  • 自分でリアルタイムに通信出来るように一から書くより簡単、確実、便利、安心

  • 使ってみたかった


グラフを表示するために

くまさんはjqplotというライブラリを使っていたので僕もそれを使うことに。


グラフを表示するためには記録が必要、なのでMongoDBを使うことにしました。


理由:




  • RDBを設定するのが面倒だった

  • 既にmongooseというライブラリがある

  • 本に例が載ってる


イイネが押された時刻だけを記録するシンプルなデータベースです。


まず、開始時刻と終了時刻を含んだURLにアクセスすると、その間のイイネを集計してJSONで返すようなAPIを作った。


あとはグラフを表示する側からそれを叩いて、jqplotに渡すだけ。jqplot便利ですね。


リアルにボタンを押すために

ボタンをArduino経由でPCに接続しようと考えた。


押せればなんでもいいので100円ショップでプッシュライトボタンを買います。


Arduinoとnode.jsをつなぐにはnode-arduinoを使います。


普通にnpm install arduinoで入れると、digitalRead(入力)が使えなくてハマってました。


Forkされてるこちらのリポジトリを使いましょう。


hapticdata/node-arduino ? GitHub


後は、ボタンを分解してスイッチのところにつなぐだけ。簡単ですね。


Tips等



  • Guardを使うとCoffeeScriptを勝手にコンパイルしてくれる。coffee -wcでやるよりも便利。

  • node-devを使うとJavaScriptファイルが更新されると自動でサーバーを再起動してくれて楽。

  • プッシュライトはLEDが3つついてるんだけど、どうやって光らせるか誰か教えてください。(Arduinoじゃ電流足りないよね?)


反省点


3つあげると




  • コード書くのに時間をかけすぎた

  • ブログの解説がざっくりすぎる

  • ブログ書くのも旬なうちじゃないと書けない(なので早く製品を作ろう)


まとめ




  • CoffeeScriptでイイネシステムが書けた。

  • expressを使うと簡単にウェブアプリが書ける。

  • socket.io使えばリアルタイムに通信が出来る。


    • どうやって通信するか考えなくて済むのでリアルタイム性の高いものがすぐ作れる。



  • jqplotは見栄えのいいグラフをさっと作れる。

  • node-arduinoを使えばnode.jsとArduinoを連携出来る。


    • ソフトウェアだけでは出来ないことが出来るようになるかも。




オリジナル版と、くまさんの作った奴がなかったら作れなかった。ありがとうございます。


作ってる途中、失敗がいくつもあったけど最終的にボタン押して出来るようになると超楽しかった。


もっと電子工作、はんだづけやってみたい!


もっと聞きたい事、詳しく書いてほしい事があれば@までお願いします。





2011年12月7日水曜日

EmacsからnodeやブラウザでCoffeeScriptを動かす



前に書いたJavaScriptの開発環境を整えるついでにCoffeeScriptの環境が整います。


EmacsでJavaScriptの環境を整えたい - はなちん C-x C-c


swank-jsの導入


ivan4thさんのswank-jsを使うとEmacsからNodeやブラウザにつないでJavaScriptを実行できます。


JavaScriptならChromeの開発者ツールやFirebugでも出来ますが、やはりEmacs上で出来ると幸せです。(そのまま保存出来るし)


僕はswank-jsにCoffeeScript用の機能をいくつか付け加えました。


GitHubに置いてあるので、そこから導入します。


hanachin/swank-js ? GitHub


swank-js導入手順


ほとんど深町さんのこの記事と一緒です。


Emacsからnode.jsやChromeとSocket通信 - 八発白中


違うところだけ、説明します。


0.swank-jsのダウンロード

CoffeeScript用に改造してある僕のリポジトリから落とします。



% git clone git@github.com:hanachin/swank-js.git


1.nodeをインストールします

自分はここを参考にしました。


no title


3.SLIMEをインストール

僕の環境だと最新のSLIMEだとswank-jsがうまく動かなかったので、LispBox同梱の物を使いました。


Lispbox


中に入っているslimeのフォルダを、Emacsのロードパスの通っている場所へ置きます。


自分の場合だと~/.emacs.d/elisp/あたりに入れてます。


5.emacsに設定を追記

もとの記事のjs2-mode-hookに加えて、coffee-mode-hookの設定を.emacsに追記します。



(add-hook 'coffee-mode-hook
(lambda ()
(slime-js-minor-mode 1)))


EmacsからCoffeeScriptを使う


swank-jsのディレクトリで以下のコマンドを実行し、swank-jsのサーバーを立てます。



% node swank


EmacsでM-x slime-connect、接続。(ここでslimeとswank-jsのバージョンが違う等のメッセージが出るが気にしない)


ここまではJavaScriptと同じです。


slime-repl JSのバッファーで,select-remoteと入力して、CoffeeScript/directを選びます。


あとはJavaScriptと同じ感覚でCoffeeScriptを入力して実行出来ます。とっても簡単ですね。


EmacsからブラウザのREPLにアクセスしCoffeeScriptを使う


次のブックマークレットを、CoffeeScript使ってattachしたいページで実行します。


Attach Swank Coffee


後はslime-reple JSの画面で,select-remoteでCoffeeScript Browserと書いてるやつを選べばおk


f:id:h6n:20111208014158p:image


このように、Emacsで書いたCoffeeScriptをブラウザで実行できます。


また、coffee-modeで範囲を選択して、C-c C-rで実行することもできます。


まとめ




  • 最初はslime-js.elの方をいじって、elisp側でCoffeeScript->JavaScriptの変換かけようとしていたが面倒だった

  • swank-handlerのRemoteを継承するクラスを作ってevaluateメソッドをオーバーライドし、そこで実行前に変換をかければよい

  • そのクラスをswank.jsの中で使うようにする

  • 別のaltJS、JavaScriptにコンパイルされる言語(Cocoなど)をEmacsから叩きたいときも同じ感じでいけるかも。


という感じ。


CoffeeScriptの問題として、コンパイル後のJavaScriptコードを実行中にエラーがおき、エラー行が表示されても、元々のCoffeeScriptの何行目に対応するのか分からないといったところがあります。


これについてはこちらのスレッドで意見がかわされてるようです。


Issue #558: line number mapping for debug ? jashkenas/coffee-script ? GitHub


まぁ僕はとりあえずCoffeeScriptをEmacsから実行できる環境が整ったので満足です。


Emacs使いでCoffeeScript使いで興味を持ってくださった方は是非試されてみてください。


フィードバックは@まで。





EmacsでCoffeeScriptの環境を整えたい



flymakeでエラーを表示したい


いちいちコンパイルしてエラーが出てるか確認するのは面倒。

ここを参考にflymakeの設定をする。

coffeescriptでflymakeする - antipop

EmacsからCoffeeScriptを評価して、結果が見たい


regionを選択してえばるとChrome Developer Toolsに表示するところまで出来ました。

追記

こちらの方でより良い方法を解説しています。

EmacsからnodeやブラウザでCoffeeScriptを動かす - はなちん C-x C-c

1.まずはここを参考にJavaScriptの開発環境を整えます。

EmacsでJavaScriptの環境を整えたい - はなちん C-x C-c

2.CoffeeScriptをえばるためのslime-coffee.elを導入する

ロードパスの通った所に置いてください。

eval region, coffee-script — Gist

後は、JavaScriptと同じようにnode swankでswankサーバーを立ち上げ、ブラウザにブックマークレット使ってattachして、範囲を選んでC-c C-rで、えばれます。

デモ動画




next


もっといい方法を思いついたのでこれから実装




2011年12月1日木曜日

CoffeeScriptでHQ9F+のインタプリタ書いた。



Esoteric Language - ネットワーク?なにそれおいしいの?


を見て書こうとおもったので、書いた!



インスタンス変数のデフォルト引数でハマりそうなところ



out = (x) -> console.log x
class Foo
constructor: (@out = out) ->


このようなCoffeeScriptは



var Foo, out;

out = function(x) {
return console.log(x);
};

Foo = (function() {

function Foo(out) {
this.out = out != null ? out : out;
}

return Foo;

})();


このようにコンパイルされる。


デフォルト引数の値に、インスタンス変数と同じ名前の変数を渡すと、インスタンス変数の方で隠されてしまうので注意。