Vue.jsをjs_of_ocamlから使う方法

Vue.jsとはJavascriptでMVVMするための軽量なフレームワークです。
ですっていうか3日前に知ってこれはいいやと思い、js_of_ocamlから使う方法を考えていました。

ちなみに自分のJavascriptのレベルはほとんど0です。「Objectに動的にメソッドを追加できる」ってことを知ったのもこの3日間という感じ。
js_of_ocamlのレベルも似たり寄ったりかな。

動機

OCamlでコード書いててGUI付けたいと思いjs_of_ocamlでHTMLのGUI作ろうとしたけど、ViewとModelをうまく分離する方法がわからなかった。
そこで、F#+WPFみたくMVVMな感じで

  • View = HTML
  • ViewModel = js_of_ocaml
  • Model = OCaml

を実現したいなーと思い調べたところVue.jsを知り、とりあえず作ってみました。

やったことはVue.jsの以下のサンプルコードをjs_of_ocamlで写経しました。

app.js(Vue.jsのサンプルコード)

var demo = new Vue({
    el: '#demo',
    data: {
        branch: 'master'
    },
    created: function () {
        this.$watch('branch', function () {
            this.fetchData()
        })
    },
    filters: {
        truncate: function (v) {
            var newline = v.indexOf('\n')
            return newline > 0 ? v.slice(0, newline) : v
        },
        formatDate: function (v) {
            return v.replace(/T|Z/g, ' ')
        }
    },
    methods: {
        fetchData: function () {
            var xhr = new XMLHttpRequest(),
                self = this
            xhr.open('GET', 'https://api.github.com/repos/yyx990803/vue/commits?per_page=3&sha=' + self.branch)
            xhr.onload = function () {
                self.commits = JSON.parse(xhr.responseText)
            }
            xhr.send()
        }
    }
})

app_ml.ml(上記のjs_of_ocaml版)

open Js

(* Vueオブジェクトのインスタンスパラメーターを定義 *)
let param = 
  let open Unsafe in (* Unsafeをよく使うので開いてしまう *)
  let created () =
(* Javascriptのthisの有効範囲がよくわかってないのでその都度取り込む *)
    let this = Unsafe.variable "this" in
    meth_call this "$watch" [| inject (string "branch");
                               inject (fun () ->
                                        (* このthisの書き方でいいのかな *)
                                        meth_call this "fetchData" [||])
                            |] |> ignore
  in
(* truncateとformatDateは実際にはModelの仕事なのでOCamlっぽく書いてみる *)
  let truncate v =
    try
      let str = to_string v in
      let newLine = String.index str '\n' in
      String.sub str 0 newLine |> string
    with _ -> v
  in
  let formatDate v =
    let str = to_string v in
    let rexp = Regexp.regexp "T|Z" in
    Regexp.global_replace rexp str " " |> string
  in
  let fetchData () =
    let this = Unsafe.variable "this" in
    let self = this in
    let branch_js = self##branch in
    Firebug.console##log(branch_js);
    let branch_ml = to_string (branch_js) in
    let url =
      "https://api.github.com/repos/yyx990803/vue/commits?per_page=3&sha="
      ^ branch_ml
    in
    let (>>=) = Lwt.(>>=) in (* 記号を導入*)
    XmlHttpRequest.get url >>= fun r ->
      let msg = r.XmlHttpRequest.content in
(*     let json = Json.unsafe_input (string msg) in *)
(*     Firebug.console##log(json); *)
(* こう書きたいんだけjs_of_ocamlのJsonオブジェクト内の文字列が
   mlstringになってて期待した通りに動かないのでJavascriptのJSONを輸入する *)
      let json = Unsafe.variable "JSON" in
      self##commits <- Unsafe.meth_call json "parse" [| inject (string msg) |];
      Lwt.return msg
  in
  obj [|
    "el", inject (string "#demo");
    "data", inject (obj [| "branch", inject (string "master") |]);
    "created", inject created;
    "filters", inject (obj [| "truncate", inject truncate;
                              "formatDate", inject formatDate |]);
    "methods", inject (obj [| "fetchData", inject fetchData |]);
  |]

(* JS Objectをコンストラクタの引数にとるVueオブジェクトを定義 *)
let vue : 'a constr = Unsafe.variable "Vue"

(* Vueオブジェクトのインスタンスを定義 *)
let demo = jsnew vue(param)

感想

とりあえず、UnsafeいっぱいであんまりOCamlのお得感はないですね。
そして、なんとなくVue.jsはよくできているように思いました。こんな感じでJavascriptでもMVVMできるっていうのが新鮮。

あと、js_of_ocamlとVue.jsの日本語のブログ記事はほぼすべて目を通しました。超参考になりました。ありがとうございました。
たとえばjs_of_ocaml

Vue.jsは

などが役に立ちました。

実行デモとソースは以下です。
http://toku.bitbucket.org/experiment/vuejs/index.html