2012年11月24日土曜日

MSpecをReSharperと一緒に使うための設定 on Visual Studio 2010

Machine Specification(MSpec)はBDDスタイルのテストフレームワークで、BDDスタイルの開発をするなら至極便利。そのMSpecもReSharperと組み合わせてVisual Studio 2010上で簡易にテスト実行できるようにしておかないとありがたみが失せるので設定方法を紹介しておく。

MSpecのReSharperに対する設定が完了すると、下図のようにVSのテストファイルの左側にポケモンボールのようなアイコンがテスト毎に表示されるようになるし、メニューからReSharper -> Unit Tests -> Run Unit Testsでテストを実行できるようにもなる。

実はNuGetからもMSpecの取得は可能なのでそちらから取得してもかまわない。今回はGitHubから取得したmachine/machine.specificationsを使用する。適当にcloneするなりzipファイルをダウンロードして展開してもらいたい。フォルダが展開できたら直下にあるbuild.cmd(Releaseビルドしたい場合はbuild-release.cmd)を実行しよう。実行するとDOS窓が起動し、うにょうにょとビルドが始まり、テストが実行され、しばらくすると完了する。するとBuildフォルダができているので、そこからDebugかReleaseを自分の好みにあわせて選択してもらいたい。使用するだけならばReleaseでよいだろう。

で、そのDebugかReleaseフォルダ内にあるInstallReshaperRunner%%.batを実行する。%%の部分は自分のReShaperのVersionに置き換えてもらいたい。実行すると下記フォルダにMachine.Specifications.dll、Machine.Specifications.ReSharperRunner.6.0.dllとそれぞれpdbファイルがコピーされる。ここではv6.0のbatファイルを叩いたとする。

%appdata%\JetBrains\ReSharper\v6.0\vs10.0\Plugins

NuGetから取得した場合は前述の2つのDLLを上記のパスに自分でコピーすればよい。コピーする際にフォルダが無い場合は自分で作ろう。

コピーできたらVSを再起動しよう。再起動後は上図のポケモンボールがテストファイルの左側に表示されているはずだ。

一点注意が必要なのが、JetBrains配下にコピーしたMachine.Spedifications.DLLとVSで参照しているMachine.Spedifications.DLLのバージョンなどが違うとどうやらReSharperのほうでMSpecのプラグインを認識してくれないようなので同じファイルを使用するようにしよう。新しい環境を構築する際にMSpecをNuGetで取得したり、JetBrainsのほうへは古いファイルをコピーしたりなどを行ってしまいMSpecのテストが中々認識されなくて苦労したことがあるので注意してもらいたい。

2012年9月12日水曜日

Cross-Origin Resource Sharing(CORS)で外部ドメインからリソースを取得する

今回はCross-Origin Resource Sharing(CORS)で外部ドメインからリソースを取得するお話。実際にどんな用途があるかというとGoogle Analyticsなんかで埋め込むスクリプトは埋め込む先のページのドメイン(仮にwww.matsuo-software.comとする)から違うドメイン(GAではwww.google-analytics.com)へ情報を飛ばしたりする(注1)のでそういう場合に必要となる。
注1:GAのコードを調べたわけではないので実際には不明です。

ではなぜJavascriptから外部ドメインへのアクセスにCORSが必要かというと、ブラウザにはセキュリティのためにSame Origin Policyというのが適用されている。そのPolicyは外部ドメインからのリソースに訳の分からんことをされて悪意のあるデータをPOSTされたりするのを未然に防ぐためにある。で、その防止策の一環で外部ドメインへ向けてのJavascriptを使った単純なAjaxリクエストを行ってもブラウザレベルでアクセスを拒否されてしまうので、その制限をかいくぐるのにCORSの設定が必要となるわけだ。


サンプル
例によってサンプルはNode.js。https://github.com/yooontheearth/cors-sampleからコードは取得してもらいたい。アプリの設定まわりはBackbone.jsでクライントスクリプトをガリガリ組むためのお手軽チュートリアルを参照してもらいたい。

NODE_PATH=/usr/local/lib/node_modules coffee app
でNode.jsを起動してからcorssample.htmlをブラウザで開き「Hi Mr. Yoo!」と表示されれば正常に動作している。

CORSでGETをするのは特に何の問題もなくできるはずなので、今回は少しひっかかるところのあるPOSTを解説している。


解説
今回のサンプルはいたってシンプル。見るべきところは13行目のretrieveNameと25行目のapp.post '/mrnobody'の部分のみ。
express = require 'express'
qs = require 'querystring'
app = express.createServer()

app.configure ->
 app.use express.logger('tiny')
 app.use express.bodyParser()
 app.use express.methodOverride()
 app.use app.router
 app.use express.static(__dirname + '/')
 app.use express.errorHandler({ dumpExceptions: true, showStack: true })

retrieveName = (req, callback) ->
    name = req.body.name
    if name?
        callback name
    else
        data = ""
        req.on "data", (chunk)-> data += chunk
        req.on "end", ->
            parsedData = qs.parse data
            name = parsedData.name
            callback name

