Fork me on GitHub
#hoplon
<
2016-12-23
>
peterromfeld02:12:45

* you might have different middleware/api (we have castra and compojure for some other stuff) * with on-error you have the ability to actually also catch the function call with the passed arguments, not sure how you could do that with a extra middleware

peterromfeld02:12:38

when reporting/logging exceptions you wanna give as much context as possible (i also log all the headers and user info - who/where had error)

dm311:12:52

Love the Panoply project!

thedavidmeister12:12:24

hey, @micha is with-dom supposed to do something when an element is added back to the dom thanks to *-tpl?

micha12:12:45

@thedavidmeister no, it just does something the first time it sees the element in the dom

micha12:12:48

then it's done

thedavidmeister12:12:02

any way to respond to something being added/removed?

micha12:12:30

you could add lifecycles, i guess

micha12:12:42

i haven't seen a case where that is needed though

thedavidmeister12:12:59

just a third party lib

thedavidmeister12:12:09

the auth0 lock widget has show/hide methods

micha12:12:11

just don't take it out of the dom

micha12:12:21

toggle visibility instead

thedavidmeister12:12:34

it’s navigation though

thedavidmeister12:12:44

e.g. logged in vs. logged out

micha12:12:08

i don't follow

micha12:12:23

why can't it stay in the dom all the time

micha12:12:28

and just hide/show

micha12:12:35

instead of taking it in and out

micha12:12:41

of the dom

thedavidmeister12:12:53

i don’t know, auth0 seems to pull it out of the dom when you log in

micha12:12:18

shouldn't it manage its own state, if it's the one pulling itself out of the dom

micha12:12:37

that shouldn't have anything to do with the -tpl business

thedavidmeister12:12:42

but when i log in, then nav around a bit, then logout then navigate back to the start, it’s missing

thedavidmeister12:12:28

well my top level nav/routes is handled with bidi and a case-tpl

thedavidmeister12:12:41

yes it probably should manage its own state

thedavidmeister12:12:55

i’m not blaming hoplon 😛

thedavidmeister12:12:02

just wondering what my options are

micha12:12:28

i would lift it out of the case-tpl probably

micha12:12:48

your application will only have one auth badge or whatever

micha12:12:21

you could modify with-dom to never stop polling

micha12:12:46

it could miss some events though

micha12:12:52

so that's maybe not a good solution

thedavidmeister12:12:56

mmm, polling seems inefficient

thedavidmeister13:12:02

with-dom is pretty tight, like 20ms

micha13:12:09

it doesn't do much though

micha13:12:28

takes it maybe a millisecond or two to do its work

thedavidmeister13:12:11

well actually i currently have 2 different pages with auth0 widget elements on it

thedavidmeister13:12:26

the home page and the “403” (not a real 403)

thedavidmeister13:12:14

i wrapped it up hoplon style so it looks like

thedavidmeister13:12:17

(h/defelem login
  [_ _]
  (let [container (h/div
                   :id auth0.config/login-form-id)
        lock  (js/Auth0Lock. auth0.config.client-id auth0.config.domain)]

   (h/with-dom container
    (j/cell= (.show
              lock
              (auth0.config/lock-options)
              (fn [e p t]
               (when-not e (auth0.api/login! p t))))))

   container))

micha13:12:02

i think i would do away with the case-tpl there

micha13:12:14

and use :toggle on the clauses

thedavidmeister13:12:48

but then i’d have my entire site sitting in the dom

thedavidmeister13:12:38

the case-tpl is like

thedavidmeister13:12:41

(let [r (j/cell= (:handler route.state/location))]
             (h/div
               :id "index"
               (h/case-tpl r
                           :landing (el.landing-page.dom/landing-page)

                           :projects (el.projects.dom/projects)
                           :project (el.project.dom/project)
                           :project-insights (el.project-insights.dom/project-insights)

                           :privacy (el.privacy-policy.dom/privacy-policy)

                           :access-denied (el.access-denied.dom/access-denied)

                           :pattern-lib (el.pattern-lib.dom/pattern-lib)))))))

thedavidmeister13:12:21

that list is just going to get longer

micha13:12:09

usually though the layout is hierarchical

micha13:12:20

