@hiskennyness itβs so you can process children in a more natural form
Actually @flyboarder, I think the reason is the unstated goal that all dom-yielding forms always accept the pattern (fn ~@[key-value-pairs] children*). Otherwise I could just specify that my-list
needs all attributes in a map as the first arg perhaps with a nicety saying the map can be skipped. I really like the idea of everything being a function, but then I do not want to be hamstrung in how I write my functions.
There are many examples of just using functions, this is common for state
I think the idea is, if you want to play that game where attributes can be spliced ahead of children instead of lumped in a map, defelem
is there for you. Me, I like text structure to facilitate editing.
@hiskennyness i use both defn
and defelem
@chromalchemy no idea about the old for-tpl
but the new one i proposed uses subvec
so there's sharing for sure
wait... i'm not quite sure what you mean about "sets of DOM nodes in the cache" lol
@hiskennyness there's no downside to using defn
if you haven't bought in to the attributes/children style
@thedavidmeister I guess I meant how elements are shown or hidden. Is this done with display: none
? I guess the cache is the DOM, and element blink in and out of view (ie :toggle
).
oh no, DOM elements are objects in their own right
they aren't being shown/hidden they are being attached/detached from the document
@hiskennyness defelem is basically sugar on hoplon.core/parse-args
https://github.com/hoplon/hoplon/blob/master/src/hoplon/core.cljs#L136
@chromalchemy the elements get built originally each with a cell that has a get
reference either to a position or a key in the items
cell you pass in
so that cell knows how to keep itself updated with the right value based on index/key relative to items
- the tpl doesn't have to do anything ongoing, just handle the original build with a sensible item
cell=
the DOM elements are then just created as needed by looking up whether we already have a DOM element for that index/key
then it's returned as a cell with a list of DOM elements in it
that's the end of what for-tpl
does, but then merge-kids
is what takes over to actually append/detach those elements from the DOM (which could definitely be improved in the future to do more efficient diffs to minimise those DOM operations)
but AFAIK you cannot do structural sharing on a DOM element directly because not only are they totally mutable but the browser is actively mutating them all the time, often in ways that you cannot respond or detect with JS
@chromalchemy i discussed the performance of the basic for-tpl
here - https://github.com/hoplon/hoplon/issues/236
@thedavidmeister Ok. That helps. I probably need to familiarize myself with basic DOM semantics (attach/detach/remove/etc).
"You can think of the way that the DOM is stored as a "cache" of sorts, but probably quite a bit smarter than most caches you may have worked with in the past. Any Javelin cells in the element definition will still be "live" even if the element is not visible." https://github.com/hoplon/hoplon/wiki/Dynamic-DOM-manipulation-aka-Template-Macros
That had me picturing mechanical caches..
@thedavimeister I understand. I think the last bit on that page is good but somewhat misdirecting.
`(defn my-list
[& items]
(ul (map li items)))
` is a straw man. There is no need to write it that way, so it does not justify defelem
(nor is there any great need for defelem
unless one does not like putting braces around maps). But this is just nitpicking. π@hiskennyness that's good feedback though, we can always update the wiki π
@chromalchemy hah, ok maybe we can update that in the wiki too
@chromalchemy on your comment about transducers, technically it isn't the comp making the transducer, it's calling a list processing function without the list like (map my-fn)
returns a transducer but (map my-fn my-list)
returns a lazy sequence - the idea is that you comp the transducers first and no lists get created and then you give it your list, and all the processing is done without intermediate lists being produced
I do see a case for stanardizing the syntax. To my mind, our dom-generating functions basically deliver on web components
, so maybe they shoul not go crazy on variable parameter lists. Me, I see this has totally throwing of the shackles of HTML markup. Call me a wild and crazy guy!
yeah it's pretty sweet!
actually, because parse-args
is called consistently when you use an element as a fn
the difference between defn
and defelem
is very small
(defn my-el [] (div))
then later ((my-el) :attr :foo (div) :other-attr :bar)
will still work
which is important because if you want to be writing functions that take an element, add some things to it then return the element, you can't be required to know the signature of the fn
used to create the element originally
So there are cases where defn
is has more utility. Are there cases where defelem
is preferred (beyond ergonomics)?
I've always used defelem
because I have little intuition about the tradeoffs, and don't want to unknowingly break any beneficial magic π
@chromalchemy 90% of it is just juggling arguments and doc strings
if you find defn
more convenient just use it
all elements will get the defelem
syntax after they are created, we're really just talking about your elements "constructor" here
That said. As a beginner. I found defelem
very welcoming because the attrs + child thing felt like a smooth continuation of my HTML experience (shackles and all!)
that's true
training wheels π
πΆ
My first LISP: I'm a real coder now! π΅
haha, well maybe we can reword the wiki so that it is still presented as beginner friendly but makes it obvious that there's nothing bad about defn
either
brb, getting β
I'm all for community conventions. Since Hoplon is so flexible, the tradeoff is that elucidating and curating the options is a great challenge in itself (Lisp Curse)
But it's pretty cool that it can works both ways. I think there's something powerful about how Hoplon can look like markup. Before I started coding, I found all the high-level stuff fascinating, but my eyes would glaze over when it came to code samples. Before you internalize syntax, it all looks like an arcanely-formatted stop sign, the symbols worse than nonsense, actively defying you to intuit the obvious and important meanings you know they have. At least until you start looking up definitions (but for many this is a HUGE leap). That's also the story for HTML. It's just that many millions have taken the leap with that particular syntax. I personally found it reassuring that Alan and Micha conceptualized hoplon as hlisp, and likened it to PHP. Now i see Hoplon as more of a generic markup simulator or VM, not merely HTML. I think it's a powerful good to continue to tend a gentle path from standard web markup + reactive -> dynamic "templating" -> bespoke (app) programming. Clojure has a problem with approachability. I see Hoplon as potentially a key gateway (It was for me at least π )
@thedavidmeister Thanks for all the explanations. I'll do more on my part to grok the dom-state internals. But would you comment on this example:
(cell mylist [:a])
(for-tpl [x mylist] (elem x))
(reset! mylist [:a :b]
(reset! mylist [:c])
What is in the dom at the end? What is removed vs merely detached? If mylist
, and the reset!'s had hundreds or thousands of keys, would this be putting undue stress on browser memory? (hence keyed-for-tpl...)@chromalchemy every dom element created by the for-tpl
lives in the cache indefinitely, just like memoize
in core
same for keyed-for-tpl
obviously this is not ideal, but the intent is that it's an improvement on a vanilla map
where DOM elements are created and kept indefinitely and also cannot be reused
for a regular for-tpl
, if you have a list with thousands of elements, well you have a list with thousands of elements
it is what it is - the only way you'd have "undue" stress would be if you spiked the list up to a thousand elements then brought it back down to dozen or so
the keyed-for-tpl
is too new (hasn't even been pushed into a snapshot yet) to have any fancy cache clearing logic
i listed that as something that can be followed up at some point!
in theory there's lots of different strategies that could be used to evict "stale" elements beyond a threshold - e.g. https://github.com/clojure/core.memoize/
but also keep in mind that destroying a DOM element and destroying the cells associated with the DOM element are two different things - this is where i'll probably need some help investigating when we get to it!
the short answer is - there is currently no "destroy this element and all cells associated with the element" general function to do the kind of cleanup you're thinking of that will work with the browser GC process
@chromalchemy as far as "hoplon looks like markup" well that's an old debate of lisp vs. XML - http://wiki.c2.com/?LispVsXml
under the hood (div)
is mostly just sugar for document.createElement('div');
in JS
and (body (div))
is sugar for document.body.appendChild(document.createElement('div'));
because body
is a "singleton" in hoplon terms so it merges children into the existing element in the document rather than creating a new <body></body>
element
calling the created DOM element later as a function uses the merge children logic
so ((div) (div))
is something like document.createElement('div').appendChild(document.createElement('div'));
obviously the hoplon approach is far less verbose but it's important to realise that there's no "magic" going on...
the singletons are: body
, html
, and head
everything else is an elem, and you can extend to new elems that we may not support yet in core by calling (def fancy-new-tag (mkelem "fancy-new-tag"))
maybe we need something like this https://kanaka.github.io/clojurescript/web/synonym.html for hoplon...
i tried to build something like that as a hoplon app a year or two ago, unfortunately it meant shuttling a huge amount of js to the client frmo the server because hoplon couldn't run entirely in browser
(i went the approach of compiling on the server because -tpl, cell= etc werent cljs compat)
@thedavidmeister @alandipert we could throw something like that up on the website, as just a side by side without eval
@thedavidmeister https://caniuse.com/#feat=childnode-remove remove is now widely supported, I would imagine we can implement our own remove method which will cleanup cells, and then call the browser remove
actually I have an idea for easily implementing this
@alandipert can we a repo with the current hoplon website in it? Iβd love to convert this to Hoplon and get a side-by-side thing going on!
flyboarder there is a repo, at https://github.com/tailrecursion/hoplon.io
flyboarder i sent you an invite, have at it! thanks
the only thing i ask you preserve is the adzerk credit, in some form (doesn't need to look like that)
@alandipert cheers! will do!
@flyboarder could you push a snapshot before you get deep into that π
@thedavidmeister yeah Iβll get a snapshot out tonight
Itβs about 5pm here right now
awesome cheers