Skip to content

The Cells Inside(tm) qooxlisp

kennytilton edited this page Sep 14, 2010 · 3 revisions

First, here is a comprehensive write-up of Cells, the dataflow package used throughout qooxlisp. Now let’s take a quick look at how it makes the programmer’s life easier.

First, a simple example from the original apropos dialog (not the make-over discussed elsewhere). The original designer felt the user needed a reminder just above the table of search results reminding them of what they had searched for. This value, by the way, is held in the sym-seg slot of the session instance. Before the first search, a reasonable label would need to be vaguer:

(lbl (c? (let ((sym-seg (sym-seg (u^ qxl-session))))
           (if (plusp (length sym-seg))
               (format nil "Symbols containing ~s" sym-seg)
             "Symbols Found:"))))

Expanding the macrology to make it easier to follow:
(make-kid 'qx-label
  :value (c? (let ((sym-seg (sym-seg (u^ qxl-session))))
               (if (plusp (length sym-seg))
                   (format nil "Symbols containing ~s" sym-seg)
                 "Symbols Found:"))))

Some notes: c? just means we are creating a rule with no parameters but access to the instance via the variable self and access to the prior value calculated by the cell via the variable .cache. u^ is short-hand for a function that searches up the model hierarchy looking for the widget of type qxl-session. The format function simply returns the assembled string if the first stream parameter is nil. Anyway…

The rule above arranges for the reminder label to change to say the appropriate thing whenever the user searches on a different symbol segment. This connection happens transparently: simply reading the sym-seg slot of the session means the value slot of the label will be re-calculated (and the new value propagated to the Web page) whenever the sym-seg value changes. This would happen, by the way, even if the rule called a function which itself read the slot.

I have not played with it, but I understand the Open Laszlo project offers a similar dataflow mechanism.

But how does the user action in the browser change the sym-seg slot back on the user? The dataflow begins with just good old procedural code in the event handler. A partial listing:

(combobox :symbol-string
  (:add '(:flex 1)
    :allow-grow-x t
    :onchangevalue (lambda (self req)
                     (setf (sym-seg (u^ qxl-session)) (req-val req "value"))))))

As might be guessed, the qooxlisp system arranges for the above anonymous function (aka lambda) to run on the server when combo-box changeValue events fire in the browser.

We just saw that Cells helps propagate change within the application model, from the value searched for by the user to the reminder label. The interesting things is that qooxlisp itself leverages Cells’s change management power to handle the task of driving the browser-side qooxdoo model from the server-side Lisp.

Now let’s look at a more challenging change propagation task, one that requires change to the population of the application model. On the qooxdoo side, this will mean some widgets get removed and some get added.

The apropos interface includes a SelectBox widget showing the set of packages (groups of symbols) in which matching symbols were found (or all packages if no symbols have been found). Here’s the code:

(selectbox :selected-pkg
  (:add '(:flex 1)
    :enabled (c? (not (value (fm-other :all-packages)))))
  (loop for pkg in (b-if syms (syms-unfiltered (u^ qxl-session))
                     (loop with pkgs
                         for symi in syms
                         do (pushnew (symbol-info-pkg symi) pkgs)
                         finally (return pkgs))
                     (list-all-packages))
      collecting (list-tem (package-name pkg))))

…with this expansion:
(make-kid 'qx-select-box
  :md-name :selected-pkg
  :add '(:flex 1)
  :enabled (c? (not (value (fm-other :all-packages))))
  :kids (c? (the-kids
             (loop for pkg in
                   (b-if syms (syms-unfiltered (u^ qxl-session))
                     (loop with pkgs for symi in syms do
                           (pushnew (symbol-info-pkg symi) pkgs) finally
                           (return pkgs))
                     (list-all-packages))
                 collecting (list-tem (package-name pkg))))))

The kids of a SelectBox become the menu items via qooxlisp glue not shown. Not just menu items which get replaced all at once, but any alteration of the interface desired can be easily achieved with simple declarative code stating only what widgets should exist given other application state. We also see the SelectBox being disabled if the user has selected “Show all packages”.

By making GUIs easier, Cells draws smarter, more dynamic interfaces out of the developer. The declarative syntax makes it easier to hide boilerplate code. With more glue, interfaces can be similarly and seamlessly connected with database data. GUIS are notoriously hard to build, but Cells makes it kinda fun.

Clone this wiki locally