Fork me on GitHub
#hoplon
<
2016-12-29
>
mynomoto01:12:20

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.

mynomoto01:12:11

UI needs work but it's usable

puzzler05:12:52

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?

puzzler05:12:49

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?

alandipert05:12:23

@puzzler with-let is in javelin, with-init is in hoplon

alandipert05:12:06

could be a typo in the post: it's with-init! in hoplon/core.clj

alandipert05:12:30

@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

alandipert05:12:56

if my explanations work i can add to hoplon wiki, which is pretty sparse atm

puzzler05:12:07

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?

alandipert05:12:26

i couldn't figure out the button to click to record, so no 😞

alandipert05:12:31

but that would be fun to do again

puzzler05:12:49

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.

alandipert05:12:07

ah, yeah, i'm frequently confused about that myself

alandipert05:12:18

revisiting your questions

alandipert05:12:44

the essential thing in javelin, that cell= converts code into using, is the formula function

alandipert05:12:08

i.e. there isn't anything you can do syntactically inside a cell= that you couldn't do functionally with formula

puzzler05:12:46

Seems like the main think cell= adds is the auto-detecting of cells (and maybe the auto derefing as well).

alandipert05:12:59

it's even dumber, but yeah

alandipert05:12:22

(def x (cell 0))
(def y (cell 1))
(def added ((formula +) x y))

alandipert05:12:33

added there is a formula cell

alandipert05:12:47

equivalent to (def added (cell= (+ x y)))

alandipert05:12:03

so you can imagine how we do the transformation

alandipert05:12:10

at least, naively

alandipert05:12:58

in 2013 tho it was untenable, on firefox, using cljs at the time

alandipert05:12:12

too many objects created. so micha rewrote cell= so that it uses the minimum number of formula calls

alandipert05:12:23

ie you don't need to call formula on a tract of code that's already in a formula body

alandipert05:12:57

so, most of the complexity of cell= is optimization related

alandipert05:12:35

so in the formula function... you see that it intercepts the arguments of the function it wraps

alandipert05:12:55

err sorry, it looks at its own arguments. in this case x and y

alandipert05:12:04

and sees if they are cells. if they are ,it adds watches to them

alandipert05:12:49

now changes to the cell arguments will propagete to + through the formula wrapper

puzzler05:12:29

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.

alandipert05:12:31

the useful invariant, with respect to formulas

alandipert05:12:38

and cells in general

alandipert05:12:55

is that by the time a formula can "see" the arguments its based on, all changes have propagated

alandipert05:12:21

or in other words, it sees a consistent view of its dependencies... cells are updated in dependency order

alandipert05:12:46

this is the real value-add of javelin as opposed to ie. manually adding watches

puzzler05:12:55

Are all cells updated instantly when one change is made, or is there a delay?

alandipert05:12:05

they are all updated synchronously

alandipert05:12:31

so, a swap! on some input cell will block until all the dependent cells have run

alandipert05:12:03

this is a decision point in data flow systems

alandipert05:12:25

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

puzzler05:12:43

I think the synchronous behavior is an advantage over the react approach, where the timing can be very tricky to figure out.

alandipert05:12:52

yes absolutely

alandipert05:12:09

it's also counterintuitive

alandipert05:12:19

micha had the idea originally, and i was not feeling it

alandipert05:12:29

it was weird to program as if everything had a value already

alandipert05:12:53

like the idea that cells have a value at all times, there's no way to delay their initialization

alandipert05:12:11

but ultimately it works great. esp. w/ lisp, and pervasive nil punning

alandipert05:12:41

another thing to recognize about react is that, they're not building on any reasonable sense of equality

alandipert05:12:00

so they couldn't do value-based changes even if they wanted. so they need you to wire up events

alandipert05:12:55

that said i'm not a react expert, i've never really used it

alandipert05:12:06

and it seems like with the various wrappers they can pave over most of the lifecycle stuff

alandipert05:12:42

otoh, javelin doesn't really compare to react, its totally browser agnostic

puzzler05:12:46

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.

alandipert05:12:19

well, thanks 🙂

alandipert05:12:48

there is an interesitng comparison to reagent to be made

alandipert05:12:05

which kind of illuminates what javelin does a little bit also

alandipert05:12:26

afaict the Best Practice in cljs react.js wrapper world is a state management technique called "The Big Atom"

alandipert05:12:10

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

puzzler05:12:28

Yes. I've been coding my hoplon application similarly.

alandipert05:12:44

yeah, totally possible, since cells are glorified atoms

puzzler05:12:01

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.

puzzler05:12:14

But I'm open to learning other approaches.

alandipert05:12:03

there is a parallel here to atoms and stm in clj

alandipert05:12:05

(def big-atom (atom {:checking 1 :savings 2}))
(swap! big-atom (fn [m] (-> m
                            (update :checking inc)
                            (update :savings dec))))
                            