with a small number of layouts

micha13:12:37

possibly many different screens inside each top level layout

micha13:12:49

but not usually a lot of different layouts in the application

micha13:12:03

like where headers and footers and whatnot are concerned

micha13:12:12

that's the thing i'd lift out of the case-tpl

thedavidmeister13:12:37

i still don’t think it fixes the problem?

micha13:12:49

then you're never taking the auth thingy out of the dom

thedavidmeister13:12:00

it takes itself out of the dom when you log in

thedavidmeister13:12:06

i think i wasn’t clear about that

micha13:12:32

where does case-tpl come into it?

thedavidmeister13:12:43

that’s just how i did navigation

thedavidmeister13:12:02

so i had a vague idea about “hooking in” to it putting the landing page or whatever back in the dom

thedavidmeister13:12:14

to tell auth0 to get itself back

thedavidmeister13:12:47

i think that (.show ) function should do the trick

thedavidmeister13:12:00

with-dom works the first time, all good

thedavidmeister13:12:24

but then say, you log in, do some stuff in the app, then log out, then navigate back to the landing page

thedavidmeister13:12:35

auth0 removed itself from being visible on that landing page

thedavidmeister13:12:55

whether its a toggle or a case tpl, or however you arrange the heirarchy

thedavidmeister13:12:20

it doesn’t change the fact that the landing page is being re-used, not regenerated like it would be with a full page reload

thedavidmeister13:12:07

so yeah.. there’s just a missing chunk in the page where the login form should be

micha13:12:08

yeah i would do it such that the auth thingy is not nested in a case-tpl

thedavidmeister13:12:21

i don’t think that helps though

micha13:12:39

then it's just like any other html

micha13:12:14

unless this library isn't compatible with single page apps

micha13:12:18

which is possible

thedavidmeister13:12:27

it’s supposed to be

thedavidmeister13:12:33

i mean they explicitly advertise SPAs

micha13:12:37

i actually always refresh the page when the user logs out

micha13:12:03

it's too tricky to ensure that all sensitive state is correctly expunged

micha13:12:32

like imagine someone leaves their workstation unlocked, expecting that logging out was enough

micha13:12:36

things like that

micha13:12:54

also it gets even more tricky when the user can log out and then logs in as a different user

thedavidmeister13:12:58

oh, but you can make a function that returns a cell that clears itself when you aren’t logged in

micha13:12:12

sure but we found that to be a source of bugs

micha13:12:38

our QA people found the bugs, not us

micha13:12:45

because we never tested the weird cases they did

thedavidmeister13:12:50

but what type of bugs?

micha13:12:56

like weird issues with caching

micha13:12:19

whenever you have data cached it's tricky to invalidate that based on login info

micha13:12:34

you can add callbacks to zero out cells and stuff

micha13:12:02

but that was unwieldy

thedavidmeister13:12:32

(let [c (j/cell nil)
      nuke! (reset! c nil)]
  (j/cell= (when-not logged-in? (nuke!)))
  c)

thedavidmeister13:12:38

something like that

thedavidmeister13:12:38

well caches are tough generally

micha13:12:38

you can do that, but you run into issues if the use refreshes

micha13:12:41

for example

micha13:12:00

like they're logged in, but you can't know that until you make a request to the backend

thedavidmeister13:12:08

oh, no i have a JWT in local storage

thedavidmeister13:12:18

i know they’re logged in straight away

thedavidmeister13:12:15

when they load the page it establishes a websocket and fresh state that is relevant comes from the server

thedavidmeister13:12:23

based on the params from bidi

micha13:12:31

interesting

thedavidmeister13:12:28

everything server-y is just datoms from datascript that i store as-is

thedavidmeister13:12:47

or can be recalculated with little overhead

thedavidmeister13:12:00

anyway, refreshing the page on logout is not the worst idea

thedavidmeister13:12:12

it might be the most straightforward way to deal with auth0 at least

micha13:12:41

do you have the server-y part abstracted to the point where you could make a demo of it?

micha13:12:45

sounds interesting

thedavidmeister13:12:22

uuum, it’s pretty simple, i honestly don’t know how far i’ll get before needing a refactor 😛

