-
Notifications
You must be signed in to change notification settings - Fork 0
Who? Me?
We had a great question, about this code from this web-mx-sampler TodoMVC code for the toggle-all widget:
(defn toggle-all []
(div {} {:action (cF (if (every? td-completed (mx-todo-items me))
:uncomplete :complete))}
(input {:id "toggle-all"
:class "toggle-all"
:type "checkbox"
:checked (cF (= (mget (mpar me) :action) :uncomplete))})
(label {:for "toggle-all"
:onclick #(let [action (mget me :action)]
(event/preventDefault %)
(doseq [td (mx-todo-items)]
(mset! td :completed (when (= action :complete) (now)))))}
"Mark all as complete")))
The question:
Why does "me" in `(mget me :action)` resolve to the DIV instead of the LABEL?
I was busted. That code was obscure, and I knew it when I wrote it. I do not comment much, but I always comment the obscure. It has since been commented, but here is the story.
First, a reminder:
-
meis an injected variable akin to Smalltalkselfor Javascriptthis; and -
meis injected by any of thecF*family of macros for cell formulas, an example above being
(input {...
:checked (cF (= (mget (mpar me) :action) :uncomplete))}
...)
Getting back to our :onclick handler, how can we know, just from looking at the code, which widget will be "me"? Easy! Just search outward to the nearest cF*! Except there is none! Now what?
More background: every Web/MX macro named for HTML tags, in this case div, silently wraps children in a cFkids macro. That saves us from having to write:
(defn toggle-all []
(div {}
{:action (cF (if (every? td-completed (mx-todo-items me))
:uncomplete :complete))}
:kids (cFkids
(input {:id "toggle-all"
:class "toggle-all"
:type "checkbox"
:checked (cF (= (mget (mpar me) :action) :uncomplete))})
(label {:for "toggle-all"
:onclick #(let [action (mget me :action)]
(event/preventDefault %)
(doseq [td (mx-todo-items)]
(mset! td :completed (when (= action :complete) (now)))))}
"Mark all as complete"))))
Now when we search out from (mget me :action), we find the cFkids defining the :kids of the div!
Let's look at some other ways we could have written this code less obscurely.
For one, we could wrap the :on-click handler in a cF, just to get a "me". This "me" would be the label, so to get the desired :action property we would have to take the mpar parent of the label. Not as clean, but not obscure! But more brittle, because as we juggle our HTML the property :action might not be on our parent. So...
We need to give the :action host a name, :toggle-all-controller, and do an ascending search: (mget (fasc :toggle-all-controller) :action):
(defn toggle-all []
(div {}
{:name :toggle-all-controller
:action (cF (if (every? td-completed (mx-todo-items me))
:uncomplete :complete))}
(input {:id "toggle-all"
:class "toggle-all"
:type "checkbox"
:checked (cF (= :uncomplete (mget (mpar) :action)))})
(label {:for "toggle-all"
:onclick (cF #(let [action (mget (fasc :toggle-all-controller) :action)]
(event/preventDefault %)
(doseq [td (mx-todo-items)]
(mset! td :completed (when (= action :complete) (now))))))}
"Mark all as complete")))
Does the anaphoric me have your head spinning? Worry not. As I said, this was an almost deliberately obscure case. And we are not stuck with me in event handlers! Web/MX internally connects handlers with the proxy models that manage every DOM node. In the above example, we could have obtained the proxy label from the dom label attached handler thus:
(label {:for "toggle-all"
:onclick (fn [evt]
(let [label (evt-md evt)
action (mget (fasc :toggle-all label) :action)]
(event/preventDefault evt)
(doseq [td (mx-todo-items)]
(mset! td :completed (when (= action :complete) (now))))))}
"Mark all as complete")
Which approach to me resolution is best? After decades with Matrix, I am comfortable with the anaphoric me, so the lighter mechanism of just grabbing a lexical works best. For others, working from the event to the proxy of the .-target might be more comfortable.