so for example, we have :workers, that just shows all the workers, but you could have an :exceptional-workers view that showed only the top x% of workers
you would query at Root for {:exceptional-workers (om/get-query WorkerRow)} and then pass that to ui-exceptional-workers
you then have to filter that list in ExceptionalWorkers/render, but you can do whatever you need to in that method
im slightly doubting myself, but i think the alternative is not really provided to you in untangled. ie in just om next you would write a parameterized read method that would filter the workers list as it was de-normalizing but with untangled weโve made the decision that that is not worth the cost in having to understand and maintain parsing code (which can get quite complicated) instead you just massage the data as you need to in your components
ah here: http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.C_App_Database
id have to think a bit more, whereas @tony.kay could just tell you, but there are more techiniques for having and updating those views
i think what untangled pushes you towards is that when you load your workers from the server, you would have a post-mutation that would create another view of that data based on your filter/logic
but all you are doing is creating pointers (idents) to the same table of data
anyway, wall of text alert!
I like walls of text. Lots of information ๐
before you go thinking im right, i would read the devguide until @tony.kay confirms or adjusts my statements
Right, I will. What I understand of the framework, I like what I see. There's going to be a pretty big rewrite over here, and I've been agonizing for a while on what to use
this seems very promising
whatโs the rewrite from?
A horrible mess of global listeners via jquery. And a bunch of plugins which are really expensive to load. It's quite painful, but they made it in a hurry (for some reason)
For the main part of the app, there's one big HTML file and one big JS file.
are you debating between languages, or cljs libraries?
The HTML is basically this giant empty form. The javascript then inits a punch of plugins. Adding dynamic elements is done by cloning the initial ones on the page (emptying them if needed)
Anyway, we're mostly settled on clojurescript at this point. Not completely discounting other languages though.
sounds nasty, like anything would be better but i would say that you will find subtler and more pervasive problems in any other libraries
personally i would go with untangled or re-frame
re-frame mainly because its a well established pattern and used by a lot of cljs devs (as far as i know)
but if you are ok with some minor turbulence and a work in progress, untangled is much more geared towards helping you create a solid enterprise level application
i dont think iโve seen a better fleshed out full stack framework in clj(s)
is your app just client side right now?
Well, after fighting with the current mess, I'm not too worried about some turbulence here and there. The main thing is that the core concept is solid
youโll get plenty of that (solid foundations) with @tony.kayโs stewardship
make the back end support GAE pls ๐
@qqq what does that mean?
iโve written a more modular way to build an untangled-server, but what exactly does supporting GAE mean?
I'e like the backend to be based on datastore + easy to deploy to GAE, rather than based on datomic
client side -- probably nothing needs to be changed
thereโs no hard dependency on datomic on the backend
yeah, but there's no trivial one-line support for GAE either (I'm working on this myself right now)
right now, I'm trying ot get the untangled-todomvc to run on GAE by changing the server side
Hm... so I'm not quite sure what you mean by "is my app just client side right now". If I'm only testing out the clientside of untangled? If so, yes. Unfortunately I have much more say over the clientside than I do over the server. So I'm not very confident if I'll be able to push that one through ๐
it seems you are more talking about supporting whatever persistence layer GAE uses with the way weโve structured the server it should just be a component that you put in the parser-injections and use in your reads and mutations
I more meant is there a server side you are trying to port over to something else
do you need something else that isnโt currently supported by untangled? I dont think that we have the man power to directly write that layer for you, but I can try to help get what you might need from untangled-server into our schedule
Oh that. Well, the server code is not nearly as much of a mess, so I think it's pretty likely going to stay as is. At least there's not going to be a huge overhaul.
gotcha, well you should be able to make that work by tweaking the untangled-client :networking, iโm assuming its a REST server?
ie: /workers
/workers/:id
/workers/:id/edit
@adambros: maybe it's best if I just post questions as I run into problems porting it to GAE
I don't think I currently know enough to even know what to ask for
well, do you have a link to something that might give an overview on how to use gae?
yeah, I have a clojure gae setup working fine
I also have the untangled-todomvc client+server working fine; I just need to mash the two code bases togehter
Yeah, It's a REST server. Transitioning to a bit of a cleaner API, so it should be good
so I started writing my app in gae, using clj on server side + reframe/cljs on client side: -- then I realized "hmm, this om/next thing makes sense since communicating between server/client is a bitch" -- then I stumbled across untangeld and it seems like you guys solved precisely the communication problem I was dealing with
well so om.next (& therefore untangled) just talk to an /api
route by sending it data/queries
so reads are [:key {:join [:a :b]}], and mutations are similar [('launch-rockets! {:id 23})]
so if the cleaner api is still rest, you will have to write (or find) some translator layer but iirc some people here might have more to say on that
yeah untangled does a lot on top of om.next to help you make an application
well let me know if you have a specific, or even abstract, question about untangled regarding the server interaction
this is very very very minor; but I think untagngled docs can be improved if, for eacy devguide devcard, thre was a link to a github repo *.clj sfile
Yes, I thought it would be something like that. I would look into not using REST, and just going with this full-stack, but I'm not really in charge of that. I do need something flexible and well thought-out on the client. And there aren't that many requests. Most of the complexity, as far as the client-side is concerned, is in this input form.
@qqq sounds nice, feel free to file an issue on it
Well, it's getting really late over here. Looking forward to exploring this further tomorrow.
nearly 2am kinda late, europe?
Yup ๐
look out for a message from tony, heโll likely write something about my earlier comments
oh and g'nite
Great, will do! Good night, and thanks again for the help/discussion! (telling someone about the mess that is the current code I'm maintaining was cathartic as well ๐ )
@tony.kay I can confirm that I can fix my problem by having the post-mutations transacted, instead of used just for their :action
whether thatโs a good idea ๐
1) I already have a basic untangled/devcard/defcard setup working. 2) I have inserted the code
(defui ^:once Counter
static uc/InitialAppState
(uc/initial-state [this {:keys [id start]
:or {id 1 start 1}
:as params}] {:counter/id id :counter/n start})
static om/IQuery
(query [this] [:counter/id :counter/n])
static om/Ident
(ident [this props] [:counter/by-id (:counter/id props)])
Object
(render [this]
(let [{:keys [counter/id counter/n]} (om/props this)
onClick (om/get-computed this :onClick)]
(dom/div #js {:className "counter"}
(dom/span #js {:className "counter-label"}
(str "Current count for counter " id ": "))
(dom/span #js {:className "counter-value"} n)
(dom/button #js {:onClick #(onClick id)} "Increment")))))
3) Now my question is -- how do I create a minimal devcard which shows the above? (In the devguide, we ahve to create something like 5 more components before we can display any of them). I just want to interact with this one component NOW.does untangled provide CRDTs (or things similar) or is this something users have to make their own decisions on
what are the 5 more components you are talking about?
having not heard of those before in any untangled conversation, I would say itโs up to you
not sure if itโs related, but one of our teams are using hazelcast...
At minute 11:30 of https://www.youtube.com/watch?v=IeIyQDg9gBc it talks about "merging data"
the only method I know of is: https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type -- (which Phoenix Presence also uses)
not sure i understand, what are you wanting to replicate, and across what?
this sounds like something your db/persistance layer uses/implements
can we discuss this over DM ? I understand you don't want to clustter the main channe, but this threading UI is really weird to use on my laptop
http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.A_Quick_Tour -- CounterPanel, Root, SampleApp, "5" may have been an overestimate
iโd rather keep it in the channel
so the example you gave you should be able to just plop in a card
you dont really need a panel or sampleapp
okay, so I've done that
I now ahve the following problem
are "id" and "n" supposed to be numbers or Objects or what?
when I do
(. js/console log (str "id: " id))
the js console prints it out as:id: [object Object]
so it seems like somehow I'm not getting the value
weird, not sure about that, maybe try prn?
that did not fix it: however, commenting out the: static uc/InitialAppState static om/IQuery static/omIdent lines did fix it I must ahve some type of error in one of those lines
ah its not
static uc/InitialAppState (uc/initial-state
but static uc/InitialAppState (initial-state ...
you dont namespace the method names
wait, so there's a bug in http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.A_Quick_Tour ?
maybe? does that fix it or was i wrong?
err, currently trying to fix shy the Increment button throws an error
okay, changing uc/initial-state to initial-state fixed it
these two lines:
(let [onClick (om/get-computed this :onClick)] ...)
^^ how that does line cause onClick to increment?That is just getting a reference to a prop and calling it onClick
. This is where it is using it as an onClick handler: (dom/button #js {:onClick #(onClick id)} "Incrementโ)
right, so where is the #(+ 1 %) or inc ?
where does the actual "inrementing" occur?
It is assuming that the parent component is passing in a function for that prop that will actually do the incrementing.
ah
In CounterPanel
: (map #(ui-counter (om/computed % {:onClick click-callback})) counters)
oh right, i see it now
click-callback (fn [id] (om/transact! this `[(counter/inc ....
Yep
The actual increment function implementation is defined near the beginning: (defn increment-counter [counter] (update counter :counter/n inc))
sure, let's kill threading though; the window is too narrow on my mbp
you can view it full screen by clicking on All Threads above your starred channels
where do I get untangled-devguide.tutmacros from ?
Its in the repo for the devguide: https://github.com/untangled-web/untangled-devguide/blob/master/src/devguide/untangled_devguide/tutmacros.clj Just a single macro and its specific to devcards.
(:require [....
[untangled.client.data-fetch :as df]])
^^ this causes a a cljs analyzer errorwoot, got QuickTour example working
What does "static" mean in the context of defui ?
devcards / untangled / :history / :insert-data is amazing
in (dom/... ) how do I generate a checkbox?
I tried (dom/input {:type "checkbox"})
@qqq I think you have to say (dom/input #js {:type "checkbox"}) so that the dom attributes is a javascript object
@urbank: ah right, thanks!js could really give me a more helpful debug msg than a 20-frame deep react stackframe
btw, the untangled devguide exercises are awesome
whoever wrote that guide deserves to have their salary doubled
@adambros: is that useable with om/untangle? I actually like the reframe style hiccup more than om/untangle's dom thingly,
definitely
afaik the only reason we dont use it in untangled is tony is against exporting extraneous dependencies
imo we should just all be using it, but iโm not in charge ๐
you also like hiccup style [:ul [:li ...] [:li ...}} more than om style (dom/ul nil (dom/li nil ...) (dom/li nil ...) ?
yup
you know, the biggest obstacle with me working through the guide right now is precisely the (dom/ul, dom/span, dom/li) stuff
what is a good guide on using sablano / hiccup / reframe style gui in untangle?
this would drastically incease my productivity
i dont think there is one, but it should be very easy to use
iirc when i tried using it you just have to wrap each render method with sablono.core/html
I'm being greedy here now
Is ther a way to use https://github.com/Day8/re-com with untangled?
that'd complete my stack
see this section of the devguide http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.M10_Advanced_UI
it covers using react components in untangled, should at the very least point you in the right direction
i'm surprised untangled isn't more well known
I suspect part of the problem is that it requires cljs/clj on client/server, and at that point, many ppl have rolled their own framework
how do I solve this line:
in particular, how do I pass the "onDelete" into the people object ?
@qqq Do you know about om/computed ? I think you have to call om/computed on the props that you pass to the child component. (your-child-component (om/computed props {:onDelete ...})) and use om/get-computed on the props of the child component within its render method.
'props' in the call to om/computed being the props that you're sending the component anyway
@urbank: yeah, looking at another exampke, looks like I have to use om/computed
let me try some stuff out, it looks like thing that is hairy to explain, but easy to get to work
I think it's quite elegant, because it keeps the callbacks and such separate from the actual data the component needs to display.
I think I should write 10k untangled loc before responding to that ๐
I can assure you taht my current untangled code is NOT elegant by any means, but it's mostly due to my n00biness
alright, I finished devguide-B onto devguide-C now
reading the first paragraph -- does untangled purposely choose it's own format, and thus datascript can't be used as the db?
hmm, so the entire untangled method is to use tables to denormalize and to make sharing explicit ?
Haha, yeah I'm just learning it as well. I just like the idea that there's a box you put stuff like :onDelete in that separate from data such as :person/name . I think it's mostly an aesthetic preference ๐
Don't really feel qualified to answer the last two question though...
opinion is fine, at this hour, my choices are : someone else's intuition vs nothing
Ok, though someone will hopefully correct me if I mislead you. I think untangled has a preset format because it enables a lot of cool automatizations. So I don't think it's meant to be used with datascript. Perhaps it's possible to hack it together somehow, but that might defeat the purpose
http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.C_App_Database is saying something similar
but I don't understand it well enough yet
this is so exciting though; it's like learning programming for the first time
it's like going from qbasic to scheme, when you realize you have been doing all this wrong all this time
Yeah, it is exciting!
okay, I get it now it's definitely NOT datascript compatible
all data in untangled db is of the following format, it has a "2-level key" and the value
the key is of the format [keyword id], where keyword has to be a keyword, and id can be anything
so the [keyword id] "pair" serves as the "key" (called an "ident")
the db is a bunch of (ident, value) pairs, though it's stored as a 2-level table indexed first on keyword, then on id
then there's somet function which takes this "tree" and converts it into a "db" (if you have shared components)
this solves the "diamond" problem mentioned ealier in the devguide
The one thing I'm confused by is if every [:keyword id] in the db is necessarily an [Ident id] value.
I don't understand you rquestion.
type ident = vector of length 2, where first elem = keyword, second elem = anything
right. so [:keyword id] is in ident. But what if the db stores something which is a [:keyword some-value], but it shouldn't be treated as an Ident
I mean, I'm not sure if that would even come up, but still
I think the answer is: don't do that
the way I think about this is as follows:
keyword = maps to the name of the table
id = maps to the "key" in the table
this an ident = [keyword id] = saying in table XYZ, item ABC
^^ this may be entirely wrong but it's my urrent mental model
this also maps amazingly well to gae/datastore, so I'm very happy
Yeah, I think that's the right way to look at it.. and "don't do that" probably is the answer with regards to storing stuff that just looks like idents ๐
you can store stuff that looks like idents
they get resolved in the normalization process
for example, section "Everything in Tables"
{ :lists/by-category { :friends { :list/id :friends :people [ [:people/by-id 1] [:people/by-id 2] ] }
:enemies { :list/id :enemies :people [ [:people/by-id 5] [:people/by-id 9] ] }}
:people/by-id { 1 { :db/id 1 :person/name "Joe" :person/mate [:people/by-id 2]}
2 { :db/id 2 :person/name "Sally" :person/mate [:people/by-id 1]} ... }}
i'm happy to hash this out, because I think we each understand 80% of what's going on
and this gives us a chance to debug each other's understanding
Yes, I think that's correct. I was talking about storing stuff that looks like an Ident but isn't, and so you don't want it to get resolved
hmm; I haven't considered that
so your point is, suppose we ahve an indent [:programmer "Rich-Hickey"]
but then suppose we wanted to store the data tuple [:programmer "Rich-Hickey"] -- and how do we tell it to not resolve it?
Yeah, basically. Not sure when you'd want to do that. I'm more wondering whether untangled would try to treat it as an ident
this is a great question
I don't think it's answered in Section C exercises; when I get to it in seciton D, E, F, or G, I'll let you know ๐
Ok, thanks! ๐
actually, I lied
if you look at section 3, exercise 2, it does the ident thing
basically, the trick appears to be "you can resolve ident manually"
and thus, when you get [:programmer "Rich Hickey"] you can decide whether you want it as a tuple or to look it up, @urbank
Ah, that makes a lot of sense!
want to help me solve exercise 3?
I'm not quite sure what's going on, but I think this om/db->tree is related to what we're talking about
Ok, i'll open the page
http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.C_App_Database
bottom of that links to the db exercise, 1 & 2 are trivial, 3 I still don't understand yet
right, I have it
I think https://github.com/omcljs/om/wiki/Documentation-(om.next)#db-tree is what we're supposed to understand
Aha, so that's basically the thing that resolves the queries.
okay, I got it working
the answer is:
(def c-ex3-uidb
{:main-panel {:toolbar ...} {:canvas ...}})
where the two ... are filled by (1) wirintg in 0, (2) looking at the error on why it doesn't match nd (3) copying/pasting the answer it's supposed to beAha, so you had to make a db that given that query would return that tree.
something like that, I'm not exactly sure, the docs confuse me
like wtf does
db->tree
(om.next/db->tree query some-data app-state-db)
Given a query expression, some data in the default database format, some application state data in the default database format, denormalize it. This will replace all ident link nodes with their actual data recursively. This is useful in parse in order to avoid manually joining in nested relationships.
mean, and why is ex3-uidb passed twice ?btw, playing around with om/db->tree I found out that you can use [*] inside a query to get all the attributes of that object.
alright, I need to sleep
this was helpful; thanks for the discussion
I think that an om thing... db->tree needs it twice, probably they can be different. not sure. Ok goodnight! It was helpful to me to ๐
anyone want to work through http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.E_UI_Queries_and_State_Exercises together? I'm currently on Exercise 1
@qqq @urbank On om/computed
: The issue is that Om can re-render a component by running the query for the component's data. When it does that, it calls render without going from root (through the parent)...but the query cannot figure out what the parent wants as callbacks (computed stuff of the parent).
By using computed
, you're giving Om a chance to cache the last computed values from the last render that went through parent
without computed it would work on the first render, but localized refresh would break
and urbank is right: the db->tree
API covers more advanced cases. The two args are in the same format, but need not be the same database. Most uses will just pass the complete app state both times.
@tony.kay: 1) thanks for creating untangled 2) thanks for occasionally dropping down from mount sinai to answer basic level questions ๐
one thing I don't get about queries:
is the root query supposed to get EVERYTHING or is it supposed to leave some unresolved for the sub-parts to load on demand?
concretely, in http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.E_UI_Queries_and_State_Exercises here, I am conflicted over the following: should the root element load the mate of the person too -- or should the root leave that unresolved as a [:db/id NUMBER], and ahve the sub component load it on demand?
So, the root query asks for everything. How much you want in RAM right now and what you want to demand load is totally up to you
and the UI only cares in the sense of triggering the load via some interaction you define
@tony.kay: suppose I have a root node that has 12 tabs; where only one of the 12 tabs is visible at any given time in the untangled model, do I load the data for all 12 tabs at the root, or only the visible one?
@qqq: In that case you could use a union query and only one of the 12 would be loaded at any one time.
what @cjmurphy said...but remember that there are 2 concerns: What the UI query processes from RAM -> UI, and what you've loaded from a server. The cookbook shows an example of a tab triggering a network load.
all up to you
the cool think is you can hide the remote trigger for a load from the UI logic. The mutation to navigate just composes in a load
the UI just says "go to tab 5". the mutation looks in RAM and notices..."Oh, don't have that yet...load it"
it is just what you'd hope for: complete flexibility
with the capability of not tying model/logic/app behavior up in the UI itself
here's another extreme case:
I have a two panel setup. Left panel = 1 MB of data to render. Right panel = 12 tabs, each of which takes 100kb of data to render. Now, suppose I'm loading the right lazily. Then, when I switch a tab in the right, is the left reloaded (a 1MB data cost) -- or, if it's not -- how does it know the data is not stale?
cache invalidation is your problem (tm) ๐
reloading is completely under your control.
it never ever happens unless you tell it to
sorry, let me rephrase my confusion
If "root node is in charge of loading ALL data", then when I switch a tab, wouldn't it automatically relaod the left panel 1MB also ?
no
so, there is a diff between loading (a server interaction) and providing the current client-side app state to the UI
just a min...
the UI query is about moving data from RAM onto the screen. Union queries, mentioned before, allow you to limit what goes from RAM towards the UI to one branch at a time.
The query on the UI is not directly sent to a server, ever
you decide what to ask the server for as part of mutations
The fact that you can use a portion of the UI query to solve that is part of the data-driven story
but don't mistake the total UI query for a server query
the UI concern (union queries) is purely a rendering efficiency concern.
The server interaction concern is addressed by the Untangled data-fetch namespace functions, like load
, which you compose into mutations
Using a Person UI component in a load just joins together what that UI component wants with what the server can provide
Hm... so if I'm not missing something, I think something goes awry with queries if I use [org.clojure/clojurescript "1.9.473"] with untangled, whereas it works fine with [org.clojure/clojurescript "1.9.229"]
if I do (om/get-query Component) with 473 I get nil back, but I get the actual query with 229
So I suppose this looks like an om issue
(ns app.core
(:require
app.mutations ; remember to load your add-on mutations
[untangled.client.core :as uc]
[app.ui :as ui]
yahoo.intl-messageformat-with-locales ; if using i18n
[om.next :as om]))
(defonce app (atom (uc/new-untangled-client :initial-state { :some-data 42 })))
(reset! app (core/mount @app ui/Root "app"))
http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.F_Untangled_Client
where does core/mount come from?that should be uc/mount
(defui ^:once Root
static om/IQuery
(query [this] [:ui/react-key ...])
Object
(render [this]
(let [{:keys [ui/react-key ...]} (om/props this)]
(dom/div #js { :key react-key } ...))))
what does the ... mean ?
this is in http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.F_Untangled_DevEnvIn this case I'm just saying "whatever else"
it is pseudocode
it isn't meant to be literal code
sorry bout that. ...
in a proper query would mean recursion
In that case I'm just trying to focus your attention on react-key
and nothing else
@tony.kay: got it; thanks!
welcome
@urbank the version of cljs and Om can be closely tied (e.g. latest + latest is often the case). Om, being a project with close ties to the maintainers of cljs, tends to push the envelope
@tony.kay : is there any reason idents are exactly 2 levels of a (keyword, id) pair? it seems that if it was "just one level", it'd basically be a virtual pointer and at more than 2 levels, I'm not sure what it is but why was 2 the right number?
because the database format:
{ :table { id { entity }}}
(get-in db [:table id])
<--- thus an ident...the identity of a table entryeverything normalizes into tables. Their ident is their location
so the db really is meant to be A GROUP OF TABLES, where each TABLE is a bunch of (id, entity) pairs ?
thus the 2 levels?
yep...flat. The structure is made by idents linking things together