app.post '/mrnobody', (req, res) ->
  retrieveName req, (name) ->
     res.header "Access-Control-Allow-Origin", '*'
     res.header "Access-Control-Max-Age", '86400' # 1 day
     res.header 'Access-Control-Allow-Methods', 'POST,OPTIONS'
     res.header "Access-Control-Allow-Headers", "X-Requested-With"
     res.send "Hi Mr. #{name}!"

app.listen 3000
console.log "Express server listening on port #{app.address().port} in #{app.settings.env} mode"
retrieveNameはそのままPOSTされたデータ(名前)を取得している。req.body.nameがNullだったら、という処理の理由は後述する。/mrnobodyのリクエストハンドラでCORSに必要なヘッダー情報を設定している。ブラウザはここで設定しているレスポンスのヘッダー情報をもとにCORSが許可されているかの判断を行う。Access-Control-Allow-Originに*を指定するとすべてのドメインからのアクセスを許可することになる。特定のドメインのみ許可したい場合は「http://www.matsuo-software.com」などを指定すればよい。またAccess-Control-Allow-Methods、Access-Control-Allow-Headersでそれぞれ許可するHTTPメソッド、ヘッダーを指定している。許可された以外のアクセスが行われた場合はアクセス拒否されるという寸法だ。それらの許可内容をAccess-Control-Max-Ageで指定した秒数だけブラウザの方でキャッシュされる。

これだけでサーバサイドのCORSの設定は完了だ。後はクライアントから通常のAjaxリクエストと同じように処理を行えば正常動作するはずだ。リクエスト時にエラーが出る場合はjQuery.support.cors = true;やcrossDomain:trueの設定をサンプルのcorssample.htmlを参考に同様に処理してもらいたい。

ただこのままで動くのはFirefoxやChromeだけのモダンなブラウザのみ。


INTERNET EXPLORER
Yes, IE! そう、IE。Web開発者の鬼門、IE。CORSでも堂々のお邪魔虫っぷりを十二分に発揮してくれる。本当にいつもいつも鬱陶しいことこの上ない。

IE10未満はXmlHttpRequestでのCORSを許可しておらず、CORSのためにはそれようのオブジェクトを使用する必要がある。XDomainRequestがそれにあたる。それなのでIEの場合はXDomainRequestを使用するようにしなければならない。

というわけで下記ライブラリでXDomainRequestを使ったAjaxリクエストをさくっと行う。
Malvolio / ie.xhr

呼び出しはこんな形になる。
$.ajax('http://localhost:3000/mrnobody', {
 data: { name: 'Yoo' },
 type: 'post',
 xhr: window.IEXMLHttpRequest || jQuery.ajaxSettings.xhr,
 crossDomain: true,
 success: function (data, textStatus, jqXHR) {
  $result.text(data);
 },
 error: function (jqXHR, textStatus, errorThrown) {
  $result.text('Failed to load data.' + errorThrown);
 }
});

で、これで終わりかと思いきやさすがはIE。さらに鬼門が用意してあってデータをポストしてるのにContet-typeをtext/plainとしてリクエストする。そのため通常ならばapplication/x-www-urlencodedでリクエストされた場合はポスト内容の解析を行ってくれるサーバサイドフレームワークがtext/plainなため内容解析を行わず、前述のreq.body.nameがNullになるというような事態を惹起するのである。そこでretrieveNameで行っているようなリクエストから生のポストデータを取得してkey=valueのペアになっている値を独自に解析する必要があるというわけである。

ここまでやって、やっとIEでもCORSができるようになった。IEでのCORSの詳しい制限事項はXDomainRequest - Restrictions, Limitations and Workaroundsを参照してもらいたい。

2012年8月31日金曜日

ASP.NET MVCのキャッシュについてあれこれ

一年半前ぐらいにASP.NET MVC 3のキャッシュ周りについて調べまくったので今更ながらまとめておく。一年以上前の話なのであやふやな部分も少々あるけれどそこはご愛嬌ということで話半分に読んで欲しい。

で、キャッシュについて。キャッシュを有効活用できれば一番手っ取り早くサーバのスループットをあげることができるわけで、もちろんASP.NET MVC 3にもキャッシュ機能はある。


OutputCacheAttribute
OutputCacheAttributeがそれにあたる。この属性をControllerかActionに付与することによってそれらの出力結果をキャッシュしてくれる。使用方法は下記のような感じ。
[OutputCache( Duration = 5, VaryByParam = "fish;angler", VaryByHeader = "X-Requested-With" )]
public class ChinuController : Controller{
}
Durationで何秒間キャッシュするのかを指定。VaryByParamやVaryByHeaderの指定でQueryStringのパラメータとかヘッダーごとにキャッシュを分けることが可能となっている。その他にもいくつか設定できる項目があるみたいだけれど使ったことがないのでよく分からない。

で、この属性を使う上で一つ注意が必要なのが、さきほども強調表示してあるけれどこの属性は出力結果をキャッシュするようになっている。つまりこの属性を使用するとControllerのアクションは当たり前だけどViewのレンダリングもスキップされることになる。

で、それの何が問題なの?ってのは次の場合。


