untangled

NEW CHANNEL: #fulcro
2017-03-03T00:53:27.004844Z

Still looking at making this css protocol server-side 🙂 (a bit slow since this is on my free time) Do I read this PR correctly in that it allows extending untangled defui? https://github.com/untangled-web/untangled-client/pull/35 If this is the case, I have a few question about untangled if you don't mind: - does it suport sse completely now? - is there anything that would make untangled unsuited for a "live" dashboard? ie. a dashboard where re-rendering is not triggered by user input, but rathter by data being updated (by a streaming query for instance)?

urbank 2017-03-03T13:07:28.004848Z

I'm looking at untangled-ui for a reference for generic components. Like a multiselect or a calendar. I see the calendar actually has I query, which I found odd, because my first instinct was that generic components would just provide hooks for their actions.

urbank 2017-03-03T13:08:10.004849Z

So that all their props would be determined by some transformation of their parents data

urbank 2017-03-03T13:09:04.004850Z

So a multiselect would get a list of options (e.g.: [{:label :id :selected?}])

urbank 2017-03-03T13:09:21.004851Z

and some callbacks via om/computed

urbank 2017-03-03T13:09:24.004852Z

:on-select

urbank 2017-03-03T13:09:40.004853Z

and :on-select woud be a function which takes a new list

urbank 2017-03-03T13:10:23.004854Z

[{:label "a" :id 1 :selected? false} {:label "b" :id 2 :selected? true}]

urbank 2017-03-03T13:10:52.004855Z

and when "a" is selected on-select gets a new vector

urbank 2017-03-03T13:11:24.004857Z

where [{:label "a" :id 1 :selected? true} {:label "b" :id 2 :selected? true}]

urbank 2017-03-03T13:12:39.004858Z

So this doesn't really require any new data in the app db

urbank 2017-03-03T13:13:28.004859Z

or at least no data whose name (:keyword) is predefined in the query of the generic component

urbank 2017-03-03T13:14:22.004860Z

But I'm sure there's a reason for the opposite approach, so I'd like to be enlightened 🙂

tony.kay 2017-03-03T16:48:43.004861Z

@nha Yes, we have an extensible defui. Not documented yet. Considered experimental (API may improve). It has not been tested server-side. Untangled is very well suited for server push. We even have a websockets networking add-on library.

1
tony.kay 2017-03-03T16:50:44.004862Z

@urbank On Untangled UI: Some components have an active nature, and instead of using component local state (which would make them not appear in the support viewer), they are instead first-class Om components. They come with construction functions and mutations. In cases where they give you data, they will have callbacks. The APIs in untangled-ui should be considered unstable and improving. Not an official release yet. Don't consider any particular thing to be the prototypical example code.

tony.kay 2017-03-03T16:51:14.004863Z

But in general, any active component will have app state, query, ident, mutations, etc.

tony.kay 2017-03-03T16:51:35.004865Z

The exceptions will be things like the image crop tool, which needs local state for performance

urbank 2017-03-03T16:57:34.004866Z

@tony.kay Right, but there's also a middle ground between Om components with queries, idents and mutations, and those with local state, right? Namely those that do not compose into a query, but just get some props from a parent that takes care of deciding which part of the db this component should affect. Like the multiselect I described above. Or should such a multiselect also have a query?

tony.kay 2017-03-03T16:58:11.004867Z

Hm. "should"

tony.kay 2017-03-03T16:58:18.004868Z

that is a can of worms

tony.kay 2017-03-03T16:59:07.004870Z

1. Do you want it to show up in the support viewer so you can see the user's interaction?

urbank 2017-03-03T16:59:11.004871Z

Just read "should" as "is there a list of reasons" 🙂

tony.kay 2017-03-03T17:00:38.004872Z

The other consideration is that local state ends up hiding things you might care about. Take a menu. If you click on a menu, it should open. What should happen if you click elsewhere? Should it disappear? How do you hook that up?

tony.kay 2017-03-03T17:01:26.004873Z

but clicking elsewhere is not the menu's concern...so, what, we install event handlers on the doc that try to deal with closing the menus that are open

tony.kay 2017-03-03T17:01:57.004874Z

ok, now do you do that in menu's code? How do you know it isn't going to infect something else with badness now?

tony.kay 2017-03-03T17:02:46.004875Z

If menu is an Om component with state, then mutations are used to open/close them. The menu calls mutations when it sees interactions directly with itself.

tony.kay 2017-03-03T17:03:41.004876Z