thedavidmeister13:12:35

but i can show a few snippets

micha13:12:59

ok cool, i need to get ready for work but i'll be back in a bit

thedavidmeister13:12:17

(defn ratom->datom
  "Convert a datom from rethink (not a real datom) to a real datom"
  [r]
  (d/datom  (:e r)
            (keyword (:a r))
            (reader/read-string (:v r))))

(defn datom->ratom
  "Convert a single datom to a hash that is rethink/sente friendly"
  [d]
  { :e (:e d)
    :a (:a d)
    :v (prn-str (:v d))
    :tx (:tx d)
    :added (:added d)})

(defn tx-report:wire!
  "Push datoms from a tx-report down the wire as ratoms"
  ([event tx-report] (tx-report:wire! event tx-report {}))
  ([event tx-report data]
   (sente.wire/send!  {:event event
                       :data (merge {:tx-data (->> tx-report :tx-data (map datom->ratom))}
                                    data)
                       :spinny :saving
                       :success (fn [r] r)})))

thedavidmeister13:12:18

i just scrape datoms being added removed straight from tx reports on datascript

thedavidmeister13:12:30

(defn tx-data->datoms:persist!
  "Persist datoms from transaction data into rethinkdb"
  [conn owner table data]
  {:pre [(:tx-data data) (:project-id data) (:stamp data)]}
  (->
    (r/branch (-> (r/table :projects)
                  (r/get (:project-id data))
                  (r/get-field :owner)
                  (r/eq owner))
              (when-let [datoms (tx-data->datoms (:tx-data data))]
                (-> datoms
                    (r/for-each
                      (r/fn
                        [datom]
                        ; Use the time that data hits the web server, not the database, as
                        ; they may be on opposite sides of the world.
                        (r/insert (r/table table) (r/merge  { :stamp (:stamp data)
                                                              :project-id (:project-id data)}
                                                            datom))))))
              nil)
    (r/run conn)))

thedavidmeister13:12:16

i check the validity of owner from the jwt before that point so it should not be forgeable

thedavidmeister13:12:45

there’s an index on the stamp and tx id, so i just send back the freshest datoms that are relevant for the datascript db that is needed

thedavidmeister13:12:31

long term i’m thinking i might move to a model where you have read only db/state snapshots (which i could get from datascript with a prn-str) and a read/write realtime collaboration thing that doesn’t hit the server at all and peers just send datoms to each other

thedavidmeister13:12:53

@micha reload on logout idea works great, thanks for the help 🙂

thedavidmeister13:12:07

a nice pragmatic one-liner

thedavidmeister13:12:16

i can probably delete some other code too now

thedavidmeister14:12:14

yup, just deleted 20 LOC and have more peace of mind too 🙂

candera14:12:50

So, I have this pattern I’ve started using in my code, but I’m wondering if I’m missing some inbuilt capability or otherwise doing something wrong. What I have is several places in my application where I have to render a group of controls against elements in a collection. That is, I’ve got a collection of compound things, and each one gets its own set of sliders, text boxes, etc. I started out just creating a cell which would react to the entire collection. Something like this:

(formula-of [coll]
  (for [item coll] (div …)))

candera14:12:22

The obvious problem here is that any time anything at all changes about the collection (say, I update :foo of the third item) it re-renders the UI for the entire collection. While that’s kind of okay a lot of the time, it definitely isn’t always okay.

candera14:12:39

I tried using for-tpl, as this seems to be what it’s for, but I ran into trouble. Unfortunately it’s far enough in the past now that I can’t quite remember what the issue was. I believe it was something along the lines of problems with the “reuse DOM elements” aspect. Like, I would delete an item, add a new one in, and the new one would not look like a fresh item, but would look like the old, deleted one.

candera14:12:06

Forgot to mention that adding items to and deleting items from the collection is a key operation.

candera14:12:21

So what I wound up doing is something more like this:

candera14:12:51

(let [indexes (-> coll count range cell=)]
  (formula-of [indexes]
     (for [index indexes
             :let item-c (cell= (get @coll index)]
        (div …)))))

candera14:12:44

So, basically, a cell that reacts to the size of the collection, and inside it, UI that reacts to changes in the items themselves.