ドーナツキャッシュ
ログインユーザに「ようこそYooさん」のようなメッセージを表示する会員制のサイトがあったとする。そういう場合にOutputCacheだとディモールト(非常に)都合がよろしくない。というのもOutputCacheは出力結果をキャッシュするので、Yooさんの内容がキャッシュされた状態でSasukeさん(うちの猫)がそのページにアクセスすると「ようこそYooさん」と表示されてしまうのである。じゃぁそういう場合はどうするの?ってのでドーナツキャッシュという手法がある。下図のようにある一箇所だけを除外してキャッシュするのでドーナツキャッシュと呼ばれる。


その除外された箇所の描画用コールバックを用意しておくとそれが後から呼ばれるという寸法だ。で、詳細は下記を参照して欲しい。
Donut Caching in ASP.NET MVC

うん、冒頭のUPDATEを読んでびっくりだと思うけどこの機能はASP.NET MVC 3には組み込まれてない。キャッシュについて調べてたときも上記のサイトの説明を飛ばしてソースコードから入ったので、実装したら動かなくてちゃんと読み直したらびっくりしたね。F***って思ったよ。

で、困ったなーということで自前のキャッシュフィルターを作ることにした。


ResultCache
要するに出力結果をキャッシュされちゃうと柔軟性に欠けるというわけで、それならControllerのアクションの結果(ActionResult)だけをキャッシュして、そのキャッシュしといたActionResultをViewに渡して描画するようにすれば大体の問題は解決できるので下記のようなアクションフィルターを作ろうと思ったら既に作っている人(ASP.NET MVC Result Cache)がいたのでそれを拝借した。
public class ResultCacheAttribute : ActionFilterAttribute
{
 public ResultCacheAttribute()
 {
  Duration = 1200;    // 20 mins
 }
 public string CacheKey{ get; private set; }
 public CacheDependency Dependency { get; set; }
 private CacheItemPriority _priority = CacheItemPriority.Default;
 public CacheItemPriority Priority
 {
  get { return _priority; }
  set { _priority = value; }
 }
 public int Duration{ get; set; }

 public override void OnActionExecuting(ActionExecutingContext filterContext)
 {
  var url = filterContext.HttpContext.Request.Url.PathAndQuery;
  this.CacheKey = "ResultCache-" + url;
  if (filterContext.HttpContext.Cache[this.CacheKey] != null)
  {
   filterContext.Result = (ActionResult)filterContext.HttpContext.Cache[this.CacheKey];
  }
  base.OnActionExecuting(filterContext);
 }

 public override void OnActionExecuted(ActionExecutedContext filterContext)
 {
  filterContext.Controller.ViewData["CachedStamp"] = DateTime.Now;
  filterContext.HttpContext.Cache.Add(this.CacheKey, filterContext.Result, Dependency, DateTime.Now.AddSeconds(Duration), System.Web.Caching.Cache.NoSlidingExpiration, Priority, null);
  base.OnActionExecuted(filterContext);
 }
}
リクエストされたURLをキャッシュのキーにしてActionResultを保持しておき、再度同じURLがリクエストされた場合はキャッシュからActionResultを取得してfilterContextのResultに渡すというだけの至極シンプルなフィルター。OnActionExecutingを見て不思議に思うかもしれないけれど、実はfilterContextのResultにActionResultが設定されるとControllerのアクションは呼び出されないのだ。


というわけで、サーバリソース節約のためにもキャッシュはガンガン使っていくべきなので機会があれば快適なWebを実現するためにもドシドシ有効にしていただきたい。ただ一点注意が必要なのはなんでもかんでもキャッシュしていると予期せぬ出力結果になることがままあるのでキャッシュが有効になっている箇所の周りを開発する場合はその箇所だけをテストするのではなく、複数人からのリクエストを想定したテストなどを行わないと本番環境にリリースしてから痛い目にあうことがあるのでくれぐれも注意して欲しい。かく言う私もチヌかかり釣りMEGAのスマホ版をリリースしたさいに、ControllerのアクションでPC版とスマホ版のViewを動的に切り替えていたのでActionResultがキャッシュされてしまい予期せぬ動作になりとても焦った。ただその問題は前述のアクションフィルターにスマホ用のキャッシュキーを追加することによってサクッと事なきを得たので良かったけれども。

2012年8月10日金曜日

Backbone.jsでクライントスクリプトをガリガリ組むためのお手軽チュートリアル

JavascriptでAjaxyなヌルヌル動くUIを作成すると
  • HTML要素上のデータ
  • ブラウザメモリ上のデータ
  • サーバサイドのデータ
というように大体データは3層に分かれることになる。これはブラウザアプリケーションに限った話だけではなくWPFのようなBinding機能のない言語で開発するWindowsのクライアントアプリケーションでも同じような問題に行き当たるので普遍的な話だ。ただ、Javascriptの場合はHTMLの要素から値を取り出すのにjQueryなどのセレクターを多用して目的の要素を見つけ、値を取り出し、データに設定し、Ajaxでデータを保存する、という流れになるのだけれど、こういったUI要素の操作とデータの操作をそこかしこで混ぜ合わせながら行わなければならず、さらにその箇所のコードを切り離すのも何かと難しい・面倒なので規模が大きくなるほどにコードのメンテナンスがディモールト大変になっていってしまう。

というわけで、そんな状況を打破するためのクライアントフレームワークがここ2~3年で大変充実してきていて、今回解説するBackbone.js、もちっとお手軽らしいEmber.js、もっともっとシンプルなKnockout.jsなどなど、どれから手にとって良いやら迷うこと必至なほど盛況だ。で、とりあえず選考の一助になればと簡単なサンプルアプリケーションを作成してみた。