alandipert05:12:27

one clear merit is changes can be transactional

puzzler05:12:55

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.

alandipert05:12:52

depending on what you're building, i can see the advantage of passing queries instead of cells, to the components, when you instantiate them

alandipert05:12:10

queries over a central cell somewhere

alandipert05:12:30

something to keep in mind though is that javelin constitutes a 'secret' big atom

puzzler05:12:31

Or a formula cell containing a query.

alandipert05:12:53

in that ,cells can be anonymous and still dependent on other cells. and can also still be updated transactionally

alandipert05:12:59

(def checking (cell 1))
(def savings (cell 2))
(dosync
  (swap! checking inc)
  (swap! savings dec))

alandipert05:12:32

in that example, dependencies of checking and savings aren't notified until after both the swap!'s have happened

alandipert05:12:49

dosync is a macro in javelin.core.clj

alandipert05:12:59

which we happily co-opt since cljs has no stm

alandipert05:12:47

re: formula cell containing query, :thumbsup:

puzzler05:12:13

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.

puzzler05:12:05

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.

puzzler05:12:06

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.

alandipert05:12:28

i think the way to understand that is by considering ((formula deref) x)

alandipert05:12:41

which is equivalent to (cell= @x)

alandipert05:12:03

we know that formula checks which of its arguments are cells, sets up watches, etc

alandipert05:12:18

and when it's fired, it gets the values from those cell arguments and passes them to the function. in this case deref

alandipert06:12:38

so you can see how, in the function that formula lifts, actual cells never make it into the formula-fied function. only their values

alandipert06:12:59

((formula (fn [y] [@x y])) x) is another way of looking at it

alandipert06:12:31

in that example, the y argument is always = @x

alandipert06:12:06

since the fn captures x wherever it is in teh example, it's a cell inside the function

alandipert06:12:32

and so, the deref is cool

alandipert06:12:43

does that make sense?

puzzler06:12:18

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.

alandipert06:12:56

you're referring to the 2nd example?

puzzler06:12:20

Referring to your statement " in the function that formula lifts, actual cells never make it into the formula-fied function. only their values"

alandipert06:12:40

i see. and i think was not clear

alandipert06:12:01

so cells can definitely be inside functions lifted by formula

alandipert06:12:14

but parameters of lifted functions cannot

alandipert06:12:26

i was trying to show that w/ the 2nd example

alandipert06:12:57

also you're right btw

alandipert06:12:10

in the case of ((formula deref) x), if (def x (cell 123)), it would explode

alandipert06:12:16

since it would try to run (deref 123)

puzzler06:12:09

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

alandipert06:12:52

hm i'm surprised by that too

alandipert06:12:57

i'll try an experiment here, 1 sec

puzzler06:12:59

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

puzzler06:12:21

Or specifically, (cell= (println x)) and (cell= (println @x)).

puzzler06:12:49

Reading through some of the docs, it seemed like maybe the second example was a non-reactive version that evaluates the @x once.

puzzler06:12:01

But this wasn't apparent through my println examples.

alandipert06:12:10

that would need to be [email protected] i think

alandipert06:12:15

i will check out the docs also

puzzler06:12:07

From the docs: (defc d {:x @a}) ;; cell containing the map {:x 42} where a is (cell 42).

puzzler06:12:13

javelin docs.

alandipert06:12:33

so defc is veneer for (def d (cell {:x @a}))

alandipert06:12:48

so in that case the deref happens before the map is installed as the cell value

puzzler06:12:27

Oh, so it is because it isn't a formula cell.

alandipert06:12:56

vs. defc=, which converts (defc= foo (inc x)) into (def foo (cell= (inc x)))

puzzler06:12:31

So what would (defc d {:x a}) do? Wouldn't that yield the exact same thing?

alandipert06:12:01

so that's the same as (def d (cell {:x a}))

alandipert06:12:10

and it wouln't be the same, because in that case a is a cell

alandipert06:12:29

so d is a cell with a key :x that points to a cell

puzzler06:12:51

So defc and cell don't do any auto-derefing of cells in the expression?

alandipert06:12:01

correct, cell is analagous to atom in clj

alandipert06:12:10

ie it's a function that takes a value, no fancy business

puzzler06:12:21

OK, I didn't realize that.

alandipert06:12:33

we should make it clearer

alandipert06:12:42

also it seems like use of defc in the readme isn't helping anyone

puzzler06:12:17

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.

alandipert06:12:24

would it have made more sense if the readme progressed from cell -> formula -> cell=?

alandipert06:12:04

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

puzzler06:12:06

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.

alandipert06:12:40

that's confusing territory

alandipert06:12:25

cell= is not amenable to any kind of 'meta' cell usage

alandipert06:12:52

like, you couldn't use it to implement a spreadsheet

alandipert06:12:43

does (defc= g (+ a ~(inc @a))) make sense to you?

puzzler06:12:45

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.