candera14:12:10

But that deref there looks weird, and the fact I’m rolling this myself makes me think I’m totally missing something.

thedavidmeister14:12:19

so there is a “gotcha” with tpl that you may have run into

thedavidmeister14:12:55

like, for-tpl should be fine

thedavidmeister14:12:27

but you have to set your data that items are based off to the empty state before deleting it

thedavidmeister14:12:44

not sure if that is relevant

candera14:12:02

Well, it moves policy about what new items should look like into the delete code, doesn’t it?

thedavidmeister14:12:16

well actually, that’s an oversimplification

thedavidmeister14:12:33

because cell value propagation is based on value equality

thedavidmeister14:12:46

if the old and new cells are the same value

thedavidmeister14:12:50

you can get weird behaviour

candera14:12:13

Right, I could see that. My above approach has similar issues.

candera14:12:28

I.e. imagine you do an atomic operation to swap the order of two items.

candera14:12:37

Actually, that might still work.

thedavidmeister14:12:42

so in my case the simple thing to do (this was for form inputs) was just set an empty string before delete

thedavidmeister14:12:56

because the empty string won’t accidentally match anything someone typed

candera14:12:30

Yeah, I have a bunch of potentially complex nested structures as the items of my collection. Some of them are e.g. booleans.

candera14:12:51

But I guess a sentinel like ::none could always be used.

candera14:12:55

Still, gross.

thedavidmeister14:12:02

yeah, well the thing is that items can be complex too

thedavidmeister14:12:09

so you can use like a map or something

candera14:12:34

Yes, now imagine that I want to introduce something like spec. Boom!

thedavidmeister14:12:34

the equality thing is definitely a gotcha though

candera14:12:42

But I’m not doing that, so that’s an invented problem.

thedavidmeister14:12:53

like, entities in datascript really got me thinking

thedavidmeister14:12:02

because two entities are the same if their entity id is the same

thedavidmeister14:12:05

regardless of the values

thedavidmeister14:12:17

so nothing would ever update 😛

candera14:12:43

Huh? Datascript mandates that?

candera14:12:56

Maybe I misunderstood, though.

thedavidmeister14:12:37

i’m not sure what your problem with for-tpl was and it’s tough if you can’t remember

thedavidmeister14:12:49

but pretty much every time i’ve ever had issues with “stale” items

thedavidmeister14:12:05

it was because i didn’t realise an equality check was doing something unexpected somewhere

candera14:12:40

OK, thanks for that perspective. Alternative formulation: what’s wrong with what I’m doing, if anything?

thedavidmeister14:12:02

i think i missed why you have to deref coll?

thedavidmeister14:12:05

other than that, if you’re adding and removing items from the dom in a way that means all the removed items just pile up in memory somewhere, that’s not good

thedavidmeister14:12:04

(cell= (for [x xs] (div x))) and anything that works like that will have this problem

candera14:12:04

The deref was a red herring - sorry.

thedavidmeister14:12:22

i mean, deref is fine, there’s nothing bad about it

thedavidmeister14:12:29

it’s just unusual to need to do it in cell=

thedavidmeister14:12:04

i’d be more worried about potential gc issues if you are trying to re-invent for-tpl

thedavidmeister14:12:38

some of the other guys in this chat can give more fine grained details about the specifics of that

candera14:12:39

Yeah, in my case, the places I’m using it are not going to result in a large amount of garbage, since additions/deletions are user-driven and infrequent, but it’s a fair point.

thedavidmeister14:12:51

oh well i wouldn’t stress then

candera14:12:56

I should go back and try for-tpl is my main takeaway.

thedavidmeister14:12:01

at the end of the day, just profile it with a realistic scenario

thedavidmeister14:12:08

and see if it’s a big deal

thedavidmeister14:12:46

if there’s an actual problem with for-tpl it would be really good to see an issue in github 🙂

candera14:12:58

Actually, my app is already complex enough that I’m having a hard time understanding the profile info. Partly because I’m not a browser guy.

candera14:12:13

And partly because it does a boatload of math.

thedavidmeister14:12:49

do you use the flame chart profiler?

candera14:12:03

I have seen it. Have not yet spent the time to understand what it’s telling me.