今回のアプリケーションの外観は下図。ソースコードはhttps://github.com/yooontheearth/backbone-tutorialから取得可能になっている。
ご覧の通りよくある一般的な尼子十勇士系のアプリケーションだ。左側のリストを選択するとその詳細が右側に表示され、追加、編集、保存が可能となっている。今回のサンプルではBackbon.jsにフォーカスしたかったのでサーバサイドでの永続化処理(DBへの保存など)は行っていない。


アプリケーションの環境
今回のアプリケーションの環境は下記の通り。
  • サーバ Ubuntu@12.04
  • Webサーバ Node.js@0.6.9
  • サーバフレームワーク Express.js@2.5.0
  • Viewエンジン Jade@0.26.0
  • CSSエンジン Stylus@0.23.0
  • クライアントフレームワーク Backbone.js@0.9.2
  • CoffeeScript
WebサーバはNode.jsを使用しているけれど、RestfulなRequest、Responseが行えるならば何でもかまわないので、Python Django、ASP.NET MVC、Ruby on Railsなどなど自分の好みにあわせて変えてもらいたい。

Node.jsのセットアップ方法などはNode.jsをUbuntu 11.10にセットアップする ついでにExpress.jsもセットアップするなどを参考にしてもらいたい。

ソースコードをどこか適当な場所に$ git cloneなりダウンロードして展開したら下記コマンドで必要なモジュールをインストールする。
$ npm install -d
package.jsonに記述してあるもので必要なモジュールはすべてそろっていると思うけれど、サンプルを作った環境はグローバルにたくさんモジュールをインストールしすぎていて何が必要なのかよく分からなかったので少し適当だ。実行時にエラーが出たらその都度必要なモジュールを追加してほしい。今回はコーディングをCoffeeScriptで行っているので下記コマンドでCoffeeScriptをグローバルにインストールする。
$ npm install coffee-script -g
これで準備が整ったので下記コマンドでアプリケーションを実行してみよう。
$ NODE_PATH=/usr/local/lib/node_modules coffee app
"Express server listening on port 3000 in development mode"
とかなんとか表示されていたら正常に起動できたので下記URLへアクセスしてみよう。
http://localhost:3000/index
先ほどの尼子十勇士的なリストが表示されていたら正常に動作している。


Backbone.jsの解説
Backbone.jsは大きく3つのパートに分けられる。
  • Model,Collectionのデータ系
  • Viewの表示系
  • Routerの管理系
Modelは単一のデータを表しここでは武将単体のデータのまとまりになる。Collectionは武将をリストとして扱うためのデータのまとまりになる。Viewは左側のリストや上部のボタン、右側の詳細表示のUIを表す。Routerはそれらのデータ・UIを操作・管理するための機能を提供し、サーバとクライアントの仲立ちを行う。また、データ系はサーバへのリクエストをラッピングしてくれているので、データの取得、保存時に$.ajaxのような記述の必要がなく、ある一定の規則に基づいて決定されたリクエスト先へ操作にあわせた適切なHTTP動詞でアクセスしてくれる。

今回のアプリケーションで設定されているRouteは下図の通り。
Route動詞説明
/indexget初期表示を行う
/listgetリストデータを取得する。Collection.fetch()でリクエストされる。今回は実装していないけれど本来的にはページングなどを行う
/bushopost武将情報の新規追加を行う。Model.save()時にModelが新規(Model.isNew())に作成されたものの場合はpostリクエストされる
/busho/:idput武将情報の更新を行う。Model.save()時にModelが新規でないものの場合はputリクエストされる
/busho/:iddelete武将情報の削除を行う。Model.destroy()でリクエストされる
Routeの詳細は./app.coffeeに記述されているので参照してもらいたい。

ここから個別にBackbone.jsを使ったコーディングを見ていこう。ソースコードは./public/javascripts/index.coffeeを参照してもらいたい。

データ系
# 武将モデル
Busho = Backbone.Model.extend
 urlRoot: 'busho' # Bushoモデルがサーバへのリクエストを行うときの基本となる部分の指定
 defaults:  # templateで使用するプロパティがundefinedだとエラーになるので初期値を設定しておく
  id:null
  name:null
  description:null
  imageUrl:null
 initialize: ->
  @on 'error', @failOnValidation
  @on 'destroy', @close
 validate: (attrs) ->
  if not attrs.name? or attrs.name.length == 0
   return '名称は必須です'   # 検証に失敗した場合はメッセージを返す
 failOnValidation: (model, error) -> alert error  # 検証に失敗した場合のイベントハンドリング
 close: ->
  @off()
ここで指定しているurlRootの部分が前述のRouteの部分(/busho, /busho/:id)と対応するようになる。
# 武将モデルコレクション
BushoList = Backbone.Collection.extend
 model: Busho # createを呼び出す場合はmodelの指定は必須
 url: 'list'  # fetchするときのリクエスト先
ここで指定しているurlの部分が前述のRouteの部分(/list)と対応するようになる。