puzzler06:12:53

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.

puzzler06:12:38

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

alandipert06:12:20

heh, that's a weird one

alandipert06:12:56

thanks for writing up your learnings

alandipert06:12:14

i think it makes sense to do some heavy editing of the javelin readme

alandipert06:12:35

definitely defc and defc= really seem to cloud learning

alandipert06:12:42

and they're totally nonessential

puzzler06:12:49

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.

alandipert06:12:33

that's how we're competitive

alandipert06:12:39

our dom mutations are precise

puzzler06:12:18

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.

puzzler06:12:40

De-magicking that a little bit I think will go a long way to showing off how efficient hoplon is.

alandipert06:12:48

that's great feedback

alandipert06:12:30

i just had the idea to make the comparison in docs to conservative/precise GCs, but that's probably also a bad analogy

alandipert06:12:50

not enough GC nerds out there

alandipert06:12:35

you had questions about html and -tpl btw? anything specific?

puzzler06:12:16

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.

puzzler06:12:40

Or an attribute of some html element, or whatever. Doesn't have to be a div.

alandipert06:12:55

yeah, attributes are good entree because they don't involve -tpl

puzzler06:12:18

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.

puzzler06:12:36

That really gets across that the minimal change is being made.

puzzler06:12:51

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

alandipert06:12:45

it's worth separating the problem of updating attributes from the problem of updating children

puzzler06:12:08

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.

alandipert06:12:25

yeah. i think moving away from the cljs.hl files could help with that

puzzler06:12:49

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.

puzzler06:12:15

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.

alandipert06:12:36

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

alandipert06:12:04

i'm not sure we have any really compelling reasons for someone to use hoplon instead of a react-based thing though

alandipert06:12:20

both things solve the don't-repaint-entire-dom-every-time problem but beyond that the differences are subtle

alandipert06:12:43

or maybe even just a matter of taste

puzzler06:12:47

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.

alandipert07:12:06

that sounds really good

alandipert07:12:13

i just realized that i don't know why it's good because i've never used anything else

puzzler07:12:22

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.

alandipert07:12:52

that is a suspicious aspect, of what i understand about react based things

alandipert07:12:06

the way they encourage, and sometimes require, your dom hierarchy to mirror your data

alandipert07:12:35

specifically what's suspicious is the degree of coupling that implies

puzzler07:12:00

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.

puzzler07:12:59

Yeah, I'm preaching to the choir 🙂

alandipert07:12:47

still, very interesting to hear your perspective

alandipert07:12:57

both as a newcomer to hoplon and as someone with recent reagent experience

alandipert07:12:09

and as someone who rocks at clojure programming

alandipert07:12:23

(instaparse, so good 💯 )

puzzler07:12:02

Thanks. I didn't realize you recognized my slack handle, but yes, that's me.

alandipert07:12:32

i had to click on it. then i recognized from clj mailing list

alandipert07:12:55

i should probably go soon, but can i clear anything up about attrs or -tpl before i do?

puzzler07:12:43

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.

alandipert07:12:05

for attrs, the key things are the do! and on! multimethods in hoplon.core

alandipert07:12:59

i shall go then. thanks for the great conversation! looking forward to hear more feedback and field questions as you have them

puzzler07:12:28

Thanks for providing such a helpful and welcoming slack community, and such useful information. Talk to you later.

dm308:12:12

@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

puzzler09:12:12

@dm3 How far along is Hoplon/UI? github site describes it as experimental.

dm309:12:36

I haven't run into things that are broken yet

dm309:12:44

but then I didn't do very much yet

dm309:12:02

on the other hand it lets me not think about CSS

dm309:12:25

which is the greatest thing ever

dm309:12:08

(CSS and all the display models)

puzzler09:12:35

@dm3 ow complete is the toolkit

puzzler09:12:49

How complete is the toolkit of widgets?

dm309:12:38

I don't think there's much in terms of widgets

dm309:12:03

it's still a work in progress, obviously, but the core is there I think

thedavidmeister12:12:13

@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

thedavidmeister12:12:10

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

thedavidmeister12:12:52

that’s been one of my favourite aspects lately, really made me feel confident refactoring my UI 🙂

dm312:12:41

hmm, I guess this aspect will be obstructed in Hoplon/UI

dm312:12:20

@thedavidmeister what do you find yourself testing in terms of UI? Do you do fine-grained element unit tests?

thedavidmeister13:12:17

@dm3 yes, i stick tests for every element just under the element

thedavidmeister13:12:06

i basically have a cljs test runner watching all my elements

thedavidmeister13:12:16

i’ll write the basic structure for what i want exactly like ^^

thedavidmeister13:12:53

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

thedavidmeister13:12:00

and i do the aesthetic stuff there

thedavidmeister13:12:12

then i wire it up into the app once i’m happy it works as an element

thedavidmeister13:12:28

a simple boolean in a cell like above is not that impressive

