Hello everyone me and @hiskennyness made this shadow-web-mx starter (TodoMVC) https://github.com/lidorcg/web-mx-shadow Hope it'll be helpful
Thanks for providing the impetus to get shadow on board as a build option! 🙏
Thx also to @thheller for getting me sorted out on a bad practice (`set!` on a dynamic var still bound to its root value) that was getting in the way of a shadow build.
ps. note that the name is indeed web-mx-shadow, not "shadow-web-mx".
(make :name :greeting
:user-name (cI "John")
:text (cF (str "Hey, " (mget me :user-name)))
:dom (cF (div (mget (fasc :greeting me) :text))))
Hey does anyone knows why can't I get to :greeting node?This is sth I have been planning to fix/clarify: fasc does not include me in its ancestor search. In detail, in your excerpt (thx for that):
• :text just mgets me (the :greeting) directly. That is fine;
• :dom however calls (fasc ... me) to searches "up" from me, perhaps reasonably thinking it is an inclusive search. But is not, so...
• ...`fasc` skips me, the :greeting, and considers first at the parent of me.
In CL I have two handy navigation utilities, upper and nearest , nearest being inclusive, upper meaning "above me".
Does this help? Please continue if I can clarify further. This is destined for the FAQ. 🙂
Does this docstring look good?
(defn fasc "Search up from `where`, excluding where and following only parent links for `what`."
[what where & options]
(apply md/fasc what where options))
Meanwhile, looking at the original CLJ version, I see a much more elaborate version of fasc. I better port that as is -- and it includes an option to say "include me".
I have said it before, forgot what a POC state of mind built this version. Time for a solid week of finishing.
Working elsewhere on the graceful GCing of MX widgets and resoures they might use. Project Quiesce, on the timesheet.I wasn't expecting fasc to be me inclusive.
I was expecting (make :name :greeting ...) to be the parent of (div (mget (fasc :greeting me) :text)
but for some reason me at div returns this:
#object[cljs.core.Atom {:val {:parent nil, :tag "div", :id "diver", :attr-keys (:id), :kids nil}}]
(I added id to be sure)OK, I see. So here is the sequence, given
:dom (div (mget (fasc :greeting me) :text))
...where :dom is a property of :greeting, the widget being made:
• in (fasc :greeting me), me is the widget :greeting;
• fasc searches up from me, the widget :greeting, and does not include that in its search. We agree this search is exclusive; therefore
• when fasc moves to its parent, it has already missed the widget :greeting.
Mind you, if we had nested widgets named :greeting, we would find the wrong widget :meeting, as the search sailed up to it and matched.
Moral: Live by unlimited MX search, and die by it if we miscontrol the search. To me this is the one speed bump when coding MX--slowing down to a crawl when I have to specify navigation.so div does not create a nested widget?
and why the object me that get logged from here:
:dom (div {:id "diver"} (mget (log me "ME") :text))
looks like this:
#object[cljs.core.Atom {:val {:parent nil, :tag "div", :id "diver", :attr-keys (:id), :kids nil}}]
?
(I don't have access to :text for it BTW)Oh, jeez, I am sorry. The problem is completely different!
Web/mx hides all this, so open-coding :dom ..... has issues. Let me look at your code some more.
fasc is part of the MX family mechanism.
In family, every model (aka map representing a reactive object), has another model as its :parent. Except the root model!.
Also, every model has zero or more children called :kids.
web/mx macrology takes care of that for us, so we never see it. But that hierarchy is what we are traversing when we navigate.
The good news is you just need a simple with-par around the div. Let me reresh my memory. brb...
Try:
(make :name :greeting
:user-name (cI "John")
:text (cF (str "Hey, " (mget me :user-name)))
:dom (cF (with-par me ;; does the right thing to support fasc
(div (mget (fasc :greeting me) :text)))))
So sorry for the false starts on this!what is the role of with-par cF and make in the construction of the tree?
• make is the model factory in the MX API. It does crucial initialization of a standard CLJ map, including setting up control internals in meta;
• (with-par me (make... arranges (via a dynamic binging) for the model created by make to have me as its the :parent
• macro cF creates a formulaic cell for a model property. The cF mechanism is how one property can be a function of zero or more other MX properties. As a convenience, cF injects the model owning the property as a lexical me , availed to functions that are the "formulas".
Here are the guts of cF and brethren, via a shared internal macro ;
(defmacro c-fn-var [[c] & body]
`(fn [~c]
(let [~'me (c-model ~c)
~'_cell ~c
~'_prop-name (c-prop ~c)
~'_cache (c-value ~c)]
~@body)))
Note that aside from me, the model owning the property a cF defines, cell functions have access to
• _prop-name, the name of the property they occupy;
• _cache, the current value of the property being recalculated, in case we are incremental in nature, or sth; and
• _cell, the whole cell structure, useful when debugging.So only with-par create a tree hierarchy and it must be used explicitly.
No other construct builds a parent-child rel?
Well, in non-UI MX code, we might see things like:
(make :mx-type :team
:name "Yankees"
:kids (cF (binding [*parent* me]
; imagine these next selected at random, so generated functionally
(make :mx-type :player...)
(make :mx-type :player...)))
The keys then, to making MX family utilities work is maintaining :parent and :kids, perhaps by binding *mx-parent.
tl;dr: manage :parent and :kids appropriately, and the MX family suite of utilities will work OK.
Btw, :parent should never change, nor should it be formulaic. Aside from a root matrix model, all :kids must have a parent, and that parent must be provided to make , either as an initarg or as the dynamic binding of tiltontec.model.core/*parent*.In UI code, the web/mx tag macros hide the parent/kids set-up. I hate typing, and noise/boilerplate, is the deep background here.
FWIW, Flutter is super-OO, so unlike HTML which is all DOM nodes and children with a single parent, Flutter/MX as we speak is developing new navigation and even model life-cycle capabilities, so the parent-child pattern will no longe rule. eg, :kids will be just one way to have navigable structure.
Do you think any of that will be back-portable to mx?
Where can I find pure mx (no web) child parent code?
Absolutely. It is a straightforward change. One issue might be whether the API should treat all substructure as "owned". In past versions of MX, I implemented the idea of a :host for substructure, so :kids could be served for tree-modelling, but a model could have other structure, perhaps :parts or a singular :thermostat. Navigation knew to try the :host if the current model's :parent was nil.
The easiest way to see pure MX is the test suite. I am afraid I do not have an example of an interesting non-UI MX app, in clojure, anyway. I have a Common Lisp RoboCup client that became quite elaborate, if you would like to see that.
Here is the test suite: https://github.com/kennytilton/matrix/tree/main/cljc/matrix/test/tiltontec/model Most are pretty abstract, but a couple get semantic.
Here is RoboCells : https://github.com/kennytilton/robocells, the Common Lisp RoboCup soccer client that got all input and executed all actions over a UDP socket.
I glossed over sth:
One issue might be whether the API should treat all substructure as "owned".
Suppose we have a :thermostat property/substructure of a larger :boiler model. That should be "owned" by the boiler, and be disposed of when is the boiler.
But what if we did sth weird, gave the :boiler a :balancer property/model, which would be the same instance for all boilers. Not how I would model it, mind you. Just cant think of a better example. But suppose we want a common instance as a property of many instances. Then we would not want to dispose of the :balancer when disposing of a :boiler. [edit: So we would need some way to tag hosted (non-kids) model as owned or not. Prolly by creating the model with some indicator that it should not be owned, possibly by the simple trick of creating it with its own parent, which hosting logic would take as an indicator, own or not."Do you think any of that will be back-portable to mx?"
I find myself pining for a #?(:cljd ...) option, btw. 🙂 But I can also conform the two closer and closer as I evolve the mx in f/mx. Version 1 was a mad dash to get to a POC in re Dart and Flutter (and CLJD) as a platform. I reached POC quite a while ago, now I have started a hot week of "finishing" which will make gene-swapping easier.
OK if I turn your code example into another bit of doc? Your related questions defined well the issues needing explication.