Weblocksでウィジェットのスロットを変更すると即座に反映される件

以前weblocksのwith-flowマクロを使ってウィジェットを置き換えるという記事を書いたが、別のウィジェットに置き換えるのではなく、同じウィジェットのスロットを変更してそれを反映させたい場合はどうするのだろうかという疑問が湧いたので実験してみた。

まずは2つのウィジェットを定義する。一つは変更対象のウィジェットrerender-testで、もう一つは変更を実行するリンクを表示するウィジェットrerender-updateだ。rerender-updateはrerender-testのインスタンスを参照するためのスロットを持っている。

(defwidget rerender-test ()
  ((body :accessor body-of :initarg :body)))

(defwidget rerender-update ()
  ((rerender-test-widget :accessor rerender-test-widget-of :initarg :rerender-test-widget)))

次に、これらのウィジェットがどのように表示されるかを定義する。rerender-testはbodyスロットに入っている文字列を単に表示するだけだ。rerender-updateはrerender-testスロットに入っている変更対象のウィジェットのbodyスロットを変更するリンクを表示する。

(defmethod render-widget-body ((widget rerender-test) &rest args)
  (declare (ignore args))
  (with-html
    (:p (str (body-of widget)))))

(defmethod render-widget-body ((widget rerender-update) &rest args)
  (declare (ignore args))
  (render-link
   (lambda (&rest args)
     (declare (ignore args))
     ;; ウィジェットのスロットを変更するだけで反映される
     (setf (body-of (rerender-test-widget-of widget)) "After update"))
   "update"))

ここまで定義した二つのウィジェットを子に持つ親ウィジェットインスタンスを作る関数make-rerender-testを定義し、init-user-sessionの中で呼び出す。

(defun make-rerender-test ()
  (let ((rerender-test-w (make-instance 'rerender-test :body "Before update")))
    (make-instance 'widget
       :children (list rerender-test-w
		       (make-instance 'rerender-update :rerender-test-widget rerender-test-w)))))

(defun init-user-session (root)
  (setf (widget-children root)
	(list (make-rerender-test))))

このWebアプリケーションをブラウザから見ると以下のようになる。

ここでupdateと表示されているリンクを押すと、以下のように変わる。

明示的にウィジェットの再描画を指示していないにも関わらず、rerender-testのスロットが変更されればその結果が自動的に反映されることが分かる。テーブルにレコードを追加するときなどに便利そうだ。
with-flowによるフロー制御されたウィジェットの置き換えと、ウィジェットのスロットの変更による再描画で、WebアプリのUIはほとんど構築できるのではないかと思う。