thedavidmeister14:12:42

ah, well it’s like, as functions are getting called

thedavidmeister14:12:45

it goes further down

candera14:12:14

Yes, the stack is…a bit deep. 🙂

candera14:12:33

If you have a second I’d love your analysis. Just fire up that page and hit “step forward” one time.

candera14:12:01

The math takes place on four web workers, so the main thread is all UI.

thedavidmeister14:12:23

it’s all minified

candera14:12:31

Oh, right.

candera14:12:50

If you’re willing, I can post a non-minified version.

thedavidmeister14:12:09

well the shape will be about the same

thedavidmeister14:12:17

see, b has lots of sub things

thedavidmeister14:12:23

but it’s probably low level

thedavidmeister14:12:54

this anonymous function looks a bit suss though

thedavidmeister14:12:18

as do these three down here

thedavidmeister14:12:01

this is pretty much how to use the chart 🙂

candera14:12:41

Standard profiler stuff in other words. I’ve already been doing that with the other views, which are more similar to other profilers I’ve used.

candera14:12:47

Nothing obvious is jumping out at me.

dm314:12:49

@jumblerg I'm having a dumb moment - trying out hoplon/ui with the Panoply project and for-tpl complains:

(elem :sh (r 1 1) :bt 6 :a :mid
      (for-tpl [event events]
        (div event)))))
Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.

candera14:12:31

@thedavidmeister Thanks for the nudge on that, though: now that I’m looking at it again the chart is making way more sense. And I like the visualization.

thedavidmeister14:12:05

@candera could you split things out a bit? like, do the hardcore math and updating the UI in separate steps

thedavidmeister14:12:16

a little timeout here and there can really help the browser feel more responsive

thedavidmeister14:12:32

also if this is deterministic, memoize?

dm314:12:54

@jumblerg sorry for the noise - event wasn't a Node 😞 dumb moment resolved

thedavidmeister14:12:19

@dm3 i do that exact thing all the time 😛

jumblerg15:12:32

also, we should really put a disclaimer on that thing too, panoply is very much a wip, and not an example of best practices... yet.

candera15:12:03

@thedavidmeister Possibly could break up the rendering into chunks, although the degree of asynchrony is fairly high already - web workers handle the computation. If I split the UI updates up, it means “tearing”.

candera15:12:41

I have thought about memoization. There’s less opportunity for that in the front end.

candera15:12:11

The flame chart has pointed out that str is a surprising portion of the rendering time, though. Still down in the 5-10% range, though.

thedavidmeister15:12:08

yeah, i mean, that update grid function just looks slow @candera

candera15:12:23

It’s modifying something like 10,000 SVG elements.

jumblerg15:12:29

@dm3: incidentally, if your goal is to render a list of things with some space between them, you might look at the gutter :g attribute instead.

candera15:12:30

Maybe twice that.

dm315:12:05

@jumblerg a gutter on what?

thedavidmeister15:12:14

would it be faster to make a single svg?

candera15:12:19

And, for that reason, doesn’t use cells. That’s all JS interop against the DOM.

candera15:12:45

Sorry, I meant 10,000 elements of an SVG. Like <path>s and whatnot.

thedavidmeister15:12:38

well that’s definitely what is slow here, based on what you posted above

jumblerg15:12:44

@dm3: maybe

(elem :sh (r 1 1) :p 6 :gv 6
  (for-tpl [{:keys [name]} events]
    (elem name)))))

thedavidmeister15:12:00

i do wonder if there’s a more performant way to handle the svg stuff

alandipert15:12:04

@candera are you doing your math in web workers?

thedavidmeister15:12:23

like fewer, more complex paths or something

alandipert15:12:45

i learned recently that web workers have special support for matrix math

jumblerg15:12:52

you should also avoid the lower-level hoplon element ctors, they will break things because the parent, which is responsible for all positioning, cannot manage them.

alandipert15:12:56

you can pass a typed array back and forth without copying

dm315:12:08

@jumblerg thanks! what if I want to lay elements out vertically?

jumblerg15:12:17

layout is specified through a) size :s and b) position :p :g :a. the elem is responsible for its own size, but knows nothing of its position. the parent alone determines the positioning of children.