thedavidmeister13:12:00

gets more interesting with elements that have a lot of nesting, things that pass state to each other and anything that uses datascript 🙂

dm313:12:16

yeah, do you find that it pays off to write the tests?

thedavidmeister13:12:45

as soon as i go around changing things it does

dm313:12:32

I guess it still has to be selective, but your approach seems low enough friction

thedavidmeister13:12:44

well that’s what i’m saying is great about hoplon

thedavidmeister13:12:50

the friction to writing fine grained tests is super low

thedavidmeister13:12:54

it’s almost brainless

thedavidmeister13:12:43

also, an “element” can be a whole page, which could actually go through a lot of nested things

thedavidmeister13:12:57

so i often flush out JS errors that i would have only seen had i actually gone to the page manually

dm313:12:10

I find I either change the elements too often or tests become too brittle

dm313:12:29

but for widely used basic elements it would make sense

thedavidmeister13:12:51

i haven’t really had any problems

thedavidmeister13:12:55

Ran 104 tests containing 575 assertions.
0 failures, 0 errors.
Elapsed time: 20.038 sec

thedavidmeister13:12:00

that’s the current state of my element tests

thedavidmeister13:12:41

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

dm313:12:41

how does your "devcards"-like macro work?

dm313:12:58

do you have a separate page where you copy the element?

thedavidmeister13:12:14

that’s the bit that needs the most work

thedavidmeister13:12:20

i have to look at “actual devcards” 😛

thedavidmeister13:12:38

(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])])

thedavidmeister13:12:06

ends up looking like

thedavidmeister13:12:11

it just syntax highlights the passed arguments and then applies them to the element function

thedavidmeister13:12:58

lots of things i want to look into there...

thedavidmeister13:12:21

like, having the syntax highlighted part be a cell, so the arguments update with the elem if its dynamic

thedavidmeister13:12:23

some navigation, etc.

thedavidmeister13:12:39

but for now it gets the job done well enough 🙂

thedavidmeister13:12:40

devcards apparently has direct integration with the cljs tests

thedavidmeister13:12:43

that would be very cool

thedavidmeister13:12:00

although, at the same time, i’m not exactly suffering just having a terminal and a browser open at the same time

thedavidmeister13:12:48

here you go, a real example, instead of a contrived thing with true/false

thedavidmeister13:12:52

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

thedavidmeister13:12:39

then i drop in this

thedavidmeister13:12:41

(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]])]])

thedavidmeister13:12:54

@dm3 i find the QA/review payoff to easily outweigh the extra upfront overhead 🙂

dm313:12:34

yeah, the tests I'm still not sure about, but the devcards-thing is awesome! 🙂

dm313:12:49

I should adopt something like that

dm313:12:12

where if devcards produce any JS errors it should fail the test run

thedavidmeister13:12:35

haha yeah, if you want a super “light touch” test

thedavidmeister13:12:41

to just flush out JS errors

dm313:12:00

because I still want to see that the element looks right anyway

thedavidmeister13:12:01

(deftest ??pattern-lib
 ; Simply loading the index in a test can flush out obvious problems.
 (pattern-lib))

thedavidmeister13:12:12

at the bottom of my pattern lib

thedavidmeister13:12:22

so if any of the examples throw an error, i’ll see it

thedavidmeister13:12:00

if you were going to implement any tests, i’d recommend this one

dm313:12:14

yeah, that's what I meant above 🙂

thedavidmeister13:12:40

i do catch a lot of things there

thedavidmeister13:12:47

usually just preventing my examples going stale

thedavidmeister13:12:48

@dm3 ok, here’s my final attempt to present something convincing for the other tests 😛

dm313:12:49

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

thedavidmeister13:12:50

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

thedavidmeister13:12:33

dynamic system messages with good/bad/neutral backed by datascript

dm313:12:25

do you have a lot of separate dbs in the app?

thedavidmeister13:12:40

well, not “a lot"

thedavidmeister14:12:02

some elements just seem like they’d benefit, e.g. ^^

dm314:12:36

I see. And with these messages, why didn't you just put them into a list inside a cell?

thedavidmeister14:12:57

oh, just because i have some simple queries around tracking the message, type and whether a message has been seen (clicked close)

thedavidmeister14:12:18

and i’ve changed exactly how this works a bunch of times

thedavidmeister14:12:03

could probably be a list of hash maps too with some maps/filters

dm314:12:57

yeah, makes sense

thedavidmeister14:12:04

just a matter of taste really...

dm314:12:08

hmm, I'm still coming back to the devcards thing

thedavidmeister14:12:13

(j/cell= (d/q '[:find ?e ?b ?v :where [?e :message/body ?b]
                                        [?e :message/vibe ?v]
                                        [?e :message/seen false]]
                conn)))

thedavidmeister14:12:31

and (d/transact! conn [{:db/id id :message/seen true}]))