ビュー系
# リストビュー
ListView = Backbone.View.extend
 tagName: 'table'
 initialize: ->
  # modelはBushoListなのでBushoListが変更されたときに備えてイベント登録
  @model.bind 'reset', @render, this
  @model.bind 'add', (busho)=>
   $(@el).append new ListItemView(model:busho).render().el  # tableに新しい行を追加する
 render: ->
  $(@el).empty()
  _.each @model.models, (busho) ->  # BushoListの内容をtableに行として追加する
   $(@el).append new ListItemView(model:busho).render().el
  , this
  return this
このビューはリスト=CollectionをModelとして受け取り、Collectionのデータ構造をUI的に表すのと、Collectionの変化(リストの更新、追加など)をUIへと反映させる責任を持つ。またtagNameはビューで作成される要素を表しデフォルトではDIVになり、elはその作成された要素を表す。
# リストビューアイテム
ListItemView = Backbone.View.extend
 tagName: 'tr'
 template: _.template $('#tpl-list-item').html()
 initialize: ->
  # modelはBushoなのでBusho情報が変更されたときに備えてイベント登録
  @model.bind 'change', @render, this
  @model.bind 'destroy', @close, this
 render: ->
  $(@el).html @template @model.toJSON()
  return this
 events:
  'click td':'select'  # 行が選択された処理をフックするためにUI要素のイベント登録を行う
 select: ->
  $('tr.selected').removeClass('selected')
  $(@el).addClass('selected')
  app.navigate "busho/#{@model.id}", true  # 詳細情報を表示するためにルーターに通知する
 close: ->
  $(@el).off().remove()
templateプロパティへビューに表示するHTML要素を設定する。今回はUnderscore.jsのテンプレートを使用しているけれど、もちろんjQueryTemplateでもなんでもかまわない。ただここにHTML要素をだらだらと記述するのは関心の分離を進めるMVC的には正しくないのでテンプレートを使用するようにしよう。eventsプロパティに要素内でフックしたいイベントの記述をする。構文的には'イベント セレクタ':'イベントハンドラ'となる。
# 詳細情報画面
DetailsView = Backbone.View.extend
 template: _.template $('#tpl-details').html()
 render: ->
  $(@el).html @template @model.toJSON()
  $(@el).find('#delete').hide() if @options.hideDelete
  return this
 events:
  'change input,textarea':'changeData' # UI要素に入力された情報をモデルに反映するためのイベントハンドリング
  'click #save':'save'
  'click #delete':'delete'
 changeData:(event)->
  # changeイベントでデータを必ずしも反映させる必要はなく、アプリの要件によっては保存ボタン押下時などにまとめて行っても良い
  changeDataSet = {}
  changeDataSet[event.target.name] = event.target.value
  @model.set changeDataSet, silent:true  # silent:trueで検証を無効化している
 save: ->
  if @model.isNew() # 新規登録時はリストに追加する
   app.list.create @model,
    wait:true
    success: (model, response) =>
     @model.set 'id', app.list.length # IDの採番は適当。本来ならばサーバからのレスポンスにIDを渡しておいて設定するとか
     app.navigate '', true
    error:(model, error)->  # サーバサイドでのエラーはresponseTextを参照、それ以外はクライアントでの検証エラー
     alert if error.responseText then error.responseText else error
  else
   @model.save {},
    success: (model, response) ->
     # とくに処理なし
    error: (model, error) ->
     alert if error.responseText then error.responseText else error
  return false
 delete: ->
  return false unless confirm '削除しますか?'
  @model.destroy
   success: ->
    app.navigate '', true
  return false
 close: ->
  $(@el).off().empty()
save時に新規の場合はリストに要素を追加したいのでapp.list.createとしているけれど、Backbone.jsの内部的にはModel.save()が呼び出されている。
# ヘッダービュー
HeaderView = Backbone.View.extend
 template: _.template $('#tpl-header').html()
 render: ->
  $(@el).html @template()
  return this
 events:
  'click #refresh':'refresh'
  'click #add':'add'
 refresh: ->
  app.navigate 'refresh', true
  return false
 add: ->
  app.navigate 'busho/add', true
  return false
Routerへの通知を行うだけのビュー。

管理系
AppRouter = Backbone.Router.extend
 routes:
  'busho/add':'add'
  'busho/:id':'details'
  'refresh':'list'
  '':'closeDetails'
 initialize: ->
  @list = new BushoList()
  @list.reset _bushoList # 初期表示するデータはページロード時に用意してあるのでそちらから取得
  @listView = new ListView model:@list
  $('#list').html @listView.render().el
  $('#header').html new HeaderView().render().el # ヘッダービューをDOMツリーに反映
 add:->
  @detailsView.close() if @detailsView
  @detailsView = new DetailsView
   model:new Busho()
   hideDelete:true  # 追加なので削除ボタンは不要
  $('#details').html @detailsView.render().el  # 詳細ビューをDOMツリーに反映
 details:(id)->
  @detailsView.close() if @detailsView
  busho = @list.get id # リストから詳細を表示するアイテムを取得
  @detailsView = new DetailsView model:busho
  $('#details').html @detailsView.render().el  # 詳細ビューをDOMツリーに反映
 list:->
  @list.fetch() # リストの更新
      # ※fetch({data: {page: 3}}) のような形でQueryStringを渡せるのでページングなどもfetchで行える
 closeDetails:->
  @detailsView.close() if @detailsView # 詳細ビューが開いていたら閉じる