candera15:12:18

@alandipert Yes. Math is in web workers.

dm315:12:40

:sh (r 1 1) on the child elem

jumblerg15:12:03

(elem :sh 400 :p 6 :gv 6
  (for-tpl [{:keys [name]} events]
    (elem :sh (r 1 1) name)))))

candera15:12:06

@alandipert Yeah, I’m aware of the direct copy stuff. I need to take advantage of that. The transit encode/decode isn’t too bad, but zero would be better.

jumblerg15:12:16

the current version of ui, btw, still has vertical positioning issues. i have this solved, but the solution still lives on my local machine and needs some serious refactoring and merging before we can integrate it with master.

alandipert15:12:16

sounds like its not your current bottleneck anyway

alandipert15:12:46

curious tho, is the math parallelizable? they have special things for web workers pools also

candera15:12:06

Well, I think the program is sophisticated enough at this point there’s no single bottleneck. Decoding is 5%, different bits of rendering are 10% each, etc. etc.

alandipert15:12:15

one demo i saw was convulution kernel w/ 4 web workers in parallel

candera15:12:26

The real win would be run a) the rendering, and b) the math on the GPU via WebGL.

candera15:12:57

The math here is trivially parallelizable due to the algorithmic choices I made. However, the wins stop around 4-6 workers on my machine.

candera15:12:11

But in theory I could use 3600 web workers, one for each grid cell. 🙂

candera15:12:28

WebGL would be a total rewrite.

candera15:12:44

And a move away from Clojure for much of it.

thedavidmeister15:12:58

@candera any chance of using fewer paths that are each more complex?

thedavidmeister15:12:21

i imagine there would be some overhead of each path element

candera15:12:54

@thedavidmeister Yes, sorry - saw that suggestion go by. It’s interesting. So the thing here is those little hockey-stick looking things are wind vectors. There are only about five of them. I reuse them via SVG <use>, and just change the rotation vector.

candera15:12:38

But I suppose I could render a path that would encompass several of them. I suspect it would not be a win, and really suspect it would not be a big win, but I can’t prove that.

candera15:12:08

On a fast machine, I should add, the responsiveness is reasonable. And high frame rates aren’t really a requirement.

thedavidmeister15:12:20

well you could pretty easily whip up some arbitrary svgs

candera15:12:22

Rendering is about 180ms on my machine. Slow but not super painful.

thedavidmeister15:12:33

one using your current technique and one using a big path string

thedavidmeister15:12:48

and see if it helps, without actually refactoring your main app

candera15:12:06

String assembly turns out to be slow, BTW.

candera15:12:51

Performance! A pain the butt since the dawn of computing! 🙂

thedavidmeister15:12:05

ruins all the fun things

candera15:12:31

Of course, the psychotic among us (I count myself) also find it amusing sometimes. 🙂

candera15:12:55

Believe me, I have seriously contemplated writing the WebGL code, since it would absolutely destroy any monkeying around in ClojureScript I could do.

candera15:12:35

But man it would suck to move away from Clojure, as I have other things in mind for the code where it would be nice to run it on the JVM, e.g.

dm315:12:44

@jumblerg I loved reading about hoplon/ui, but writing it is pure joy! No freaking CSS!

dm315:12:15

the CSS indoctrination for apps must end!

alandipert15:12:32

ui is incredible

micha15:12:34

css, you're banished from the land!

jumblerg15:12:15

glad to hear you're having a good experience with it!

jumblerg15:12:45

i'm pretty psyched about the possibilities after we let the last few layout quirks resolved on master.

dm315:12:49

it just does what you think it will, which is never the case with CSS

dm315:12:07

unless you know all of it

jumblerg15:12:33

yeah... the trick was drawing out that box model - coming up with a general thing that works in all cases, instead of a specialized layout and positioning scheme for every corner.

jumblerg15:12:47

the new version will eliminate most of the extra elements though, and only use them when they're necessary. it has a new box model, basically, but one that makes heavy use of calc fns and transformation matrices.

jumblerg16:12:48

it's great to hear feedback like that. sometimes you lose perspective when you're working on a project for a long time: it becomes hard to tell if it is something that works only for you because you know it well or whether it works for other people too.

