This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-12-29
Channels
- # adventofcode (4)
- # beginners (113)
- # boot (165)
- # cider (192)
- # cljsrn (82)
- # clojure (148)
- # clojure-austin (6)
- # clojure-russia (22)
- # clojure-spec (45)
- # clojure-uk (19)
- # clojurescript (153)
- # core-async (5)
- # cursive (7)
- # datomic (2)
- # defnpodcast (2)
- # emacs (1)
- # hoplon (617)
- # instaparse (10)
- # lein-figwheel (19)
- # luminus (2)
- # off-topic (12)
- # om (3)
- # onyx (36)
- # pedestal (1)
- # protorepl (43)
- # re-frame (8)
- # ring (7)
- # specter (17)
- # testing (2)
- # untangled (117)
- # yada (12)
Now https://github.com/mynomoto/github-client has infinite history because I haven't written the code that limits it to a reasonable number yet and a debug view to navigate to previous states.
I see that when-dom
is a way to run a function after a dom element is loaded. Is there any way to run a function after a given dom element is removed?
Relatedly, @alandipert's sample code from his blog post http://adzerk.com/blog/2014/10/a-dashboard-with-hoplon/ references with-let and with-init, but I have not been able to find these either of these macros in Clojurescript or Hoplon (possibly the commonness of the words is thwarting my attempts to google those). Where are those located?
@puzzler with-let is in javelin, with-init is in hoplon
could be a typo in the post: it's with-init!
in hoplon/core.clj
@puzzler btw i saw your earlier post about points of confusion, i have time now to explain some things if you're up for it
if my explanations work i can add to hoplon wiki, which is pretty sparse atm
My typo: yes, with-init!. Further explanation would be great. Also, I remember a year ago you did a live walkthrough explaining the source code, but I missed it. Do you have a video of that?
i couldn't figure out the button to click to record, so no đ
but that would be fun to do again
Ah, I was incorrectly looking for with-init! in hoplon/core.cljs, not thinking about how as a macro it would be in core.clj.
ah, yeah, i'm frequently confused about that myself
revisiting your questions
the essential thing in javelin, that cell=
converts code into using, is the formula
function
i.e. there isn't anything you can do syntactically inside a cell=
that you couldn't do functionally with formula
Seems like the main think cell= adds is the auto-detecting of cells (and maybe the auto derefing as well).
it's even dumber, but yeah
(def x (cell 0))
(def y (cell 1))
(def added ((formula +) x y))
added
there is a formula cell
equivalent to (def added (cell= (+ x y)))
so you can imagine how we do the transformation
at least, naively
in 2013 tho it was untenable, on firefox, using cljs at the time
too many objects created. so micha rewrote cell= so that it uses the minimum number of formula
calls
ie you don't need to call formula on a tract of code that's already in a formula body
so, most of the complexity of cell= is optimization related
so in the formula function... you see that it intercepts the arguments of the function it wraps
err sorry, it looks at its own arguments. in this case x and y
and sees if they are cells. if they are ,it adds watches to them
now changes to the cell arguments will propagete to +
through the formula
wrapper
OK, so a (formula f) takes all the inputs to f, and it is also checking as to which of those inputs need to trigger an update.
precisely
the useful invariant, with respect to formulas
and cells in general
is that by the time a formula can "see" the arguments its based on, all changes have propagated
or in other words, it sees a consistent view of its dependencies... cells are updated in dependency order
this is the real value-add of javelin as opposed to ie. manually adding watches
they are all updated synchronously
so, a swap! on some input cell will block until all the dependent cells have run
this is a decision point in data flow systems
we did it because its useful to know all side effects have run, since in some scenarios you need to update the cell graph as a side effect
I think the synchronous behavior is an advantage over the react approach, where the timing can be very tricky to figure out.
yes absolutely
it's also counterintuitive
micha had the idea originally, and i was not feeling it
it was weird to program as if everything had a value already
like the idea that cells have a value at all times, there's no way to delay their initialization
but ultimately it works great. esp. w/ lisp, and pervasive nil punning
another thing to recognize about react is that, they're not building on any reasonable sense of equality
so they couldn't do value-based changes even if they wanted. so they need you to wire up events
that said i'm not a react expert, i've never really used it
and it seems like with the various wrappers they can pave over most of the lifecycle stuff
otoh, javelin doesn't really compare to react, its totally browser agnostic
I mainly pointed it out just because I'm coding something in both hoplon and reagent for comparison purposes right now, and the hoplon approach is a breath of fresh air. Makes me want to understand it more thoroughly.
well, thanks đ
there is an interesitng comparison to reagent to be made
which kind of illuminates what javelin does a little bit also
afaict the Best Practice in cljs react.js wrapper world is a state management technique called "The Big Atom"
the idea being, instead of having many disparate piecse of state in atoms strewn around, you keep as much state as possible in a global atom with a map
yeah, totally possible, since cells are glorified atoms
Hoplon seems to support multiple ways of structuring the application, and that's the way I (currently) understand best how to make the design clean.
there is a parallel here to atoms and stm in clj
(def big-atom (atom {:checking 1 :savings 2}))
(swap! big-atom (fn [m] (-> m
(update :checking inc)
(update :savings dec))))
one clear merit is changes can be transactional
My focus right now is to build components which are completely agnostic about where the data is coming in from, and where it is getting stored to, and making those inputs to the component. Then the component could be fed independent cells, or lenses into one data structure -- it really shouldn't matter from the perspective of the component.
depending on what you're building, i can see the advantage of passing queries instead of cells, to the components, when you instantiate them
queries over a central cell somewhere
something to keep in mind though is that javelin constitutes a 'secret' big atom
in that ,cells can be anonymous and still dependent on other cells. and can also still be updated transactionally
(def checking (cell 1))
(def savings (cell 2))
(dosync
(swap! checking inc)
(swap! savings dec))
in that example, dependencies of checking
and savings
aren't notified until after both the swap!'s have happened
dosync
is a macro in javelin.core.clj
which we happily co-opt since cljs has no stm
re: formula cell containing query, :thumbsup:
I think I understand the principles of formula cells and propagation. I'm still a little fuzzy on how much is discovered by the cell= macro, but perhaps I understand it as much as I am going to without a careful reading of the code. For example, I was really surprised by the behavior of @my-cell inside a cell= formula, versus my-cell by itself, and I don't think I quite understand what ~
does, since it doesn't suppress discovery of the cells the way I thought it would.
ok, yeah
And then beyond that, I want to understand how it interacts with the html elements in hoplon, and how cells in certain positions cause the whole element to become reactive.
Like, when I do a println of my-cell and @my-cell
inside a cell= formula, I get the same thing printed out, which surprises me.
i think the way to understand that is by considering ((formula deref) x)
which is equivalent to (cell= @x)
we know that formula
checks which of its arguments are cells, sets up watches, etc
and when it's fired, it gets the values from those cell arguments and passes them to the function. in this case deref
so you can see how, in the function that formula
lifts, actual cells never make it into the formula-fied function. only their values
((formula (fn [y] [@x y])) x)
is another way of looking at it
in that example, the y
argument is always = @x
since the fn
captures x
wherever it is in teh example, it's a cell inside the function
and so, the deref is cool
does that make sense?
I'm not quite seeing it. Seems that if the function in the formula never actually sees the whole cell, the deref should be applied to the contents of the cell, not the cell itself.
you're referring to the 2nd example?
Referring to your statement " in the function that formula
lifts, actual cells never make it into the formula-fied function. only their values"
i see. and i think was not clear
so cells can definitely be inside functions lifted by formula
but parameters of lifted functions cannot
i was trying to show that w/ the 2nd example
also you're right btw
in the case of ((formula deref) x)
, if (def x (cell 123))
, it would explode
since it would try to run (deref 123)
That wasn't the behavior I saw though when doing (cell= @x)
. So am I mistaken about the behavior I think I observed, or does it not really correspond to ((formula deref) x)
?
hm i'm surprised by that too
i'll try an experiment here, 1 sec
The behavior I observed was that if you did (def x (cell 123))
, I got seemingly the same result with (cell= x)
and (cell= @x)
.
Reading through some of the docs, it seemed like maybe the second example was a non-reactive version that evaluates the @x
once.
that would need to be ~@x
i think
i will check out the docs also
From the docs: (defc d {:x @a}) ;; cell containing the map {:x 42}
where a is (cell 42).
ok yeah
so defc is veneer for (def d (cell {:x @a}))
so in that case the deref happens before the map is installed as the cell value
correct
vs. defc=
, which converts (defc= foo (inc x))
into (def foo (cell= (inc x)))
so that's the same as (def d (cell {:x a}))
and it wouln't be the same, because in that case a
is a cell
so d is a cell with a key :x that points to a cell
correct, cell
is analagous to atom
in clj
ie it's a function that takes a value, no fancy business
we should make it clearer
also it seems like use of defc in the readme isn't helping anyone
Now, I need to double-check my-cell
vs @my-cell
inside formula cells. Maybe I am misremembering because I confused a formula cell with a regular cell, but I'm not certain.
would it have made more sense if the readme progressed from cell
-> formula
-> cell=
?
yeah, maybe what you saw was legit, but you were dealing with a cell that had a cell value in it? in that case the deref might have worked
Actually, the problem I was having was that I had a cell with a cell in it, and I wanted to get at the inner cell and I couldn't, because cell= kept detecting my use of cells in a way where I was either derefing not enough or too much, and I was struggling to get the behavior I wanted. I eventually worked it out, but it was confusing.
ok yeah
that's confusing territory
cell= is not amenable to any kind of 'meta' cell usage
like, you couldn't use it to implement a spreadsheet
does (defc= g (+ a ~(inc @a)))
make sense to you?
I think the things I needed to know were: 1. that cell
is a function and does nothing special, just like atom
. I think it is useful to explain cell=
in terms of formula
but maybe that wasn't the key piece of info. Key pieces of info are: 2. Inside of cell=
, any other symbol that refers to a cell does not require a @
because it will be discovered, automatically dereferenced, and set up to propagate into this cell. And 3. Certain things make a symbol opaque to the codewalking macro, such as putting it inside a function (even an anonymous function directly inside the macro). I still probably don't know the full set of things that are opaque to the codewalk.
Also, it's key, I think, to understand that you don't explicitly deref a cell when used in certain positions of html elements or attributes -- I'm not sure I have a perfect picture of this yet, either, but my understanding is probably getting close to the true behavior.
Yes, that example makes sense, although I think (defc= ~a)
surprised me. (Can't remember exactly what that did, but it didn't do what I thought it would do).
heh, that's a weird one
thanks for writing up your learnings
i think it makes sense to do some heavy editing of the javelin readme
definitely defc and defc= really seem to cloud learning
and they're totally nonessential
There's a big picture concept that I think is missing from the docs as well. I think there's a big misconception in the community that hoplon is rerendering the full page in every cycle. For me, it was a big aha when I realized that (div my-cell)
is setting up what I imagine to be a special kind of formula that when my-cell updates, it calls some jquery function to change the contents of that specific div. Since propagations don't flow unless a value actually changes with respect to equality, you get really surgical precision and changes only to the elements that require it, arguably even more tightly focused than something like reagent where whole components are rerendered if anything inside changes.
right
that's how we're competitive
our dom mutations are precise
Yeah, it's great. But I remember at one of the Clojure/West conferences where you talked about Hoplon, someone next to me shook their head dismissively and said, "We've tried re-rendering the DOM every cycle and everyone knows it's too slow." So I think the way that cells are interpreted by things like div
are really key to understanding the value proposition of hoplon.
De-magicking that a little bit I think will go a long way to showing off how efficient hoplon is.
that's great feedback
i just had the idea to make the comparison in docs to conservative/precise GCs, but that's probably also a bad analogy
not enough GC nerds out there
you had questions about html and -tpl btw? anything specific?
I think a compelling way to explain it would be to first show cells, then formula cells. Then actually set up a plain ordinary div and show a formula cell that responds to some other cell by using a manual jquery command to manipulate the div. Then show that hoplon's div automatically does that for you.
yeah, attributes are good entree because they don't involve -tpl
Might be useful to add another level of formula in there. So you have some source cell, say a map. Then you access one particular value in that map via a formula cell, and this is the thing you want to react to. Then you can demonstrate that the jquery mutation doesn't happen when the other value of the map changes, or when the same value is re-assoc'ed to the same key.
i like it
So was the summary I received this morning correct that the things that auto-hook into jquery to become reactive is when a cell is an attribute or a single child of an HTML element (with text
being the one exception where it can occur as any element among several children)?
kind of
it's worth separating the problem of updating attributes from the problem of updating children
Also, might want to make clear that the magic behind cell=
and the interaction of cells with hoplon.core's elements have nothing to do with boot, but are just built into the functions that generate the elements.
yeah. i think moving away from the cljs.hl files could help with that
yogthos's post back in June was a huge eye-opener for me that the javelin/hoplon system worked just fine as libraries in a leiningen project, equally easy to use as, say, reagent.
Since I often use luminus templates as a starting point to learn how to effectively use Clojure's many libraries for databases, authentication, etc., that was really useful to discover.
yeah it would be great to be more approachable, we've never really hit critical mass the way boot did when it was split off
i'm not sure we have any really compelling reasons for someone to use hoplon instead of a react-based thing though
both things solve the don't-repaint-entire-dom-every-time problem but beyond that the differences are subtle
or maybe even just a matter of taste
For me, some of the compelling reasons are: 1. hoplon removes much of the complexity of reasoning about the subtle timings of when things are updated or displayed. 2. hoplon integrates better with existing non-reactive widgets. 3. Reagent has essentially two layers of "magic", the reactive atoms and the whole react virtual dom, and it is fairly complicated to understand in depth the behavior of both -- hoplon essentially has one key "magic" concept to learn: cells, specifically formula cells. 4. Javelin cells can be used for a rather wide assortment of problems and supports a number of different ways of designing a web page: it's a simpler concept, but with more flexibility. 5. Even more precision about what updates and under what conditions.
that sounds really good
i just realized that i don't know why it's good because i've never used anything else
Just as an example, I've got a cursor pointing at a cursor pointing at a cursor pointing at a source reactive atom... about four levels deep, mirroring the way a widget is nested in another widget, etc. This propagation is way too slow, because some sort of delay is introduced with each propagation in reagent. In hoplon, that's a non-issue and it works perfectly.
that is a suspicious aspect, of what i understand about react based things
the way they encourage, and sometimes require, your dom hierarchy to mirror your data
specifically what's suspicious is the degree of coupling that implies
I also have a vision about how javelin cells could potentially be used to drive canvas animations and graphics. Because a formula cell can trigger any kind of side-effecting operation. I think that would be more difficult in reagent. Again, you offer a simpler concept with arguably a wider range of uses.
:thumbsup:
still, very interesting to hear your perspective
both as a newcomer to hoplon and as someone with recent reagent experience
and as someone who rocks at clojure programming
(instaparse, so good đŻ )
i had to click on it. then i recognized from clj mailing list
i should probably go soon, but can i clear anything up about attrs or -tpl before i do?
I'll probably want to probe that more deeply with you at some point, but I can do some more explorations on my own first.
for attrs, the key things are the do! and on! multimethods in hoplon.core
i shall go then. thanks for the great conversation! looking forward to hear more feedback and field questions as you have them
Thanks for providing such a helpful and welcoming slack community, and such useful information. Talk to you later.
@alandipert FWIW my conversion path was Om -> Reagent -> Re-Frame -> Hoplon with Hoplon being by far the simplest. Also you won't find things like Hoplon/UI anywhere else đ So it must be the simplicity attracting smart people to building such ambitious projects
@puzzler @alandipert also can i point out how easy it is to test hoplon thanks to it returning dom elements that are just normal dom elements
(defn data-div
[c]
(div :data-foo c))
(deftest ??data-div
(let [c (cell nil)
el (data-div c)]
foo? #(-> % js/jQuery (.is "[data-foo]")
(is (not (foo? el)))
(reset! c true)
(is (foo? el)))
thatâs been one of my favourite aspects lately, really made me feel confident refactoring my UI đ
@thedavidmeister what do you find yourself testing in terms of UI? Do you do fine-grained element unit tests?
@dm3 yes, i stick tests for every element just under the element
i basically have a cljs test runner watching all my elements
iâll write the basic structure for what i want exactly like ^^
then iâve got a basic macro i wrote that is like a âpoor manâs devcardsâ that i use to drop a few examples into an element gallery
and i do the aesthetic stuff there
then i wire it up into the app once iâm happy it works as an element
a simple boolean in a cell like above is not that impressive
gets more interesting with elements that have a lot of nesting, things that pass state to each other and anything that uses datascript đ
as soon as i go around changing things it does
well thatâs what iâm saying is great about hoplon
the friction to writing fine grained tests is super low
itâs almost brainless
also, an âelementâ can be a whole page, which could actually go through a lot of nested things
so i often flush out JS errors that i would have only seen had i actually gone to the page manually
i havenât really had any problems
Ran 104 tests containing 575 assertions.
0 failures, 0 errors.
Elapsed time: 20.038 sec
thatâs the current state of my element tests
the majority of the time, fails are legit, and i donât find myself spending much time on test writing except to fix actual bugs
thatâs the bit that needs the most work
i have to look at âactual devcardsâ đ
(pattern "Stats counter"
"Counts some stat in a very prominent way"
el.stat-counter.dom/counter
[["Zero" 0]
["One" 1]
["One point five" 1.5]
["Two" 2]
["Ten" 10]
["Some big number" (rand-int (.-MAX_SAFE_INTEGER js/Number))]
["Some float" (rand (.-MAX_VALUE js/Number))]
["Padded" 10 5]
["Number bigger than padding" 123456 5]
(let [c (j/cell 0)]
(h/with-interval 1000 (swap! c inc))
["Incrementing padded number" c 5])])
ends up looking like
it just syntax highlights the passed arguments and then applies them to the element function
lots of things i want to look into there...
like, having the syntax highlighted part be a cell, so the arguments update with the elem if its dynamic
some navigation, etc.
but for now it gets the job done well enough đ
devcards apparently has direct integration with the cljs tests
that would be very cool
although, at the same time, iâm not exactly suffering just having a terminal and a browser open at the same time
here you go, a real example, instead of a contrived thing with true/false
(defn blocks
([] (blocks monte-carlo.state/risks))
([risks]
{:pre [(j/cell? risks)]}
(let [colours-risks (j/cell= (zipmap styles.colours/colours risks))]
(h/div
:id "risk-profile"
(h/div :class "risky-business"
(h/for-tpl [[c [t v]] colours-risks]
(el.boxed-number.dom/number t v :colour c)))))))
(deftest ??risk-levels
(let [risks (j/cell [["Mega risk" 1] ["Nuts risky" 2.123] ["Safe" 4000.789]])
el (blocks risks)
el->risks (fn [el]
(vec
(zipmap
(dom.traversal/find-text el ".number-caption")
(dom.traversal/find-text el ".big-number"))))]
(is (dom.traversal/is? el "#risk-profile"))
(is (dom.traversal/contains? el ".risky-business"))
; The numbers should round.
(is (= (el->risks el) [["Mega risk" "1"] ["Nuts risky" "2"] ["Safe" "4001"]]))
; We should be able to dynamically update risks.
; We should be able to change the number of risks.
(reset! risks [["Massive risk" 100] ["BBBig risk" 150]])
(is (= 2 (count (dom.traversal/find el ".number-box"))))
(is (= (el->risks el) [["Massive risk" "100"] ["BBBig risk" "150"]]))))
then i drop in this
(pattern "Risk level indicator"
"Displays options for different risk levels as results of the Monte Carlo simulation on a project."
el.risk-levels.dom/blocks
[["Basic example" (j/cell [["High risk" 10] ["Medium risk" 2000] ["Low risk" 12] ["Very low risk" 334534]])]
["Rounding numbers" (j/cell [["One point five" 1.5] ["Oh point two" 0.2] ["nine nine nine" 999]])]
["More than four" (j/cell [["a" 1] ["b" 2] ["c" 3] ["d" 4] ["e" 5]])]])
and i get this
@dm3 i find the QA/review payoff to easily outweigh the extra upfront overhead đ
haha yeah, if you want a super âlight touchâ test
to just flush out JS errors
i have this
(deftest ??pattern-lib
; Simply loading the index in a test can flush out obvious problems.
(pattern-lib))
at the bottom of my pattern lib
so if any of the examples throw an error, iâll see it
if you were going to implement any tests, iâd recommend this one
i do catch a lot of things there
usually just preventing my examples going stale
@dm3 ok, hereâs my final attempt to present something convincing for the other tests đ
ok, so to do this with minimum friction we need 1) a separate page which displays elements from the pattern library (may allow grouping, collapsing, etc) 2) a way to register elements in the main source tree in the pattern library 3) a test to check the pattern library is rendered without errors
(deftest messages
(let [conn (system-message.state/new-conn)
m (system-message.dom/messages :conn conn)]
(is (dom.traversal/is? m "div.clearfix.system-messages"))
(is (not (dom.traversal/contains? m ".message")))
(system-message.state/+! conn "Basic message" :neutral)
(is (dom.traversal/contains? m ".message[data-vibe=\"neutral\"]"))
(is (= ["Basic message"] (dom.traversal/find-text m ".message div")))
(system-message.state/+! conn
(h/ol (h/li "A problem") (h/li "Another problem"))
:bad)
(is (dom.traversal/contains? m ".message[data-vibe=\"neutral\"]"))
(is (dom.traversal/contains? m ".message[data-vibe=\"bad\"] ol li"))
(-> m (dom.traversal/find "[data-vibe=\"bad\"] .close") first (dom.events/trigger-native! "click"))
(is (dom.traversal/contains? m ".message[data-vibe=\"neutral\"]"))
(is (not (dom.traversal/contains? m "[data-vibe=\"bad\"]")))
(-> m (dom.traversal/find "[data-vibe=\"neutral\"] .close") first (dom.events/trigger-native! "click"))
(is (not (dom.traversal/contains? m ".message")))))
dynamic system messages with good/bad/neutral backed by datascript
well, not âa lot"
maybe 4-5
some elements just seem like theyâd benefit, e.g. ^^
oh, just because i have some simple queries around tracking the message, type and whether a message has been seen (clicked close)
and iâve changed exactly how this works a bunch of times
could probably be a list of hash maps too with some maps/filters
just a matter of taste really...
i preferred
(j/cell= (d/q '[:find ?e ?b ?v :where [?e :message/body ?b]
[?e :message/vibe ?v]
[?e :message/seen false]]
conn)))
and (d/transact! conn [{:db/id id :message/seen true}]))
to a bunch of listy things
anyway, yeah the devcards thing
iâm pretty noob at macros, but i can give you what i did so far if you want?
that would be awesome
it is cool, but maintaining it is really just a distraction from building the main app
i donât have any other tests
for the ui anyway
if i dropped my element tests iâd have nothing đ
nothing? haha
thatâs fair
well anyway, if the actual devcards worked with hoplon that would be great
if there was something analagous that would be great too
but if you can cheaply exercise an element in all boundary conditions and see how it behaves - that'd be helpful
i donât really want to be handrolling my own approach here
itâs very helpful
i literally just found a visual regression on prod while i was talking to you
just skimming the cards
in an element iâd pretty much never review otherwise
the screen that appears when a user drops their internet connection
@dm3 https://gist.github.com/thedavidmeister/9812b815b643dedcff60ec0b4f013e51
i made a gist
I guess the most natural place for keeping such examples/cards would be below/above the actual elements
you can refer to it later when you actually have some time for it
youâll see when you try to use it that thereâs plenty of low hanging fruit for improvements
and the devcards
task could pick the pattern
s out of the sources and construct a separate "gallery" page
that would 100% be best
if i could have a file like
(defelem foo ...)
(deftest ??foo âŚ)
(defpattern #foo âŚ)
or something...
that would be great
could the gallery just be an elem?
iâm currently manually doing the requires
so that would be an improvement already
does this way of specifying classes change in recent version of hoplon? https://gist.github.com/8080e72fbdbeae9627e25d8cb44397a5
it renders this now https://gist.github.com/8080e72fbdbeae9627e25d8cb44397a5
whoops, i mean this http://prntscr.com/dpcqki
@raywillig can't reproduce. Are you using the goog stuff or jquery?
On alpha-17 if you don't use boot-hoplon you need to. If you are on boot-hoplon it should work as always.
if i include boot-hoplon in my build-boot, i get a message that it's in there already
I mean using the hoplon task, sorry about the confusion. So if you use the hoplon task you shouldn't need to include jquery manually.
this is the relevant part of build.boot https://gist.github.com/09773263b6b66ac8b78ead6119a71943
I think reload should come before cljs but I don't see anything else that seems odd.
i feel really stupid, i know this is going to be some simple obvious thing i overlooked
i notice hoplon brings in javelin 3.8.4 but latest is 3.8.5 do you think it pays to explicitly dep the latest javelin since it seems to be cell related?
@raywillig I think so or something on hoplon itself. Can you run boot show -d
on both to see what has changed?
@puzzler an interesting thing about the cell=
macro is that any external reference in the expression can be a cell, including cases like this:
(let [+ (cell -)
a (cell 1)
b (cell 2)]
(cell= (prn :result (+ a b)))
(reset! + *))
:result -1
:result 2
also, an interesting thing about how hoplon handles children is that any child of an element can be a cell
any time a cell is a child of an element, the contents of the cell will be added as children of the parent
when the contents of the cell change those children will be replaced with the new children
(defc items ["one" "two"])
(ul
(li "negative infinity")
(cell= (map li items))
(li "infinity"))
this way ^^ does not have the benefits of the caching and pooling the *-tpl forms provide though
it will create new li
elements every time the formula changes, and it will destroy the existing ones
but you can even do (reset! items nil)
and then (reset! items ["foo" "bar"])
, which is interesting
even though you removed all the items, it knows where to insert them when you later add new items
@micha so the restriction of cells having to be the only child of an element doesn't exist anymore?
you can even have an empty vector in there, which preserves the location of where elements will go if they are added later
Yeah, I remember when loop-tpl could change the order of things when it had siblings. That is much better now.
but that's the only way to get something like this to work properly:
(ul
(li "hello")
(for-tpl [item items] (li :text item))
(li "bye"))
you need something to stay in the place where elements will go when items
is no longer empty
or this case, even
(ul
(for-tpl [item items1] (li :text item))
(for-tpl [item items2] (li :text item)))
that's a dumb one because you'd just combine items1 and items2 with into
in a single for-tpl
Seems like a Hoplon renaissance lately which is amazing. Much simpler than any of the react libs.
i think with that we're approaching the time when i can have a ui kit that i can use for all line of business type application front ends
I still have to try it, I'm working on orthogonal issues on github-client. Pretty happy with the restoring and navigating of app state. Instant bug reproduction hopefully. I sort of want a more explicit re-frame.
was talking with jumblerg about possibly using something like firebase as a place to store ui state, completely separate from whatever backend you might have
like if you're building the ui from components at runtime, at least partially, you will need something like that
@micha: I did that with firebase for the blog, works well!
Essentially components are generated based on db entries
Cells make the perfect "dynamic" system
Where the data is irrelevant
we talked briefly with @thedavidmeister about a devcards-like solution for Hoplon. Is anyone else using something like that during development? Basically you have a pattern library/gallery of components exercised with various inputs running at all times. This is great for development/visual tests/knowledge sharing
I was thinking of a Boot task which would pick out something like a defpatterns
out of source files and construct a separate page with those
and associated data cells, per component instance and possible static cells per component type
when you compose things to make a component you would wire their attributes up to cells in the worksheet
one thing that is needed is a good menagerie of workflow state machines you can use with cells
this makes sense, however this looks like an app-building strategy rather than a component library tool
I guess you can look at it this way, but you sure still have to get from a dropdown to a multi-page form somehow
it's another name for a component/group of components, implying that they belong in a "pattern library" where developers go to look up a UI element they should use for a task
I think youâre talking about something slightly different, but I will still drop this: Components (and libraries) are not applications. Applications have global concerns (logging, exception handling, etc.) that libraries cannot have an opinion about without causing trouble.
Libraries and components are composed in a context they do not/cannot know when they are built. They can either avoid having opinions about that, or they can impose constraints on their consumers.
Applications, on the other hand, know everything. They know every library they are using, even transitively, and can (and often have to) make decisions based on that.
However, I hope, given that I am coming to work on your code, you donât have any applications that donât âhave logging. đ
But I think what youâre talking about is coarse-grained components. I believe applications to be categorically different.
Well, I think there are two things. I donât care very much about which word we use.
But I think components can be composed into other, coarser-grained components. And thatâs super-useful. I just donât think youâre done, no matter how much you do that.
but the thing i was getting at was that there are the same concerns at all levels when it comes to stuff you're interested in with something like devcards
Right, so I can get on board with that statement in the devcards context, because thatâs not what I think of as an application, i.e. something I would ship to an end user.
There is definitely value in composing your way closer to the solution. But âintegrationâ canât be componentized, and that is classically a big part of application development.
Well, thatâs an area where I have less experience, so I will grant the possibility and remain doubtful. But you show me and Iâll be like âHell yeahâ. đ
Yeah, so my joke was a little unfair there. I have been convinced that visualization is a huge and important problem, but thatâs really orthogonal to the problem youâre talking about solviing.
also i think that business UIs can be built with a finite collection of composable widgets
When you say âback endâ, I assume you donât literally mean a remote machine. The weather app does computation in a web worker, and thatâs largely the same thing. And the split has been useful, but weâve known a separation of UI and computation to be valuable for literally decades.
Iâd be interested what selection from a finite set of components I would leverage to do the weather thing, or the carrier landing visualization tool.
Right, so if you constrain the problem enough (and maybe even not very much) then I can agree that thereâs promise. However, I happen to think that visualization is highly under-leveraged right now.
like scaffolding a databasase doesn't give you an application a business user can use to get work done
So, the path this generally leads down is âpaving the cowpathsâ. I.e. you wind up with something that makes easy things easier.
Right. So you make a thing thatâs good at certain classes of complex workflows, and that has huge value for some things, and totally ties your hands for other stuff. Historically.
i'm saying the UI widgets are pretty much figured out by now, that is to say the UX components you need in a business application
Yes, agreed. Rails is good at projecting rectangle-shaped data onto a UI. Super important problem to solve, but ultimately limited.
I think a reasonable async model that allows you to split processing in appropriate ways between remote and local computation is going to be a key enabler. I donât think you can move all of a business process off the local machine and not wind up with something thatâs just as bad.
Yeah, so thatâs always an interesting question: what problem are you trying to solve. đ
basically data in a database and processes that happen in the real world as a result of information in that database
we need to expose that database to our customers so they can manipulate the objects in there that will advise our operations in the real world
i think that's pretty much exactly what the objective is for a line of business application
I am extremely skeptical of a general solution to my read of the problem as stated. I am optimistic that the state of the art can be advanced, however, for an important subset of it. Itâs highly likely Iâm misunderstanding some or all of what youâre saying.
Yeah, this comes back to âapplicationâ. The assemblage of parts often introduces requirements that drive down into the parts themselves. In the general case.
Like, âOh, this library needs me to stand on my head, and that one breaks when I do, even though neither one of them are exactly about gymnastics.â I.e. they are not fully orthogonal.
Sure. Memory. Or CPU for that matter. Only the whole matters, not the individual parts.
I am making a very, very general engineering observation, that assembling things has side effects, and that many thresholds (including abstract things like âaggregate complexityâ) have global thresholds that make hill-climbing impossible.
i think people would have said that about lisp, and especially macros without seeing lisp work
I will readily admit that I am coming from a position of ignorance. Prove me wrong - I will cheer!
my thinking is that at the end of the day you are performing operations on data in a database
with that in mind, you can imagine that the number of ways you can manipulate things in a database is finite
and database manipulation in my hoplon apps boils down to calling functions in some backend api
What, literally? Because things like paging and streaming are not always best (IMO) modeled as function calls.
so when you do (next-page! pmachine-instance)
it will call a function that gets the next page of data from the backend
and components that are wired up to the state of the machine will themselves update if/when the data comes in and the machine state updates
the really interesting thing is that the pagination machine is completely orthogonal to any of the actual UI components