routesプロパティにクライアント側でページの遷移を制御するためのRouteを登録しておく。Routerはroutesプロパティに基づいてリクエストされた処理を行い、クライアントとサーバの仲立ちを行う。

呼び出し
app = null
$(document).ready ->
 app = new AppRouter()
 Backbone.history.start()
使用方法はいたって簡単。AppRouterをインスタンス化するだけ。あとはRouterがよきように取り計らってくれる。


まとめ
ここまで駆け足で説明してきたけれどいかがだろうか?今回のサンプルではデータの永続化処理を行っていないので微妙な部分も多々あるけれど、Backbone.jsを使うとクライアントとサーバ、ビューとデータ、UI操作とデータ処理の分離が上手に行えるのが分かったかと思う。ただBackbone.jsを使ったとしてもアプリケーションの規模が大きくなってくれば必然的にコード量が増えるのでどうしても管理は煩雑になっていき適切なサブモジュール化などを行う必要が出てくるけれど、そのサブモジュール化自体をBackbone.jsのおかげで容易に行える実感は持てたのではと思う。UI要素とデータの結びつきとデータの更新に伴うUI要素への反映をさっくりとできるBackbone.jsはどのような場面でも非常に有用なので是非活用してもらいたい。

2012年6月20日水曜日

Open XMLでExcelを生成する

@ryuichi111std氏からのお仕事紹介でサーバサイドでのExcel出力機能を承り、Excel周りの作業の調査をしたら時代の移り変わりを実感したので解説。

一昔前のExcelのオートメーションと言えば、ExcelのオブジェクトをCOM InteropやらCOM Callable WrapperやらでごにょごにょしてぐりぐりしてどこやらでCellオブジェクトの解放忘れやらシートとブックの解放の順番間違いやらをしてしまいメモリリークという悲劇を生む精根尽き果てる作業というのが筋だったけれど、今やそれも昔。今やOpenXMLという規格があるのでそれに準じた形のXMLを吐き出すことで、あらまOffice 2007/2010対応のファイルができちゃった、という寸法になっている。

で、OpenXMLの初期の初期のころはそんなXMLのフォーマットをごりごりと手動で実装するというキチガイ沙汰の作業が横行していたようだけれど、そんな時代が長く続くはずもなくOpenXMLのフォーマット形式で出力してくれるSDKが登場した。それが下記。

Open XML SDK 2.0 for Microsoft Office
OpenXMLSDKv2.msiの方をインストールしてね。ToolのほうはExcelファイルを渡すとそのファイルを作成するためのコードを生成してくれるというツールなので興味がある人はどうぞ。

で、OpenXMLSDK、実はこいつもかなり使いづらい。かなりどころか、ディモールト(非常に)使いづらい。使いづらいどころかまったくこなれておらず、かなり分かりづらい仕様となっているOpenXMLを単純にそのままラップしたような構成になっているので、とっても廻りくどいしバグが入り込みやすい。

一体OpenXMLSDKを使っての開発がどういうものになるのか、下記のコードを参照してもらうのが一番手っ取り早いと思う。
Using C# and Open XML SDK 2.0 for Microsoft Office to Create an Excel 2007 Document
Creating basic Excel workbook with Open XML

で、めんどくせーなー、と思いつつ実装していたら、di molto(非常に)使いやすいライブラリを発見したのでご紹介。それが下記。

ClosedXML - The easy way to OpenXML

ClosedXMLはOpenXMLSDKをラップして使いやすくしたライブラリで非常に直観的。下記のコードはワークブックを作成してシートを追加してA1セルに値を設定して名前を付けて保存している。下記と同等のコードをOpenXMLSDKだけで行おうとすると50行~100行ほどの実装が必要になる。

var workbook = new XLWorkbook();
var worksheet = workbook.Worksheets.Add("Sample Sheet");
worksheet.Cell("A1").Value = "Hello World!";
workbook.SaveAs("HelloWorld.xlsx");

ほかにもほとんどのパターンに対応するサンプルコードが豊富にあるので詳細はDOCUMENTATIONを参照してもらいたい。

このプロジェクトが気に入ったらブログで紹介してね、とClosedXMLのサイトにあったのでご紹介。このエントリーをご覧になられた方もどうぞよろしく。

2012年5月10日木曜日

正規表現を使って簡単置換 with Visual Studio

jquery.tmplを使って帳票っぽい画面を作っていたら、直接値を表示するのではなく関数を呼び出して値を変更してから表示したくなったんだけど、項目がたくさんあって一個一個手で変更していくのは骨だなぁ、ってことでVisual Studioと正規表現で簡単に置換できる方法を調べたのでその紹介。

下記のようなjquery.tmplを使用しているコードがあるとする。

${prop1}
${prop2}
${prop3}

このprop1~3を下記のようにしたいとする。

${callMethod(prop1)}
${callMethod(prop2)}
${callMethod(prop3)}

数が少なければ手でしゃしゃっと修正してしまうのもありだけれども、大量の項目をこのように置換したい場合はVisual Studioの置換機能と正規表現を使用すれば簡単に変更できる。