Then, on your top-level div of the app, you can easily install an onClick that transacts a (close-all-menus) mutation. Now everything is still reasonable (you don't have to know implementation detail or internals of menu). You only need to know the name of a mutation

tony.kay 2017-03-03T17:03:52.004877Z

and can even choose what actions close all menus.

tony.kay 2017-03-03T17:04:22.004878Z

Then again, the menu installing event handlers on the doc has worked for us for years. YMMV

tony.kay 2017-03-03T17:05:40.004879Z

but think about all that complexity of event handling, component lifecycle, making sure 3 menus don't install 3 event handlers (or maybe you make the close idempotent so you don't care). Making sure the callbacks are not triggered if the menus are somehow gone. etc etc.

tony.kay 2017-03-03T17:06:08.004880Z

Ask me: bleh. I'd rather have a few mutations I can reason about, and actually visibly SEE the installation of the event handler on my root

tony.kay 2017-03-03T17:06:22.004881Z

cause I typed it, not because some component infected my DOM with it

tony.kay 2017-03-03T17:08:21.004882Z

To me, closing menus on a global click is a global concern (not private to menu)

urbank 2017-03-03T17:11:36.004884Z

Right, and I agree with you on all that. I'd also prefer the global mutations. However, does every component that doesn't directly affect the app-db need to have local state to be useful? In the example of the multiselect, it doesn't have any local state, and it doesn't directly have entries in the app-db

urbank 2017-03-03T17:12:02.004885Z

It's just nested inside some component which has some concept of selecting things

urbank 2017-03-03T17:12:28.004886Z

that component passes some transformation of its props to the multiselect

tony.kay 2017-03-03T17:12:31.004887Z

Yes, I'm on the fence on those as well.

tony.kay 2017-03-03T17:12:43.004889Z

They don't really need local state or their own query. Just props.

tony.kay 2017-03-03T17:13:29.004890Z

