読者です 読者をやめる 読者になる 読者になる

Light Table プラグインチュートリアル

LightTable ClojureScript Clojure

はじめに

Light Table Plugin Tutorialの翻訳になります。
LightTable のプラグインに関する日本語情報が不足しているため、翻訳してみました。
間違い等ありましたら、ご指摘いただけると助かります。

Original article: Light Table Plugin Tutorial by Jakub Arnold

ちなみにオリジナルの記事はLightTableのwikiにもリンクが貼ってあります。

元記事の著者の許可は取得済み↓

翻訳

ソースコードがオープンになってからLight Tableで遊んでいます。(Rubyのプラグインも作りました)

Light Table は 3つのコンセプトを持つ BOT アーキテクチャに基づいて作られています。振る舞い(Behaviors)、オブジェクト(Objects)、そしてタグ(Tags)の3つです。もしあなたがNode.jsや他のイベント駆動プログラミングを経験していれば、そのコンセプトを理解するのは簡単です。

クリックイベントを受け取るボタンを持ち、それがクリックされた際、ユーザにそれを通知することを想像してみてください。

jQuery を使えば、以下のようにシンプルに実装できます。

<input class="my-button" type="submit" value="Do work"/>
$(".my-button").click(function() {
  showProgress("I'm doing some heavy lifting");
});

しかしこのやり方にはちょっと問題があります、とりわけLightTableの視点から。まず、第一に要素に割り当てられた後、コールバックを参照する方法がありません。これは実行時、簡単に変更できないことを意味します。BOTでは、オブジェクト(the button)をトリガー(click)による実際の振る舞いから分離することができます。

ClojureScript による実装を見てみましょう。もしあなたがチュートリアルに沿って進むなら、まずはファイルを作ります。例えば /tmp/tutorial.cljs に。そしてCtrl-Spaceを押し、Add Connectionと入力、そしてLight Table UIを選択します。これは稼働している Light Table インスタンスの中で直接 ClojureScript を評価することを可能にします。その前に、以下の requires をファイルの先頭に追加しましょう。

(ns lt.tutorial
  (:require [lt.object :as object]
            [lt.objs.tabs :as tabs]
            [lt.objs.statusbar :as statusbar]
            [lt.objs.notifos :as notifos]
            [lt.util.js :as util])
  (:require-macros [lt.macros :refer [behavior defui]]))

今後は、カーソル位置にある現在のフォームを Cmd-Enter により評価することができます。

次にdefuiマクロを使ってボタンを定義しましょう。

(defui work-button [this]
  [:input {:type "submit" :value "Do work"}]
  :click #(object/raise this :clicked %))

このコード断片は明白ですね。<input type="submit" value="Do work"/>をクリックイベントのハンドラと共に生成します。#(object/raise this :click %) はただ単に(fn [e] (object/raise this :click e))の簡易表現です。ここでobject/raiseはターゲットとなるオブジェクトに対してクリックイベントを発生させます。名前から想像するかもしれませんが、例外を発生させるわけではありません。

次にワーカーオブジェクトを定義しましょう。

(object/object* ::worker
                :name "A hard worker"
                :behaviors [::work-on-click]
                :init (fn [this] (work-button this)))

これはクリックした際にハードな処理を行うワーカーです。ここで着目する点はinit関数で返される値です。init関数で返された値はオブジェクトがタブに置かれる際に使用されます。今回のケースでは、作成したオブジェクトを持ったボタンが返されています。

残りは振る舞いの設定です。Light Table の素晴らしいライブラリnotifosを利用し、進捗を表す動く四角形を表示します。

(behavior ::work-on-click
          :triggers #{:clicked}
          :reaction (fn [this]
                      (notifos/working "Doing some heavy lifting!")
                      (util/wait 10000 #(statusbar/loader-set 0))))

振る舞いの名前はオブジェクトの:behaviors に記載したのと同じ名前にする必要があります。また、振る舞いは:reaction関数を引数として渡されたオブジェクトと共に呼び出す際の引き金となる:triggersを持っています。今回は振る舞いは、ただ単に作業の進捗を示し、10秒後にそれを隠すだけとなります。

これで、オブジェクトを作成してそれをタブに追加する準備が整いました。

(let [worker (object/create ::worker)]
  (tabs/add! worker)
  (tabs/active! worker))

コンテンツを含んだ新しいタブが出現するでしょう。そこにあるボタンをクリックすると小さいプログレスバーがページ下部に表示され、10秒後に自動的に消えると思います。

ただ、勘の良い人なら既に気づいているかも知れませんがタブを閉じることができません。この原因は、タブを閉じるためのデフォルトとなる振る舞いが存在しないことに付随します。いくつかのタブは閉じる際、ユーザーに保存を促したいかもしれないし、また他のタブは全く違った実装にしたい場合があるのでこのようになっています。 都合が良いことに、閉じる際のアクションは Light Table をリスタートせずに追加することができます。

closeイベントに反応する新しい振る舞いを追加しましょう(docs.cljs から流用)

(behavior ::on-close-destroy
          :triggers #{:close}
          :reaction (fn [this]
                      (when-let [ts (:lt.objs.tabs/tabset @this)]
                        (when (= (count (:objs @ts)) 1)
                          (tabs/rem-tabset ts)))
                      (object/raise this :destroy)))

次にオブジェクトに対し、behaviors リストに追加することによって、この振る舞いの利用を知らせる必要があります。

(object/object* ::worker
                :name "A hard worker"
                :behaviors [::work-on-click ::on-close-destroy]
                :init (fn [this] (work-button this)))

何かを再起動する必要はありません。ただ振る舞いとオブジェクトの定義を評価するだけでタブを閉じることができるでしょう。:) Light Table はなんてダイナミックなんだ!

全体的な結果を見たい人達に対して、ここに gist のリンクと、以下に gif の画像を貼っておきます。:)

screencast

このチュートリアルは LightTable でできることを示したちょっとした紹介でした。ただ、Light Table のシステム全体が如何にダイナミックかってことについて、あなたに考える機会を与えられたのではと思います。


翻訳終わり