Ctrl + h で検索と置換ウィンドウを開いて、検索する文字列に\$\{{.*}\}、置換後の文字列に${callMethod(\1)}と入力する。条件チェックボックスにチェックして正規表現を選択するのを忘れないように。


これで置換ボタンを押下すると予期したように置換されているのが確認できると思う。
簡単に解説すると、検索する文字列で指定した{.*}の部分でprop1~3のマッチングを行っている。そのマッチングした部分を置換後の文字列で指定した\1で使用している。マッチングの部分が複数ある場合は\2, \3のようにすることで対応できる。

ちなみにJavascriptだとマッチングの部分が{}ではなく()だし、\1の部分が$1となっていて、ここらへんは言語によって微妙に異なっているので他の用途で使用する場合は言語仕様をよく確認してほしい。

ASP.NETでDateTimeをJSONに変換したら不思議な文字列になった場合の対処法

ASP.NETでJavaScriptSerializerを使用してDateTimeをJSONに変換すると下記のような嬉しくない形で出力される。

/Date(1329058800000)/

JSONに変換する前にToString("yyyy/MM/dd")でもして文字列にしてしまえば問題ないけれど、毎回そういうことが可能なわけでもないのでJavaScriptでの対処方法を紹介する。
var date = "/Date(1329058800000)/";
alert(convertToDateTime(date));

function convertToDateTime(value) {
    var date = new Date(parseInt(value.replace("/Date(", "").replace(")/", ""), 10));
    return date.getFullYear() + '/' + fillZero(date.getMonth() + 1, 2) + '/' + fillZero(date.getDate(), 2);
}
function fillZero(number, width) {
    width -= number.toString().length;
    if (width > 0) {
        return new Array(width + (/\./.test(number) ? 2 : 1)).join('0') + number;
    }
    return number;
}
上記のコードを実行すると2012/02/13と表示されるのが確認できる。

2012年2月4日土曜日

Node.jsをUbuntu 11.10にセットアップする ついでにExpress.jsもセットアップする その2

Express.jsのインストールもうまくいき、$ expressでテンプレートもできあがったので満足していたら実はまだ問題があったようで下記コマンドを実行したらエラーがもりもりと出てきてしまった。

$ node app.js

エラー内容はこちら。
node.js:201
        throw e; // process.nextTick error, or 'error' event on first tick
              ^
TypeError: Cannot read property 'prototype' of undefined
    at Object. (/home/yoo/matsuo-software/sandbox/node-firstapp/node_modules/express/lib/express/server.js:87:44)
    at Module._compile (module.js:444:26)
    at Object..js (module.js:462:10)
    at Module.load (module.js:351:31)
    at Function._load (module.js:310:12)
    at Module.require (module.js:357:17)
    at require (module.js:373:17)
    at Object. (/home/yoo/matsuo-software/sandbox/node-firstapp/node_modules/express/lib/express/index.js:28:31)
    at Module._compile (module.js:444:26)
    at Object..js (module.js:462:10)

することなすことすべてがエラーで返ってくるので常に心を整えていないとやっていられない。エラー場所として示されているserver.jsの87行目を見てみると

Server.prototype.__proto__ = connect.Server.prototype;

となっている。で、GoogleしてみるとExpress.jsに対応したConnect.jsのバージョンがどうやらインストールされていないらしい。インストールしているExpress.jsのバージョンは1.0.7で、そのバージョンのExpress.jsが対応しているConnect.jsは0.5.10らしい。しかしnpmからインストールするとConnect.jsは1.0.1がインストールされてしまうためにエラーとなっているようだ。なので下記コマンドで古いConnect.jsをインストールする。

$ npm install connect@0.5.10

これで冒頭のNode.jsのコマンドを実行するとサーバがリッスン状態に突入するので、http://localhost:3000にアクセスしてHello Worldの出力が正常にされているのを確認できた。ちなみに自分の環境にどのモジュールのどのバージョンがインストールされているかを調べるコマンドは下記。

$ npm ls

表示されるモジュールの一覧で、Connect.jsのバージョンが0.5.10になっているのが確認できると思う。

Node.jsをUbuntu 11.10にセットアップする ついでにExpress.jsもセットアップする

Node.jsを使ってみたかったのでUbuntu 11.10にセットアップしたときのメモ書き。Node.jsのセットアップ自体はさっくりと終わり何事もなかったのだけれども、後述する諸々の事情からExpress.jsのインストールに失敗してしまい、完了するまでやたらと時間がかかったので後続の方のためにも記しておく。

下記の一連のコマンドを実行してもらいたい。

1, Gitから最新のNode.jsコードを取得する
$ sudo apt-get update
$ sudo apt-get install git-core curl build-essential openssl libssl-dev
$ git clone https://github.com/joyent/node.git && cd node
Node.jsでSSLを使う場合はopensslが必要なのでついでにインストールしておく。上記のコマンドでnodeフォルダに最新のNode.jsコードがクローンされた。

2, コンパイルしてインストールする
$ ./configure
$ make
$ sudo make install
$ node -v
正常にインストールがされたら一番最後のコマンドのnode -vでインストールされたNode.jsのバージョンが表示されていると思う。ちなみに今日(2012/2/2)現在で0.7.2-preとなっている。