I would prefer they be pure render, no local state: (ui-multiselect {:options [:a :b :c] :selected #{:a} :onChange (fn ...)})

tony.kay 2017-03-03T17:14:20.004891Z

that way the parent manages the state

tony.kay 2017-03-03T17:14:37.004892Z

and can do things like deny combinations of selections...and we don't end up with hidden state in the UI

tony.kay 2017-03-03T17:14:44.004893Z

that is out of sync from app state

tony.kay 2017-03-03T17:15:34.004894Z

Something like Calendar: I'd rather have the query, etc. There is a lot of complex stuff to manage, and if there is a bug in Calendar you'd like to see it in app support viewer

urbank 2017-03-03T17:19:59.004895Z

Right, that's my intuition about the multiselect as well. I suppose if the calendar were made in the same way, you'd still see it in the support viewer, right? Just indirectly? Isn't that just a copy of the application for the support that gets sent the list of sequence of mutation that happened on the user's side?

tony.kay 2017-03-03T17:20:26.004897Z

Yeah, but you'd have a much larger data set to manhandle

tony.kay 2017-03-03T17:20:47.004898Z

the point of composable queries is to...compose...why push that off on your parent?

tony.kay 2017-03-03T17:21:20.004899Z

(defui Parent 
   (query [this] [I HAVE TO KNOW ABOUT CALENDAR HERE NO MATTER WHAT])

tony.kay 2017-03-03T17:21:48.004901Z

so what if it is a join or a set of props?

tony.kay 2017-03-03T17:22:40.004903Z

I'd rather think "Oh, I ask for calendar's props here". InitiailAppState or mutations make it simple/easy to put the calendar's initial state in the db

urbank 2017-03-03T17:23:30.004905Z

Hm, right. I hadn't considered it from the 'I HAVE TO KNOW ABOUT CALENDAR HERE NO MATTER WHAT' perspective... good point

tony.kay 2017-03-03T17:23:44.004906Z

yeah, sorry for the caps. Not sure how to bold in code 😉

urbank 2017-03-03T17:23:56.004907Z

not bothered at all 🙂

tony.kay 2017-03-03T17:24:27.004908Z

the parent always needs to know about immediate children. You want that reasoning to be abstract. Composable components/queries/initial state/rendering work out fine

tony.kay 2017-03-03T17:26:11.004910Z

We're used to composing this way...it's just we're used to the information hiding stateful component model. The Om model keeps the composition and abstraction, but throws away the information hiding and statefulness

tony.kay 2017-03-03T17:26:53.004911Z

You're allowed to see what is in Calendar, but you're allowed/encouraged to think of it in the abstract

urbank 2017-03-03T17:28:20.004912Z

Yeah, I'm a huge fan of that model because it tends to turn out that I need some information that I've hidden

urbank 2017-03-03T17:31:24.004913Z

In the case of the multiselect though, I have a feeling (and I need to verify it), that it does work best as stateless and without representation in the db, because the list you're selecting can be something other than a list of values.

urbank 2017-03-03T17:33:21.004914Z

For example, if I have a bunch of 'Instruction entities' in my db that each have some field that holds the day to which a particular instruction pertains.

tony.kay 2017-03-03T17:34:49.004915Z

The component can still be a stateful component, just not THAT state. But I do agree that perhaps you'd rather transform your data on the fly and pass it as props as opposed to transform it into a diff form in the db.

urbank 2017-03-03T17:36:33.004916Z

Yeah, basically. That's a very concise way of putting it!

urbank 2017-03-03T17:37:10.004917Z

I was going to go an a whole ramble, but that sums it up 🙂

gardnervickers 2017-03-03T17:39:14.004918Z

Tracking down what I think to be a bug in Untangled. I just wanted to check my thinking. Should a df/load on a component using the component’s ident replace the entire components db table entry with a load marker, or just put a load marker under :ui/fetch-state?

urbank 2017-03-03T17:39:49.004919Z

@tony.kay Well, I've gotta run. Thanks for the discussion!

tony.kay 2017-03-03T17:40:21.004920Z

@gardnervickers replaces the component.

gardnervickers 2017-03-03T17:40:29.004921Z

Thanks

tony.kay 2017-03-03T17:40:36.004922Z

the fetch state has to be in the query, or you won't see it

tony.kay 2017-03-03T17:41:01.004923Z

you can turn off that behavior with a load option...I think :load-marker

gardnervickers 2017-03-03T17:41:04.004924Z

So the parent would be in charge of lazily-loaded then, so as not to be passing the munged state to ident?

tony.kay 2017-03-03T17:41:14.004925Z

yes

tony.kay 2017-03-03T17:41:28.004926Z

if the component isn't loaded, you should not try to render it 🙂

gardnervickers 2017-03-03T17:41:41.004927Z

Yup makes sense. I’ll put up a PR in a few minutes.

tony.kay 2017-03-03T17:41:46.004928Z

a PR?

gardnervickers 2017-03-03T17:42:22.004929Z

df/load doesn’t behave like that unless you specify a :target.

gardnervickers 2017-03-03T17:42:49.004930Z

From the doc string I take it that using an ident with df/load should make it ignore a :target.

tony.kay 2017-03-03T17:44:12.004931Z

Hm. Not sure ignoring target is necessary

tony.kay 2017-03-03T17:44:40.004932Z

the object should normalize to the ident location, but there could be a good reason for plopping the ident into the graph somewhere.

tony.kay 2017-03-03T17:44:55.004933Z

and avoiding needing a post mutation for that op

gardnervickers 2017-03-03T17:45:42.004934Z

https://github.com/untangled-web/untangled-client/blob/develop/src/untangled/client/impl/data_fetch.cljc#L309-L311 Don’t we need a case here to place the data-ident when a data-field is not specified?

gardnervickers 2017-03-03T17:45:51.004935Z

It works (loading by ident) for df/load-field but not for df/load

tony.kay 2017-03-03T17:54:35.004937Z

sorry....brb

gardnervickers 2017-03-03T17:55:28.004938Z

I agree that putting the ident elsewhere in the graph would be very useful too. :target or no :target, we should still be replacing the data in the ident’s table with a load marker.

gardnervickers 2017-03-03T17:55:30.004939Z

Sure!

tony.kay 2017-03-03T18:01:19.004940Z

OK, some useful info just to make sure we're on the same page.

tony.kay 2017-03-03T18:01:45.004941Z

I think (if I remember right), that a load marker has this structure {:ui/fetch-state { nested data }}

tony.kay 2017-03-03T18:02:06.004942Z

in an early version we just merged this with your entity

tony.kay 2017-03-03T18:02:23.004943Z

but the to-many case made that not work, cause you cannot merge a map with a vector

tony.kay 2017-03-03T18:02:47.004944Z

we toyed with metadata, but Om messes with the metadata, and we didn't want to take the chance on collisions

tony.kay 2017-03-03T18:03:21.004945Z

so, since we were not able to mark the vectors while also keeping them around, we decided replacing them was the right thing.

tony.kay 2017-03-03T18:03:45.004946Z

The load markers are simply a convenience. Nothing says you can't implement them yourself (do a mutation to mark your load, then use a post mutation to remove the marker)

tony.kay 2017-03-03T18:04:08.004948Z

so, if you want to leave the data in place you can accomplish it, but only you know how

tony.kay 2017-03-03T18:04:27.004949Z

now, that said, when targeting an ident, you ARE targeting a map

tony.kay 2017-03-03T18:04:57.004950Z

but that map may or may not already be present, so even if we could merge, you still would not want ident being called on it

tony.kay 2017-03-03T18:05:04.004951Z

in case it wasn't there yet

gardnervickers 2017-03-03T18:05:21.004952Z

Yup, sorry I don’t mean to get focused on the lack of ident there.

tony.kay 2017-03-03T18:05:44.004953Z

no, I was just talking through some of the logic so you understand.

gardnervickers 2017-03-03T18:05:50.004954Z

Ahh gotcha thanks

tony.kay 2017-03-03T18:06:05.004955Z

now let me answer your question 😉

tony.kay 2017-03-03T18:06:57.004956Z

I believe you are right

tony.kay 2017-03-03T18:08:25.004957Z

well, perhaps no

tony.kay 2017-03-03T18:08:42.004958Z

The query should cause normalization, so the table should get filled no matter what

tony.kay 2017-03-03T18:09:09.004959Z

the data-path is about additional locations for idents

tony.kay 2017-03-03T18:09:58.004961Z

might be wrong

tony.kay 2017-03-03T18:10:07.004962Z

if the key is an ident

gardnervickers 2017-03-03T18:12:13.004963Z

I guess this is where I’m confused how the else the load marker is placed in the table entry for an ident https://github.com/untangled-web/untangled-client/blob/develop/src/untangled/client/impl/data_fetch.cljc#L46

tony.kay 2017-03-03T18:12:16.004964Z

oh, no, an ident dissoc would just crash

tony.kay 2017-03-03T18:13:05.004965Z

Oh, ok, I see

tony.kay 2017-03-03T18:13:25.004966Z

I think you are right there...the data path of an item targeted at an ident is just the ident.

tony.kay 2017-03-03T18:15:33.004967Z

Yeah, I don't see how that could hurt anything, and it definitely sounds like the bug you're seeing.

gardnervickers 2017-03-03T18:44:39.004968Z

Thanks for your help, PR #61 works for me now.

tony.kay 2017-03-03T18:44:48.004970Z

cool

tony.kay 2017-03-03T19:01:53.004971Z

@gardnervickers got a failing test

tony.kay 2017-03-03T19:02:55.004972Z

and that is definitely in the wrong order

tony.kay 2017-03-03T19:04:17.004973Z

that would break load-field

tony.kay 2017-03-03T19:04:34.004974Z

cond short-circuits, so the field case is now unreachable

tony.kay 2017-03-03T19:13:38.004975Z

I went ahead and patched it and added a new test. Try 0.8.0-SNAPSHOT

tony.kay 2017-03-03T19:13:40.004976Z

on clojars

tony.kay 2017-03-03T19:14:26.004977Z

I'm testing for regressions using cookbook recipes

gardnervickers 2017-03-03T19:14:42.004978Z

Ah ok thanks

gardnervickers 2017-03-03T19:22:05.004979Z

@tony.kay Was that pushed to github? Not seeing

tony.kay 2017-03-03T19:22:38.004980Z

just did, sorry

tony.kay 2017-03-03T19:22:43.004981Z

was pushed to clojars though

gardnervickers 2017-03-03T19:23:59.004982Z

Does that logic not conflict with this? https://github.com/untangled-web/untangled-client/blob/develop/src/untangled/client/data_fetch.cljc#L68-L69

tony.kay 2017-03-03T19:31:06.004983Z

I'm not seeing why

tony.kay 2017-03-03T19:31:24.004984Z

keyword is just a keyword

tony.kay 2017-03-03T19:31:35.004985Z

oh...shoot. Is the ident always set?

tony.kay 2017-03-03T19:34:10.004986Z

hm. no it isn't, but I'm not sure we've fixed anything.

gardnervickers 2017-03-03T19:34:56.004987Z

I was referring to the part “A custom target will be ignored"

gardnervickers 2017-03-03T19:35:26.004988Z

If we put the check for ident at the end of the cond, then setting a custom target ignores the ident.

tony.kay 2017-03-03T19:36:08.004989Z

sorry, I'm in the middle of a bunch of other stuff. This is not a trivial fix, and I probably should not have even committed a patch without better analysis.

gardnervickers 2017-03-03T19:36:40.004990Z

No worries

tony.kay 2017-03-03T19:37:02.004991Z

load is one of the more complicated areas, any change will need to be regression tested against all full-stack cookbook recipes.

tony.kay 2017-03-03T19:37:32.004992Z

I've verified that my change does not break the load markers recipe, but it does not enable a load marker on a ident-based load

tony.kay 2017-03-03T19:38:28.004993Z

It could be that detecting the ident and putting in the params map at :ident in load will work, but I'll have to comb through it

tony.kay 2017-03-03T19:38:41.004994Z

the internals of this need refactoring 😕

tony.kay 2017-03-03T19:39:38.004995Z

probably deserves separate functions for each use-case, so that each one is clear. They're all tangled up at the moment, which is a bit ironic 😉

gardnervickers 2017-03-03T19:40:27.004996Z

Heh

gardnervickers 2017-03-03T19:40:51.004997Z

No rush, thanks for looking at it. We’ll go back to just using the :target then.

tony.kay 2017-03-03T19:42:00.004998Z

probably better. I'm thinking it prob does not make sense to put a marker in a db table...because of the possible confusion about ident

tony.kay 2017-03-03T19:42:27.004999Z

for example what if you queried the table in a UI component and tried to render all of the things...you would not expect a loading marker, nor would you want to mess with the logic

tony.kay 2017-03-03T19:42:35.005Z

the markers make more sense in fields in the graph

gardnervickers 2017-03-03T19:44:45.005001Z

A data-load for an entity is a pretty app-wide thing though right?

tony.kay 2017-03-03T19:45:13.005002Z

hm. could be argued that way, true

tony.kay 2017-03-03T19:45:29.005003Z

might want to see loading in all the rendered spots.

gardnervickers 2017-03-03T19:46:07.005004Z

It’s more I would rather not have stale data in other spots

gardnervickers 2017-03-03T19:46:23.005005Z

Stale data isn’t a good word for it actually

tony.kay 2017-03-03T19:53:59.005006Z

hm. nothing will be stale

tony.kay 2017-03-03T19:54:18.005007Z

oh you mean the marker only appearing in one location

tony.kay 2017-03-03T19:54:26.005008Z

if you're rendering it more that once on same screen

tony.kay 2017-03-03T19:55:25.005009Z

in that case you could do your own marker. Just a boolean on your object. set it to true, trigger load, include post mutation that sets it to false

tony.kay 2017-03-03T19:55:45.005010Z

easily a reusable pattern/helper function of your own

tony.kay 2017-03-03T19:57:19.005011Z

:ui/is-loading?, (defmutation loading-done ... (assoc-in state-map (conj ident :is-loading?) false), (load ... {:post-mutation 'loading-done :post-mutation-params {:obj ident}})

tony.kay 2017-03-03T19:58:31.005013Z

then just implement your loading UI via that boolean

tony.kay 2017-03-03T19:59:36.005014Z

that's possibly a better general pattern for refresh

tony.kay 2017-03-03T20:04:26.005015Z

I don't think "dumb" load markers should ever appear in tables, because of the ident generation issue

tony.kay 2017-03-03T20:15:34.005016Z

I just pushed a sample to the develop branch in cookbook:

tony.kay 2017-03-03T20:15:43.005018Z

@gardnervickers ^^^

gardnervickers 2017-03-03T20:16:20.005019Z

Oh neat thanks

tony.kay 2017-03-03T20:16:21.005020Z

of course, I'd recommend composing that stuff into a single mutation, so it looks like a clean transact...I should refactor the example 🙂

tony.kay 2017-03-03T20:32:54.005021Z

pushed a slightly cleaner bump on that

gardnervickers 2017-03-03T21:28:21.005022Z

With using the post mutation there, is it possible to trigger a re-render on the component that originally called the mutation?

2017-03-03T23:01:54.005024Z

I find myself needing to change the AST for a remote from CLJS land - anybody have a good example of that? I feel like I've set it up correctly, but it's not working

2017-03-03T23:02:37.005025Z

(In certain situations, I want to tack on an additional parameter to a mutation before it is sent to the server)

2017-03-03T23:07:16.005026Z

Actually, nevermind - I did it correctly - it's just my server-side mutation is not responding the way I thought it should.