dm316:12:43

one thing that WILL throw people off from the beginning are the short names for all the options

dm316:12:50

like :s, :p, etc

dm316:12:02

would be good to have the long versions too

jumblerg16:12:05

you're not the first person to suggest this.

jumblerg16:12:20

i'm wary of having hoplon ui codebases where some ppl use the long form keys and others the short, but maybe i need to revisit this.

alandipert16:12:47

maybe you can only have one or the other? maybe you have 2 versions of the lib

jumblerg16:12:49

there's something about the concise syntax that i think makes the layout easier to digest.

alandipert16:12:57

i guess that defeats libraries

jumblerg16:12:13

we can support both trivially

dm316:12:15

it's great to have

dm316:12:21

but it's like a power tool

jumblerg16:12:28

kinda vimlike, sure.

dm316:12:29

e.g. unix shell utils

dm316:12:39

has all the long versions and short ones

jumblerg16:12:11

hm, yeah, you have a point here i guess.

jumblerg16:12:51

to play devil's advocate, there are only few things to remember once you get the pattern.

candera16:12:52

Have you considered using namespaced keywords for the long form, perhaps?

candera16:12:29

It might be interesting to make those normative and then allow some sort of aliasing.

candera16:12:39

Perhaps with canonical aliases.

jumblerg16:12:42

i actually had something like this implemented a while ago, then removed it based on a "do things well one way" heuristic.

jumblerg16:12:53

micha has actually worked something into hoplon core for this

jumblerg16:12:10

that makes the "keys" real functions

candera16:12:29

Yeah, there’s a lot to be said for that, especially when something is going to be pervasive. It’s one thing to have :g somewhere in your code, used exactly once. UI looks like it’s going to be something where you have to buy in to make use of it, so the conventions are a one-time cost amortized over much usage.

jumblerg16:12:51

yeah, exactly

jumblerg16:12:08

on the other hand, if we replaced the keywords with real functions

jumblerg16:12:49

we could have (ns (:require [hoplon.ui.size :refer [s sh sv]])) (elem sh (r 11) ...) etc.

jumblerg16:12:39

if the attributes were split up into granular namespaces, the namespaces might provide more context.

jumblerg16:12:53

and people could use the qualified namespaces if they wanted

jumblerg16:12:34

(elem hoplon.ui.size/sh (r 1 1) ..) i dunno.

jumblerg16:12:23

don't think that approach is necessarily ideal, but lots of possible permutations here.

alandipert16:12:30

that's a pretty great api imo

alandipert16:12:52

at least, i see no immediate downsides. esp. pretty because people can use the var-renaming machinery in ns to write things however they want

jumblerg16:12:04

micha's idea there.

jumblerg16:12:44

and i think the context is another benefit of using functions with actual namespaces instead of keywords.

alandipert16:12:15

keywords, an epicycle?

dm316:12:28

offtopic: what's the best way to pprint a js object and show it verbatim?

alandipert16:12:30

i seem to find a new reason to hate them about once a year

jumblerg16:12:32

better increment the badge lol.

alandipert16:12:48

@dm3 i do (.log js/console <obj>) and chrome prints it all special

dm316:12:56

yeah, I want to show it to the user

jumblerg16:12:00

(.log js/console ...)?

jumblerg16:12:16

hm. what sort of object is user-facing?

alandipert16:12:55

i don't know how it handles non-json

jumblerg16:12:21

there's JSON/stringify.

dm316:12:16

hm, it should be just data so I might get out with pr-str and Fipp

dm316:12:08

what's the catch with scrollable elements?

dm316:12:07

how do I make the whole window scrollable?

micha16:12:23

json.stringify does pretty printing

micha16:12:35

JSON.stringify(thingy, null, 2)

micha16:12:37

for example

micha16:12:59

dies with circular references etc of course

dm316:12:07

@jumblerg first thing I ran into that isn't obvious - how do I get the "default" browser behaviour of having the whole page scrollable? I have the same top-level structure as the Panoply project

dm316:12:28

(window (list (header) (body)))

jumblerg16:12:35

:scroll true

jumblerg16:12:30

