Fork me on GitHub
#untangled
<
2016-04-14
>
tony.kay00:04:37

Just uploaded my talk from a meetup in Portland to YouTube. Still processing at the moment, but should be visible soon: https://www.youtube.com/watch?v=IeIyQDg9gBc&amp;feature=youtu.be

jimmy01:04:07

nice, thanks for posting simple_smile

jimmy02:04:27

how do untangled deal with staled data in remote fetch. For example I have the old data in :project/search, now I do another search ( fetch ) with different params, and of course I don't want to fetch the old one in the state, I want to check if the params is the same with the one before, then load it and at the same time we do the remote fetch, otherwise just return nil and remote fetch.

cjmurphy03:04:01

Just from that talk. set-query! and IQueryParams are not used in UnTangled - is that correct?

jimmy03:04:07

yeah it is

jimmy03:04:12

I am watching the talk as well

cjmurphy03:04:30

I wonder if Navis have found any use for subquery??

jagare13:04:43

@tony.kay: Great video! Thanks! 😀

anmonteiro13:04:25

@tony.kay: nice Untangled T-shirt

tony.kay15:04:36

@anmonteiro: Thanks! Our UI guy made the logo...custom T-shirts are so easy to get these days. Notice the sticker on my laptop? @jagare: Thanks, hope it was helpful. @nxqd: stale data is detected by you, of course. You have to write the logic. If you send a load (remote query) you can include parameters that let the server detect staleness. Too many possibilities to be very specific, but one example: You run a query with some top-level key that is different from your stale data. If you get something back use a post-mutation to move the new stuff into place. If you don't...well, don't. If you're asking about read after write (e.g. did a mutation, and want to do a follow-on read: use the built-in (app/load) mutation (see the implementation of load-data). @cjmurphy: No, that is incorrect. Query parameters (which are known as dynamic queries) are usable. Using parameters in the query syntax that need parser interpretation is what we don't support (since you don't write a parser). [(:prop {:trim true})] is not ok. If you want to say [:a :b ?other-prop-to-choose] and use a query parameter to fill that in, well, that's fine. Of course, sending parameters to the server is not only supported, but encouraged. You'll often want to do that, and you do write a parser on the server.

cjmurphy16:04:43

Some of us tried to re-write the Auto-complete example by moving the component away from the root level. It uses dynamic query (`set-query` and IQueryParams) for the search String that gets sent to the endpoint. The only way to do it was to have a complicated recursive read that had a special pathway for the remote case. With Untangled not having a parser on the client our solution is ruled out. I'm thinking that solving the same problem with Untangled might not call for the use of dynamic queries in the first place...

uwo17:04:17

I have a very form heavy UI with many auto-complete/combobox widgets that require dynamic queries and ui-local state. I see that untangled supports both, but I’m curious having only written a little bit of om-next, is it going to be difficult to create generic components like this? For instance, a combobox requires a lot of ui local state to describe whether it’s open, what is selected, etc. When you create a generic combobox component you’ve got to make sure that each instance has a separate set of ui local state. I realize dynamic queries are unrelated to ui local state, and I ask about them only because it was difficult to compose dynamic queries up to the root in om.next.

tony.kay17:04:03

@cjmurphy: I'd have to think through it to be sure...so, the auto-complete bits are essentially: - Grab some characters in the input field (de-bounced collection) - After some amount of typing, send off a query to the server for search results - When search results arrive, put them in place in the database so UI sees them - Write the UI to render properly In Untangled, none of that requires dynamic queries (or component local state for that matter)

tony.kay17:04:38

- Gathering the characters is a mutation that gathers into the local app state, and sets/resets a timeout - The timeout can trigger a load-data with a post-mutation. The post-mutation can rewrite the search result into the UI portion of the database, and also update :ui attributes - The various other states (combo box open) can be done with :ui attributes, which puts them in the app state and makes the support viewer usable - Rendering should be a pure function with no local state...a given input state shows a very specific rendering.

tony.kay17:04:25

I would suggest that your state-of-the-widget be something that is nicely denormalized...e.g. no :ui attribute if you can derive what the UI should do from the data itself. A concrete example: "all checked" is not something you store as a boolean anywhere for a list of checked things...instead you derive it from the items themselves. Thus, you state cannot possibly be wrong.

tony.kay17:04:05

(let [all-checked (every checked? items)] ...)

tony.kay17:04:30

Also, don't use the Om default of local state for form widgets.....pin the state of a form element to declared data

tony.kay17:04:43

that way the UI cannot be out-of-sync with app state

uwo17:04:31

yeah, I stay away from component local state for that reason

tony.kay17:04:33

This also has the advantage that you can unit-test your logic around your UI rendering without actually using the UI

tony.kay17:04:46

if the data is right, your UI is right

uwo17:04:45

however i still need a generic way to, say, indicate that this combobox is open and this item is selected or hovered, and not another combobox elsewhere. Do have a recommendation on how to achieve that

uwo17:04:23

or does every instance of such a widget need its own defui

tony.kay17:04:40

The box queries for :ui/open and updates it with built-in mutation: (m/toggle! this :ui/open) The items do similar...just add in :ui prefixed attributes

tony.kay17:04:51

(m/set-value! ...) for strings on ui attributes

tony.kay17:04:37

the :ui prefix ensures those attrs never appear in server interactions if you use those UI queries on a load-data

tony.kay17:04:02

Yes, they need queries...so they need a defui

tony.kay17:04:28

And remember that it is perfectly fine to use defui JUST to make queries (no render)

uwo17:04:42

oh? I hadn’t thought about that

tony.kay17:04:48

(defui ServerQuery static om/IQuery (query [this] ...))

tony.kay17:04:06

You'll want Idents as well, to get normalization

tony.kay17:04:13

That is a standard acceptable Om practice

uwo17:04:14

I was about to ask, then, if it’s not possible to create a generic Combobox/autocomplete that get’s used in different places across that app

uwo17:04:38

such a thing is possible simply by not defining the render?

tony.kay17:04:50

certainly...just make a "constructor" method that sets up the app state, and pepper calls to that about in the initial app state (or as result of a mutation to put one up)

tony.kay17:04:15

oh....you mean re-use the state of a single on in multiple places? Yeah, that is ok too

tony.kay17:04:22

as long as they aren't on screen at same time

tony.kay17:04:32

just use a link query, and put the state at the top of the database

uwo17:04:38

they are on screen at the same time. many autocompletes in the same screen

uwo17:04:00

they all have the same basic structure, but query different things

tony.kay17:04:01

`[ {[:combo-box _] (om/get-query ComboBox)} ]

tony.kay17:04:08

ok, so the other answer

tony.kay17:04:38

multiple ones...just make a constructor that makes the correct app state, use that in initial app state or at mutation that initializes a form

tony.kay17:04:03

you could also give the combo boxes global identities, and put them in a top-level initial app state table:

tony.kay17:04:37

(def initial-app-state { :combobox { :search-box (cb/make-combo-box) :email-box (cb/make-combo-box) ...} })

tony.kay17:04:05

then the query can join on idents: (defui ... (query [this] [{ [:combobox :email-box] (om/get-query ComboBox)}]))

uwo17:04:21

yeah, that sounds nice. Just so I’m clear, when you say constructor is that the function resulting from calling om/factory on the class?

tony.kay17:04:33

of course then the ComboBox Ident function returns [:combo-box (:boxid this)]

tony.kay17:04:55

no, I mean a constructor to be a function that returns a map of what state you need to track

tony.kay17:04:15

(defn make-combo-box [id otherthings] {:boxid id ...})

tony.kay17:04:26

just convenience for clarity when generating app state

tony.kay17:04:35

instead of hand-coding the stupid map every time

tony.kay17:04:56

also gives you a true "component" feel....the state is defined with the defui via this constructor

tony.kay17:04:19

You can think "I put a combo box here in the app state, so then I can query it there, and render it there"

tony.kay17:04:42

well, render it anywhere if it is in a table, since you can join on idents

uwo17:04:13

well, thanks. that’s a good amount for me to get my head around. I’m still not clear on how idents work, so I’ll try to get something like [ {[:combo-box _] (om/get-query ComboBox)} ] working. Do you know of any example projects that use a similar approach?

tony.kay17:04:57

I clarified the defs above

tony.kay17:04:14

All an Ident is, is a location in an app state database table

cjmurphy17:04:15

So you construct initial app state already normalized, since you are using functions to do it?

tony.kay17:04:33

[:tbl 1] is used just like a get-in paramter against your app state

tony.kay17:04:13

so a join: {[:a 1] [:x :y]} means "get :x and :y from the object I see with (get-in [:a 1] app-state)

tony.kay17:04:37

Om happens to also know how to take a tree and turn it into tables (reversing this operation for incoming tree data)

tony.kay17:04:23

The declaration of Ident on the ui component (which is also where you get the queries) just defines what database tables hold the data for what UI components

tony.kay17:04:38

you make the table names and IDs up...the table names are required to be keywords

tony.kay17:04:19

@cjmurphy: You can do it either way, if your UI matches up....I often find hand normalized app database is easier to deal with. In our production apps, that's mostly what we do.

tony.kay17:04:26

you can also mix and match!

uwo17:04:53

ah, I think see how using a link works nicely for what would be component local state for a generic component

tony.kay17:04:55

@cjmurphy: just use merge (or maybe deep-merge) on hand-normalized combined with a tree you're running through tree->db...it's all just data simple_smile

tony.kay17:04:09

@uwo Right...but even that instance of the component (data) could be rendered in multiple places...not very useful on screen at same time, but nice for reusing state in different dialogs, for example...just query for the same ident in two different dialog UIs.

uwo17:04:38

ah, gotcha

tony.kay17:04:30

@uwo That also gets you "persistence"...e.g. the multiple on-screen uses remember the last thing you did simple_smile (since they share the data state)

tony.kay17:04:40

which can be desirable

tony.kay17:04:38

imagine you're always asking a similar question they might answer the same next time you ask it

tony.kay17:04:46

but it is in a diff place in the UI

cjmurphy17:04:32

Hand normalizing - maybe easier to have a dedicated app that does that for you, giving you the thumbs up when you've got it right.

tony.kay17:04:46

@cjmurphy: Like I said...use tree->db for the parts that match the UI, and hand normalize anything that might not normalize right...e.g. union queries are a bit problem...anything that isn't currently on the screen won't auto-normalize.

tony.kay17:04:49

so, initial app state can either be generated by multiple bits of state that you combine applying tree->db, or just hand normalize it....which in practice is really pretty simple.

cjmurphy17:04:53

Oh thanks - I'm used to everything being able to normalize.

tony.kay17:04:05

yeah, in practice...it doesn't simple_smile

tony.kay17:04:50

Unions cut out huge swaths of "things not on the screen". A tree can only contain one version of a branch at a time

tony.kay17:04:10

meaning you cannot even represent "tabs" via a union in a tree

tony.kay17:04:50

Also, you might want to encode on UI bit that represents a pop-up, and use a union to populate it...again, not going to be possible to give an initial state with auto-normalize

cjmurphy17:04:59

So union is a good way to handle tabs..

tony.kay17:04:08

That's how I do it. See untangled-demo

tony.kay17:04:24

Also makes it possible to have a hot-loaded (from server) tab

cjmurphy17:04:37

Yes cool, looking forward to reading it...

tony.kay17:04:50

You see the Clojure PDX talk I posted earlier?

tony.kay17:04:15

That little demo program I show with the hot-loaded comments is what I'm referring to...it is like 150 lines of code

tony.kay17:04:21

very digestable, and full-stack

cjmurphy17:04:35

Yes - and that's on github?

tony.kay17:04:47

yep...might be under awkay instead of untangled

tony.kay17:04:20

OK....gotta get on the road to Seattle...headed to clj west. I'll be wearing an Untangled T-shirt. Anyone: feel free to say "Hi". I'm there to promote simple_smile

uwo17:04:45

thanks again.

currentoor18:04:12

@tony.kay: I noticed in todo-mvc you used local component state to track the value of an input field. https://github.com/untangled-web/untangled-todomvc/blob/master/src%2Fclient%2Funtangled_todomvc%2Fui.cljs#L62 Is that just because the code was adapted from React todo-mvc or do you recommend not tracking input field content in the global app state?

ethangracer18:04:12

@currentoor: I wrote that piece of the app, I did it that way because I didn’t want to persist edited text to the app state

ethangracer18:04:29

if you hit the escape key, you need to restore the item’s original text

ethangracer18:04:57

I could have also saved edits to the text in a key like :ui/edited-text, using the pattern tony just described

ethangracer18:04:23

basically, I just didn’t want to overwrite the existing todo item text until the user hit enter

currentoor18:04:51

@ethangracer: cool thanks. I think I'd prefer to store most things in app-state but in the past I've had flicker issues with input text fields and state tracking.

ethangracer18:04:44

huh, don’t think I’ve tried using app-state instead of component local state. the downside of using app state is having to write the post-mutation to clear out the ui keywords you don’t want anymore. though i guess there’s no harm in leaving them there either. not sure about the flicker

wilkerlucio23:04:06

hello, how can I set the :id-key from the reconciler into an Untangled app?

ethangracer23:04:38

@wilkerlucio: not understanding your question, are you referring to untangled’s ui/react-key? or a config option in om’s reconciler? I don’t see any config options in the om code for id-key.

ethangracer23:04:07

nvm, just found it

ethangracer23:04:24

I don’t think we currently have an option to override reconciler configuration

ethangracer23:04:29

we probably should though