thedavidmeister14:12:39

to a bunch of listy things

thedavidmeister14:12:21

anyway, yeah the devcards thing

thedavidmeister14:12:45

i’m pretty noob at macros, but i can give you what i did so far if you want?

dm314:12:36

I won't do anything with it right now

dm314:12:55

but I'd consider starting something like this under Hoplon umbrella

thedavidmeister14:12:15

that would be awesome

dm314:12:17

so that you could add something like (devcards) to the Boot pipeline

dm314:12:39

and have a separate page with the element library

thedavidmeister14:12:47

it is cool, but maintaining it is really just a distraction from building the main app

dm314:12:10

well, one part of it is having the pattern library for other developers

dm314:12:39

the other is testing, as I'm still disillusioned by unit-tests for separate elements

dm314:12:47

but visual tests have to be done anyway

thedavidmeister14:12:04

i don’t have any other tests

thedavidmeister14:12:13

for the ui anyway

thedavidmeister14:12:28

if i dropped my element tests i’d have nothing 😛

dm314:12:36

yeah, that's pretty much what I have now

dm314:12:20

it's internal projects, so users aren't too mad when something doesn't work

thedavidmeister14:12:53

well anyway, if the actual devcards worked with hoplon that would be great

thedavidmeister14:12:00

if there was something analagous that would be great too

dm314:12:09

but if you can cheaply exercise an element in all boundary conditions and see how it behaves - that'd be helpful

thedavidmeister14:12:16

i don’t really want to be handrolling my own approach here

thedavidmeister14:12:29

it’s very helpful

dm314:12:32

I don't think we'd reuse the same devcards project

thedavidmeister14:12:50

i literally just found a visual regression on prod while i was talking to you

thedavidmeister14:12:55

just skimming the cards

thedavidmeister14:12:09

in an element i’d pretty much never review otherwise

thedavidmeister14:12:19

the screen that appears when a user drops their internet connection

dm314:12:29

I guess the most natural place for keeping such examples/cards would be below/above the actual elements

thedavidmeister14:12:38

you can refer to it later when you actually have some time for it

thedavidmeister14:12:31

you’ll see when you try to use it that there’s plenty of low hanging fruit for improvements

dm314:12:37

and the devcards task could pick the patterns out of the sources and construct a separate "gallery" page

thedavidmeister14:12:57

that would 100% be best

thedavidmeister14:12:09

if i could have a file like

thedavidmeister14:12:24

(defelem foo ...)

(deftest ??foo …)