scrolling is one of those areas that gets messy on the current master, due to a browser thing called stacking contexts. it is special cased on window.

jumblerg16:12:29

basically, if you set overflow on an element, regardless of whether it is overflow x or overflow y, it changes the stacking context of that element.

jumblerg16:12:46

so changes to x affect y and vice versa. 😕

jumblerg16:12:33

also, you only get the elastic scroll in chrome on body, but not the other elements, which is a bit annoying.

dm316:12:47

ok, now I have this

(elem 
      (let [caused (cell= (:event/caused-by event))]
       (when-tpl (cell= (boolean (seq caused)))
         (elem
           (for-tpl [{:keys [data timestamp]} caused]
             (elem
               (cell= (as-timestamp timestamp))
               (edn data))))))))
I see the for-tpl runs, but nothing gets rendered

dm316:12:33

is there some interplay with when-tpl that I'm missing?

dm317:12:12

when I remove when-tpl and do a :toggle with the same condition - everything is fine

dm317:12:51

ah no, :toggle just displays it all the time

jumblerg17:12:02

the *-tpls should behave the same way they do in hoplon.

jumblerg17:12:00

an elem is just a fancy supernode; hoplon.core can't tell the difference.

jumblerg17:12:18

it sees it the same way as a div.

dm317:12:34

figured that 🙂 yeah, will figure out why it doesn't work

jumblerg17:12:01

i don't see anything obvious, but try eliminating the first child/cell in the innermost elem.

jumblerg17:12:44

there is one bug on one of the many branches, maybe the current master you're using, that doesn't properly handle cells/*-tpls as siblings.

jumblerg17:12:28

i'm going to straighten all this this stuff out over the holidays.

dm317:12:54

now it works, how strange...

dm317:12:56

@micha I always forget - what was the solution to resetting a cell from inside a cell=?

candera17:12:59

My solution has been: don’t use cell= 🙂

candera17:12:37

(defmacro formula-of
  "Emits a form that will produce a cell using the formula over the
  specified input cells. Avoids some of the code-walking problems of
  the Hoplon macros. Cells can be either a vector, in which case the
  cells will be re-bound to their values under the same names within
  `body`, or a map whose keys are binding forms and whose values are
  the cells to bind.

  E.g.
  (formula-of [x y z] (+ x y z))

  (formula-of
    {x-val x-cell
     {:keys [a b]} y-cell}
    (+ x-val a b))"
  [cells & body]
  (if (map? cells)
    `((javelin.core/formula
       (fn ~(-> cells keys vec)
         ~@body))
      ~@(vals cells))
    `((javelin.core/formula
       (fn ~cells
         ~@body))
      ~@cells)))

candera17:12:47

(formula-of {foo-val foo-cell} (reset! foo-cell (f foo-val)))

candera17:12:57

That’s a stupid usage example, but shows the form @dm3.

candera17:12:25

Anyway, Micha probably has a better answer, but that’s what one Hoplon n00b (me) has done.

micha17:12:03

(cell= (when foo (~(partial swap! bar) inc)))

dm317:12:33

yeah, right. Thx!

dm317:12:43

I did a do-watch

jumblerg19:12:16

if anyone has time over the holidays, it would be great we could investigate the impact of using the async/defer attributes on the script tag. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script

jumblerg19:12:57

micha mentioned that he tried them a long time ago, and that there were browser-implementation issues associated with using them at the time. it would be worth investigating again.

jumblerg19:12:49

since most of the perceived latency associated with using a hoplon app is a consequence of parsing the initial javascript, if we could start the process of parsing and even evaluating code while index.html.js is still streaming from the server, we might see a significant improvement in performance.

jamieorc20:12:47

Y’all, I’ve been really digging following all the discussions here over the past few months. I’m not actively developing in Hoplon/CLJS currently, but if plans go well, I will be in the not too distant future. Lots of great developments, not the least of which is UI.

candera20:12:37

Hi @jamieorc. Long time, no see. 🙂

jamieorc20:12:41

Yeah, I’ve been lurking on here for months

jamieorc20:12:41

and I really enjoyed your interviews with @micha and @alandipert, @candera

candera20:12:23

Yeah, those were fun ones. Of course, I have to say that now that I’m their work-slave.