3, NPM(Node Package Manager)をインストールする。NPMはNode.jsまわりで必要になるモジュールを簡単にインストールできるとても便利なツールだ
$ curl http://npmjs.org/install.sh | sudo sh
$ npm -v
こちらも今日現在で1.1.0-3となっている。($ npm searchでnpmでインストールできるモジュールの一覧が参照できるので興味がある人は見てみると良いだろう)

ここまででNode.jsのセットアップが整ったのでExpress.jsのセットアップにとりかかる。
4, Express.jsをインストールする
$ npm install -g express
-gはグローバルにインストールするオプションだ。グローバル指定だと/usr/local/bin/あたりにインストールされる。ローカルにだけインストールしたいのなら-gははずそう。ローカル指定にすると現在のディレクトリ直下のnode_modulesにインストールされる。

5, 忘れずにExpress.jsに必要なモジュールもインストールする
$ npm install -d

これでExpress.jsのインストールが完了したはずなのでExpress.jsを実行してみよう。
6, Express.jsを実行してテンプレートを作成する
$ express

すると高確率で意味不明なエラーに遭遇すると思う。しなかったら今回のポストの表題の件は達成したのでアリーベデルチしていただいて結構だ。

エラーは「process.nextTick error, or 'error' event on first tick」という感じのまったく要領を得ない内容で取り付く島がない。で、色々とGoogle & 試行錯誤の結果、どうやらテンプレートエンジンであるJade.jsというのがインストールされていないらしい。

7, なのでJade.jsをインストールする
$ npm install -g jade

するとまたエラーになる。エラー内容によるとJade.jsと依存関係にあるCommander.jsがNode.jsの0.7未満までにしか対応しておらず、gitから調子こいて最新のNode.jsをインストールしたばっかりにインストールがうまくいっていないようだ(これが冒頭で述べた諸々の事情)。この時点であまりのうまくいかなさに少し常軌を逸しかけたけれども、幸いかな、優秀な開発者の方々のおかげで簡単にNode.jsのバージョンを変更する手段がすでに用意されていた。ありがとう優秀なみなさん。

8, Node.jsのバージョンを変更するためにnをインストールして、Node.jsのバージョンを0.6.9に変更する
$ npm install -g n
$ sudo n 0.6.9
$ node -v
これでNode.jsが0.6.9になったので、再度7を実行しJade.jsをインストールする。Jade.jsのインストールが成功したら再度6を実行してみよう。ディレクトリの指定がないけどここにつくっていいの?的なことを尋ねられるので yes とタイプすると、直下にviewsとかtestとかのディレクトリをもりもりと作ってくれるはずだ。

以上でNode.jsとExpress.jsのセットアップの解説は終わりだ。偉そうに解説しておいてなんだけれども、Node.jsは今から開発しようとしている人間なのでNode.jsまわりの知識はほぼゼロに近い、かつLinuxも触り始めたばかりのため疎く間違った解説があるかもしれない。また今回は色々なコマンドを試しながらセットアップしたので正しくない解説 & 不要なコマンドもあるかもしれないが、少しでもセットアップに行き詰った方の一助になれば幸いだ。

今回のセットアップがやたらと大変だったのは、Express.jsの依存関係としてあがってくるconnectとqsのインストールに失敗しているものだとばかり考えていたので、そちらと格闘するのに大幅な時間がとられてしまったせいだと思う。ただJade.jsに目をつけてからは、インストールできない→nのインストール→バージョン変更→Jade.jsのインストール成功→Express.jsの実行成功、ととんとん拍子に進んだので最後のほうは楽だった。これで週末はNode.jsとmongoDBの評価がさっくりとできそうでなによりだ。ふぃ~

2012年1月5日木曜日

Html5でサイトを作る理由

Html5で新しい機能が大量に導入されたためHtml5を使う利点を考えるときには切り口によって色んな見方ができると思う。ブラウザベースのゲームを作っていた人はオーディオがネイティブでサポートされたのとCanvasによってぐりぐりとビジュアルをアニメーションさせることが可能になったので今までのようにプラグインを導入する必要がなくなったのが嬉しいだろうし、Web socketによってサーバとの通信がよりライトウェイトになったおかげで色々とアイディアを思いつく人もいるだろうし、Data StorageとかOffline時の制御やDrag & Dropも導入されたのでブラウザベースだからできないと言って限定されていたものがより普通のアプリのように動作するようになるだろうし、Html5と一口で言ってもこれだけ多種多様な広がる未来がある。

では通常のサイトを作る上でのHtml5を使う利点はなんだろうか。それはセマンティックウェブを意識した新しい要素達にある。以前もIntroducing HTML5を読んでの感想で軽く触れたけれども、Html5ではコンピュータから見て意味のある記述を可能とする新しい要素達がたくさん導入されている。それらを正しく使用することによってより検索エンジンフレンドリーになるし、それによって結果的に効果的なSEOとなるのは明らかだろう。というわけでなんらかのサイトを運営されている方は早めにHtml5で記述し始めたほうがお得というわけだ。

で、タグの紹介とか、タグの使用方法とかを調べるのにざっと見た中で下記のサイトでの解説が大変分かりやすかったのでお勧めだ。IE8以前のブラウザでHtml5のタグを正常に認識させる方法も解説してくれているので参考にしてもらいたい。
HTML5の簡易メモ