(defpattern #foo …)

thedavidmeister14:12:48

that would be great

dm314:12:11

now I'm thinking if there's a way of not screwing non-Boot users

dm314:12:34

I guess just invoking the Boot task from Lein would be enough

thedavidmeister14:12:46

could the gallery just be an elem?

dm314:12:06

yep, but you still need the requires

dm314:12:33

you need to produce a valid .cljs file at least

thedavidmeister14:12:26

i’m currently manually doing the requires

thedavidmeister14:12:34

so that would be an improvement already

raywillig16:12:34

does this way of specifying classes change in recent version of hoplon? https://gist.github.com/8080e72fbdbeae9627e25d8cb44397a5

mynomoto16:12:32

@raywillig can't reproduce. Are you using the goog stuff or jquery?

mynomoto16:12:45

I'm on alpha-17 with jquery.

raywillig16:12:16

oh do i need to explicitly require jquery in my page?

mynomoto16:12:53

On alpha-17 if you don't use boot-hoplon you need to. If you are on boot-hoplon it should work as always.

raywillig16:12:46

if i include boot-hoplon in my build-boot, i get a message that it's in there already

mynomoto16:12:51

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.

mynomoto16:12:07

boot-hoplon is included on hoplon itself now.

raywillig16:12:12

i definitely am using hoplon task

mynomoto16:12:52

No warnings at all? Neither on terminal or browser?

mynomoto16:12:38

I think reload should come before cljs but I don't see anything else that seems odd.

mynomoto16:12:05

No install from repositories of hoplon or javelin?

raywillig16:12:25

i don't think so, but i can blow away that part of my .m2

raywillig16:12:55

i feel really stupid, i know this is going to be some simple obvious thing i overlooked

mynomoto16:12:25

Can you reproduce on a fresh generated project? lein new hoplon hoplon-test ?

mynomoto16:12:36

I did that and it worked fine on my machine.

raywillig16:12:22

works as expected in the test repo

mynomoto16:12:55

Can you repro on a separate file? Creating another page?

raywillig17:12:53

i can repro on a separate file. so must be my build.boot?

raywillig17:12:02

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?

raywillig17:12:52

@mynomoto i rolled back to hoplon alpha16 and problem is gone

raywillig17:12:14

so maybe some transitive dep got in there?

mynomoto17:12:55

@raywillig I think so or something on hoplon itself. Can you run boot show -d on both to see what has changed?

mynomoto17:12:29

Also which boot version are you running?

mynomoto17:12:16

Also javelin 3.8.4 should be ok.

mynomoto17:12:52

Also boot show -p shows dependencies conflicts and the winner in each case.

micha17:12:50

@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

micha17:12:20

also, an interesting thing about how hoplon handles children is that any child of an element can be a cell

micha17:12:51

in fact, the for-tpl macro for example, and all the *-tpl macros, return a cell

micha17:12:40

any time a cell is a child of an element, the contents of the cell will be added as children of the parent

micha17:12:55

when the contents of the cell change those children will be replaced with the new children

micha17:12:17

it even works when you have something like this

micha17:12:00

(defc items ["one" "two"])

(ul
  (cell= (map li items)))

micha17:12:14

or even this

micha17:12:47

(defc items ["one" "two"])

(ul
  (li "negative infinity")
  (cell= (map li items))
  (li "infinity"))

micha17:12:35

this way ^^ does not have the benefits of the caching and pooling the *-tpl forms provide though

micha17:12:00

it will create new li elements every time the formula changes, and it will destroy the existing ones

micha17:12:44

but you can even do (reset! items nil) and then (reset! items ["foo" "bar"]), which is interesting

micha17:12:20

even though you removed all the items, it knows where to insert them when you later add new items

mynomoto18:12:02

@micha so the restriction of cells having to be the only child of an element doesn't exist anymore?

micha18:12:21

@mynomoto no, that's been fixed for a while now

micha18:12:38

there is a bug with children of the <body> though

mynomoto18:12:33

@puzzler sorry about that then, I was wrong about the cell as only child ⬆️

micha18:12:41

you should be able to use cells as children pretty much anywhere now

micha18:12:07

we fixed that by making a sort of rudimentary virtual dom 🙂

micha18:12:16

each element keeps an atom of children

micha18:12:35

and that's the thing we update when .appendChild() et al are called

micha18:12:06

that lets us create the thing that the DOM is lacking -- a generic container element

micha18:12:15

like the atom is a vector of child elements

micha18:12:33

but you can also have vectors of elements in that vector

micha18:12:46

basically vectors are the generic container element

micha18:12:03

we needed the virtual dom because the actual dom doesn't provide such a thing

micha18:12:29

so you can have a child vector that's like [a b [c d] e]

micha18:12:47

and that will be flattened into the child list of the element itself

micha18:12:11

but that preserves the information about groups of elements

micha18:12:23

like that vector could be the elements of a -tpl thing

micha18:12:46

you can even have an empty vector in there, which preserves the location of where elements will go if they are added later

micha18:12:57

like [a b [] e] if the -tpl has no items

micha18:12:12

when it's flattened that empty vector goes away

mynomoto18:12:55

Yeah, I remember when loop-tpl could change the order of things when it had siblings. That is much better now.

micha18:12:13

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

micha18:12:29

also the -tpls just return a cell now

micha18:12:35

a cell containing a vector

micha18:12:39

the tricky part is when the items is empty

micha18:12:05

you need something to stay in the place where elements will go when items is no longer empty

micha18:12:03

or this case, even

(ul
  (for-tpl [item items1] (li :text item))
  (for-tpl [item items2] (li :text item)))

micha18:12:41

that's a dumb one because you'd just combine items1 and items2 with into in a single for-tpl

mynomoto18:12:21

Seems like a Hoplon renaissance lately which is amazing. Much simpler than any of the react libs.

micha19:12:03

i'm really excited about h/ui

micha19:12:55

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

micha19:12:07

basically the dream unfolding

micha19:12:38

like how you can do almost anything with just clojure data on the backend

mynomoto19:12:45

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.

micha19:12:56

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

micha19:12:23

like maybe the user interface needs its own persistent configuration and state

micha19:12:32

orthogonal from all the other stuff

micha19:12:52

i can imagine that including what you describe

micha19:12:35

like if you're building the ui from components at runtime, at least partially, you will need something like that

flyboarder19:12:21

@micha: I did that with firebase for the blog, works well!

flyboarder19:12:41

Essentially components are generated based on db entries

micha19:12:10

yeah that seems pretty good to me

flyboarder19:12:30

Cells make the perfect "dynamic" system

flyboarder19:12:43

Where the data is irrelevant

dm319:12:00

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

dm319:12:24

would be great to hear opinions on feature set/best approach to doing that

dm319:12:08

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

micha19:12:11

this is the thing i have always wanted to make

micha19:12:25

i imagine something similar to excel in some ways

micha19:12:55

first, everything will be a defelem, or some kind of generic component

micha19:12:12

so to start you'd have one component, perhaps

micha19:12:20

the hoplon/ui elem

micha19:12:34

you can then compose those to define your own components

micha19:12:57

each component you define will have both a visual representation (the "chart")

micha19:12:29

and associated data cells, per component instance and possible static cells per component type

micha19:12:39

that's like the "worksheet"

micha20:12:09

youd be able to declare attributes your component would use to configure itself

micha20:12:24

you'd declare those in the worksheet view

micha20:12:46

when you compose things to make a component you would wire their attributes up to cells in the worksheet

micha20:12:18

that's basically it

micha20:12:51

one thing that is needed is a good menagerie of workflow state machines you can use with cells

micha20:12:35

i think you could program every level of the UI like that

micha20:12:45

like an application is just a component itself

micha20:12:55

composed from other components

micha20:12:07

does that sound like what you were thinking of?

dm320:12:58

@micha was this addressed to me? 🙂

dm320:12:00

this makes sense, however this looks like an app-building strategy rather than a component library tool

micha20:12:18

components are applications, no?

dm320:12:53

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

micha20:12:32

even components have this multi-page type behavior

micha20:12:41

like take the dropdown even

micha20:12:52

it has a workflow and a state machine type thing

micha20:12:04

and it's composed of other components probably

micha20:12:24

it's like a little application itself

dm320:12:14

I'm just confused as to how this relates to the tool that I described

micha20:12:37

maybe i misunderstood

micha20:12:53

i thought you were talking about a live component gallery type thing

micha20:12:09

where you could attach live components to different inputs and whatnot

micha20:12:20

and see how they do

micha20:12:38

is that not what you meant?

dm320:12:50

hm, I didn't think of that

dm320:12:08

rather I didn't get there yet I think 🙂

dm320:12:24

had something more static in my mind

micha20:12:38

what did you mean by "patterns"?

dm320:12:02

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

candera20:12:58

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.

micha20:12:22

@candera why can't classes have static members, for example?

micha20:12:38

that's the global concerns of a component, more or less

candera20:12:47

I don’t believe that to be true.

candera20:12:27

There are lots of cross-cutting things. E.g. metrics reporting.

candera20:12:11

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.

candera20:12:39

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.

micha20:12:59

sure but you can have an application that has no logging or whatever

micha20:12:10

so components are like those

candera20:12:32

However, I hope, given that I am coming to work on your code, you don’t have any applications that don’t ‘have logging. 🙂

micha20:12:44

lol oh ye of little faith

candera20:12:00

More like, “ye of extensive experience”. (kidding)

candera20:12:32

But I think what you’re talking about is coarse-grained components. I believe applications to be categorically different.

micha20:12:47

i guess maybe application is the wrong word perhaps

micha20:12:56

"program" maybe better

candera20:12:09

Well, I think there are two things. I don’t care very much about which word we use.

candera20:12:36

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.

micha20:12:37

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

candera20:12:18

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.

micha20:12:30

i think it's extremely close

micha20:12:47

all the things you mentioned, logging, etc, are side effects attached to cells

candera20:12:01

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.

micha20:12:22

i think it can be, for the user interface

candera20:12:53

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”. 🙂

micha20:12:24

the ultimate goal is to do all application development on the backend really

micha20:12:29

exposing an API

micha20:12:37

this is where most of the work is done

micha20:12:46

the UI is just a way to visualize that

micha20:12:54

and to interact with it in a structured way

candera20:12:57

“just” 🙂

micha20:12:19

it seems crazy now because of all the things we need to do with CSS and so on

micha20:12:35

but i think we're close to solving that problem

candera20:12:06

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.

micha20:12:21

also i think that business UIs can be built with a finite collection of composable widgets

micha20:12:40

combined with a zoo of various state machine type things

micha20:12:44

and cells

candera20:12:01

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.

micha20:12:32

i meant a remote machine yes

micha20:12:39

but by "work" i mean programming

micha20:12:47

not CPU cycles exactly

micha20:12:52

development effort

candera20:12:40

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.

micha20:12:55

the weather visualization widget is like a specialized type of graph in excel

candera20:12:07

Well, “specialized” is a tricky word.

micha20:12:08

you would code that from scratch probably

candera20:12:19

Yes, so there’s the rub.

micha20:12:29

most business processes don't need weather visualization

micha20:12:41

if they did then we'd be able to make the widget and reuse it

micha20:12:54

most of what we need is just CRUD

candera20:12:59

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.

micha20:12:09

but the catch with "just" CRUD is the workflow

candera20:12:17

Right, I see where you’re going now.

micha20:12:44

like scaffolding a databasase doesn't give you an application a business user can use to get work done

micha20:12:59

you need well designed workflows that help them be as efficient as possible

candera20:12:24

So, the path this generally leads down is “paving the cowpaths”. I.e. you wind up with something that makes easy things easier.

candera20:12:33

And some hard things impossible.

micha20:12:46

well not impossible, because you can always fall back to programming

candera20:12:52

That’s Rails. Rails is awesome. Rails is the Microsoft Access of the 2000’s.

micha20:12:06

it's not good for complex workflows though

candera20:12:40

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.

micha20:12:44

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

micha20:12:03

we have studied them for decades, and we know what works

candera20:12:13

So people don’t write custom ones any more?

micha20:12:16

what there is no cowpath for is the business workflow

micha20:12:31

well people do but i don't think it's really so valuable

micha20:12:59

like at adzerk for example

micha20:12:25

the model for the objects our users are manipulating, the ads, the campaigns, etc

micha20:12:33

those have complex relationships to each other

micha20:12:41

and to the things the user is trying to achieve

micha20:12:55

things like rails are really bad at that kind of thing in my experience

micha20:12:21

the contextual interface problem

candera20:12:28

Yes, agreed. Rails is good at projecting rectangle-shaped data onto a UI. Super important problem to solve, but ultimately limited.

micha20:12:37

where the interface is presenting you with the information you need as you need it

micha20:12:48

changing in response to your interaction

micha20:12:53

in real time of course

candera20:12:58

Like I said, Rails is MS Access for the web. 🙂 Not an insult, at all.

candera20:12:57

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.

micha20:12:41

the adzerk app has done this, and it's ok

micha20:12:46

i mean the hoplon version

micha20:12:01

there is no business logic in the client at all

micha20:12:06

only workflows

candera20:12:07

Yeah, so that’s always an interesting question: what problem are you trying to solve. 🙂

micha20:12:26

we have an Enterprise System or whatever

micha20:12:47

basically data in a database and processes that happen in the real world as a result of information in that database

micha20:12:26

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

micha20:12:40

so they can make an ad start serving, whatever

micha20:12:06

and the things they're manipulating have pretty complex relationships to each other

micha20:12:17

so we need to devise ways to visualize those relationships

micha20:12:33

and help the user make the changes they want to make

micha20:12:56

i think that's pretty much exactly what the objective is for a line of business application

candera20:12:16

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.

micha20:12:35

no, that's exactly the point i am making

micha20:12:43

there is no general solution

micha21:12:01

but the constituent parts of a solution are general and known, imho

micha21:12:17

but each business will have its own unique way it composes them

candera21:12:19

I don’t believe in the total orthogonality of the parts.

micha21:12:31

by parts i mean like the concept of "dropdown"

candera21:12:13

Yeah, this comes back to “application”. The assemblage of parts often introduces requirements that drive down into the parts themselves. In the general case.

micha21:12:43

how do you mean "drive down to the parts" exactly?

candera21:12:48

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.

candera21:12:04

Memory is a great example.

micha21:12:15

can you think of an example? or hypothetical case

candera21:12:34

Sure. Memory. Or CPU for that matter. Only the whole matters, not the individual parts.

candera21:12:42

But yes, I am generalizing wildly. 🙂

micha21:12:54

you mean like limitations of the browser platform?

micha21:12:35

because that is a huge issue that i have been thinking about a lot

candera21:12:57

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.

micha21:12:25

i think people would have said that about lisp, and especially macros without seeing lisp work

candera21:12:02

I will readily admit that I am coming from a position of ignorance. Prove me wrong - I will cheer!

micha21:12:25

my thinking is that at the end of the day you are performing operations on data in a database

micha21:12:40

that greatly limits the domain

micha21:12:04

with that in mind, you can imagine that the number of ways you can manipulate things in a database is finite

candera21:12:15

I might restate as “That is an extremely useful constraint to leverage”.

micha21:12:35

yeah that's what defines "line of business" in my mind

micha21:12:10

and database manipulation in my hoplon apps boils down to calling functions in some backend api

candera21:12:52

What, literally? Because things like paging and streaming are not always best (IMO) modeled as function calls.

micha21:12:12

ah exactly

micha21:12:24

they're modeled in our current application as state machines

micha21:12:37

which have associated methods that change the state

micha21:12:45

and which expose their state as javelin cells

micha21:12:04

we have a single pagination machine we use for pretty much everything

micha21:12:18

like even a dropdown in the adzerk app needs to be paginated

micha21:12:28

because users can create a bajiooion of anything with the api

micha21:12:19

so when you do (next-page! pmachine-instance) it will call a function that gets the next page of data from the backend

micha21:12:59

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

micha21:12:22

you might trigger next-page! when the user clicks a button

micha21:12:35

or when they scroll, whatever

micha21:12:57

or you might change the query as the user types

micha21:12:05

that's actually the part that something like rails cannot really do

micha21:12:15

these machines that maintain their own state in the client

micha21:12:27

and encapsulate that state effectively

micha21:12:00

the really interesting thing is that the pagination machine is completely orthogonal to any of the actual UI components

micha21:12:19

it interacts with them via data in cells and the functions it exposes

micha21:12:00

so you can use the pagination machine to set the items in the dropdown list of a typeahead form input widget

micha21:12:12

or as the data for a data table

micha21:12:26

we have other similar machine type things, like there is one that is used to talk to the backend