hoplon

The :hoplon: ClojureScript Web Framework - http://hoplon.io/
flyboarder 2018-01-23T00:12:20.000145Z

@hiskennyness it’s so you can process children in a more natural form

kennytilton 2018-01-23T00:57:13.000208Z

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.

flyboarder 2018-01-23T00:58:21.000126Z

There are many examples of just using functions, this is common for state

kennytilton 2018-01-23T01:16:06.000304Z

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.

2018-01-23T01:16:29.000122Z

@hiskennyness i use both defn and defelem

2018-01-23T01:17:38.000211Z

@chromalchemy no idea about the old for-tpl but the new one i proposed uses subvec so there's sharing for sure

2018-01-23T01:18:22.000287Z

wait... i'm not quite sure what you mean about "sets of DOM nodes in the cache" lol

2018-01-23T01:21:03.000056Z

@hiskennyness there's no downside to using defn if you haven't bought in to the attributes/children style

2018-01-23T01:22:31.000246Z

@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).

2018-01-23T01:23:00.000140Z

oh no, DOM elements are objects in their own right

2018-01-23T01:23:18.000192Z

they aren't being shown/hidden they are being attached/detached from the document

2018-01-23T01:26:44.000224Z

@hiskennyness defelem is basically sugar on hoplon.core/parse-args https://github.com/hoplon/hoplon/blob/master/src/hoplon/core.cljs#L136

2018-01-23T01:28:58.000045Z

@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

2018-01-23T01:29:27.000167Z

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=

2018-01-23T01:30:06.000228Z

the DOM elements are then just created as needed by looking up whether we already have a DOM element for that index/key

2018-01-23T01:30:45.000199Z

then it's returned as a cell with a list of DOM elements in it

2018-01-23T01:32:02.000241Z

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)

2018-01-23T01:33:50.000067Z

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

2018-01-23T01:36:34.000118Z

@chromalchemy i discussed the performance of the basic for-tpl here - https://github.com/hoplon/hoplon/issues/236

2018-01-23T01:43:41.000225Z

@thedavidmeister Ok. That helps. I probably need to familiarize myself with basic DOM semantics (attach/detach/remove/etc).

2018-01-23T01:45:02.000035Z

"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

2018-01-23T01:45:42.000186Z

That had me picturing mechanical caches..

kennytilton 2018-01-23T01:47:28.000043Z

@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. πŸ™‚

2018-01-23T01:51:30.000146Z

@hiskennyness that's good feedback though, we can always update the wiki πŸ™‚

2018-01-23T01:52:09.000075Z

@chromalchemy hah, ok maybe we can update that in the wiki too

2018-01-23T01:55:21.000207Z

@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

kennytilton 2018-01-23T01:55:36.000027Z

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!

2018-01-23T01:58:34.000143Z

yeah it's pretty sweet!

2018-01-23T02:00:18.000149Z

actually, because parse-args is called consistently when you use an element as a fn the difference between defn and defelem is very small

2018-01-23T02:01:15.000223Z

(defn my-el [] (div)) then later ((my-el) :attr :foo (div) :other-attr :bar) will still work

2018-01-23T02:02:25.000374Z

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

2018-01-23T02:04:15.000090Z

So there are cases where defn is has more utility. Are there cases where defelem is preferred (beyond ergonomics)?

2018-01-23T02:06:43.000072Z

I've always used defelem because I have little intuition about the tradeoffs, and don't want to unknowingly break any beneficial magic 😁

2018-01-23T02:07:24.000020Z

@chromalchemy 90% of it is just juggling arguments and doc strings

2018-01-23T02:07:38.000263Z

if you find defn more convenient just use it

2018-01-23T02:08:06.000320Z

all elements will get the defelem syntax after they are created, we're really just talking about your elements "constructor" here

2018-01-23T02:09:22.000153Z

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!)

2018-01-23T02:09:44.000134Z

that's true

2018-01-23T02:09:57.000067Z

training wheels πŸ˜›

2018-01-23T02:10:25.000077Z

πŸ‘Ά

2018-01-23T02:12:34.000080Z

My first LISP: I'm a real coder now! 🎡

2018-01-23T02:14:41.000231Z

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

2018-01-23T02:20:32.000102Z

brb, getting β˜•

2018-01-23T02:27:48.000344Z

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)

2018-01-23T02:38:34.000199Z

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 πŸ™‚ )

2018-01-23T02:45:30.000190Z

@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...)

2018-01-23T03:15:01.000157Z

@chromalchemy every dom element created by the for-tpl lives in the cache indefinitely, just like memoize in core

2018-01-23T03:15:17.000136Z

same for keyed-for-tpl

2018-01-23T03:16:02.000107Z

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

2018-01-23T03:16:54.000088Z

for a regular for-tpl, if you have a list with thousands of elements, well you have a list with thousands of elements

2018-01-23T03:17:26.000035Z

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

2018-01-23T03:18:14.000074Z

the keyed-for-tpl is too new (hasn't even been pushed into a snapshot yet) to have any fancy cache clearing logic

2018-01-23T03:18:49.000030Z

i listed that as something that can be followed up at some point!

2018-01-23T03:19:59.000003Z

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/

2018-01-23T03:21:14.000142Z

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!

2018-01-23T03:23:01.000162Z

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

2018-01-23T03:25:51.000210Z

@chromalchemy as far as "hoplon looks like markup" well that's an old debate of lisp vs. XML - http://wiki.c2.com/?LispVsXml

2018-01-23T03:28:27.000278Z

under the hood (div) is mostly just sugar for document.createElement('div'); in JS

2018-01-23T03:29:37.000248Z

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

2018-01-23T03:32:20.000089Z

calling the created DOM element later as a function uses the merge children logic

2018-01-23T03:32:54.000272Z

so ((div) (div)) is something like document.createElement('div').appendChild(document.createElement('div'));

2018-01-23T03:33:47.000088Z

obviously the hoplon approach is far less verbose but it's important to realise that there's no "magic" going on...

2018-01-23T03:37:41.000282Z

the singletons are: body, html, and head

2018-01-23T03:38:48.000171Z

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"))

2018-01-23T08:13:48.000090Z

maybe we need something like this https://kanaka.github.io/clojurescript/web/synonym.html for hoplon...

2018-01-23T17:00:42.000001Z

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

2018-01-23T17:01:01.000526Z

(i went the approach of compiling on the server because -tpl, cell= etc werent cljs compat)

flyboarder 2018-01-23T21:09:52.000317Z

@thedavidmeister @alandipert we could throw something like that up on the website, as just a side by side without eval

flyboarder 2018-01-23T21:35:49.000178Z

@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

flyboarder 2018-01-23T21:42:30.000287Z

actually I have an idea for easily implementing this

flyboarder 2018-01-23T21:48:14.000767Z

@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!

2018-01-23T21:48:55.000157Z

flyboarder there is a repo, at https://github.com/tailrecursion/hoplon.io

2018-01-23T21:50:00.000041Z

flyboarder i sent you an invite, have at it! thanks

2018-01-23T21:51:00.000298Z

the only thing i ask you preserve is the adzerk credit, in some form (doesn't need to look like that)

flyboarder 2018-01-23T21:52:49.000063Z

@alandipert cheers! will do!

2018-01-23T23:49:04.000033Z

@flyboarder could you push a snapshot before you get deep into that πŸ˜‰

flyboarder 2018-01-23T23:49:27.000012Z

@thedavidmeister yeah I’ll get a snapshot out tonight

flyboarder 2018-01-23T23:49:34.000020Z

It’s about 5pm here right now

2018-01-23T23:49:54.000028Z

awesome cheers