Very cool. And probably useful for what I'm working on right now. Typo: "(defn on-changed...)" => "(on-change...)". Actually, I like the name "on-change" or "on-changes".
Cheers, Jamie On Apr 22, 2015, at 10:38 AM, Colin Yates <[email protected]> wrote: > A cursive glance at this makes me want to marry you (if you don't mind > me bringing my current wife and 4 kids?). > > Thanks Mike. > > On 22 April 2015 at 15:28, Mike Thompson <[email protected]> wrote: >> On Wednesday, April 22, 2015 at 9:02:04 AM UTC+10, Mike Thompson wrote: >>> On Tuesday, April 21, 2015 at 11:43:12 PM UTC+10, Colin Yates wrote: >>>> Yes, that is a valid reduction. Specifically my register-handler, >>>> which only has access to db needs to know the result of f. >>>> >>>> The general principle of having my register-sub delegating to a defn >>>> which is called from the register-handler is causing the pain because >>>> there is actually a hierarchy of subscriptions going on here, so the >>>> value of f might actually be resolving a chain of subscriptions. For a >>>> simplified example: >>>> >>>> (register-sub :reference-data/locations) >>>> (register-sub :reference-data/active-locations .. (reaction (subscribe >>>> [:reference-data/locations)))) >>>> (register-sub :page-1/location .. (reaction (if (following-defaults? >>>> (-> db ...) @(subscribe [:reference-data/active-locations)))) >>>> >>>> the value of :page-1/location is of interest. >>>> >>>> >>>> On 21 April 2015 at 14:27, Mike Thompson <[email protected]> wrote: >>>>> On Tuesday, April 21, 2015 at 10:58:31 PM UTC+10, Colin Yates wrote: >>>>>> Hi Mike - yeah, reading through I wasn't very clear. Let me try again >>>>>> with a more fleshed out example: >>>>>> >>>>>> On the server there is a hierarchy, each node in that hierarchy may >>>>>> contain some meta data for example: >>>>>> :id - the unique id of the node >>>>>> :type - indicating some semantics about that particular node >>>>>> :current? - indicating whether it is still actively used or not >>>>>> >>>>>> Imagine there are 5 nodes and node 4, which is a leaf is not :current?. >>>>>> >>>>>> On the client I have a number of projections of this data: >>>>>> - an "active" tree which prunes out all nodes that aren't :current?, >>>>>> so there are 4 nodes >>>>>> - an "everything" tree which shows everything, e.g. all 5 nodes >>>>>> >>>>>> In addition, every node in each tree is selected and there are >>>>>> multiple instances of these trees. >>>>>> >>>>>> On the client (on a reporting page for example, where one of these >>>>>> trees are in the filter) I need to know which nodes the user has been >>>>>> selected. I have a handler, which in response to some event (either >>>>>> the server indicating new data is available or the user changing some >>>>>> data) which needs to know which nodes have been selected. >>>>>> >>>>>> If the user hasn't selected anything then the 'selected nodes' should >>>>>> be the default set (i.e. all of the nodes). As soon as the user >>>>>> changes the selection (by deselecting a node in the first instance) >>>>>> the set of selected nodes is now every node that is selected (e.g. >>>>>> every node apart from the one they just selected) and that instance of >>>>>> the tree is no longer tracking the defaults. >>>>>> >>>>>> The server is free to send new config data at any point in time. When >>>>>> this happens, the default set should be updated. The non-default set >>>>>> that the user has changed should also be consolidated as well, but >>>>>> that is different. >>>>>> >>>>>> Lets say my app state looks like: >>>>>> >>>>>> {:page-1 {:some-active-tree {:selected-ids [] :tracking-default? true} >>>>>> :another-active-tree {:selected-ids [] :tracking-default? >>>>>> true} >>>>>> :some-all-tree {:selected-ids [] :tracking-default? true}} >>>>>> >>>>>> The user hasn't done anything so the selected-ids should be the >>>>>> default sets (4 ids for :some-active-tree and :another-active-tree and >>>>>> 5 ids for :some-all-tree). If the user were now to deselect node 2 in >>>>>> :another-active-tree then app-state looks like: >>>>>> >>>>>> {:page-1 {:some-active-tree {:selected-ids [] :tracking-default? true} >>>>>> :another-active-tree {:selected-ids [0 1 3] >>>>>> :tracking-default? false} >>>>>> :some-other-tree {:selected-ids [] :tracking-default? >>>>>> true}} >>>>>> >>>>>> If they deselect node 3 in some-other-tree: >>>>>> >>>>>> {:page-1 {:some-active-tree {:selected-ids [] :tracking-default? true} >>>>>> :another-active-tree {:selected-ids [0 1 3] >>>>>> :tracking-default? false} >>>>>> :some-other-tree {:selected-ids [0 1 2 4] >>>>>> :tracking-default? false}} >>>>>> >>>>>> Should the server now update the config hierarchy changing node 4 back >>>>>> to :current? and adding another node then at the very least the >>>>>> 'selected nodes' for :some-active-tree should contain the ids of all 6 >>>>>> nodes. :another-active-tree and :some-other-tree should also be >>>>>> informed but they might not be updated depending upon the selections >>>>>> (it gets more complicated...). >>>>>> >>>>>> At this point it is clear that one solution is to record a delta from >>>>>> the defaults, but that only works because we are talking about >>>>>> booleans; there are other non-boolean use-cases unfortunately. >>>>>> >>>>>> Another solution is to store the sets of defaults in app-state itself >>>>>> rather than have it be a subscription and then overtime it changes >>>>>> update the affected parts of app-state (:some-tree and >>>>>> :some-other-tree in this example). >>>>>> >>>>>> This would be much easier if it was just dealing with rendering data, >>>>>> in which case subscriptions are a perfect fit, but the set of data >>>>>> needs to be sent back to the server periodically in a handler, and >>>>>> handlers can't see subscriptions. >>>>>> >>>>>> To be frank, if anyone is still reading, my experience tells me that >>>>>> if the problem is this hard to explain and requires this much >>>>>> explanation then _I_ haven't understood it properly :), so I think I >>>>>> need some more hammock time is in order. >>>>>> >>>>>> Thanks for anybody who hasn't lost the will to live yet ... :). >>>>>> >>>>>> On 21 April 2015 at 13:08, Mike Thompson <[email protected]> >>>>>> wrote: >>>>>>> On Tuesday, April 21, 2015 at 4:52:05 AM UTC+10, Colin Yates wrote: >>>>>>>> Hi, >>>>>>>> >>>>>>>> This is somewhat reframe specific, but how do people handle >>>>>>>> default-values that can change? My specific use-case is that I have a >>>>>>>> tree which can be expanded and collapsed. By default the tree should >>>>>>>> be expanded to a certain level, however, as soon as the user manually >>>>>>>> expands or collapses a node they should no longer follow the default. >>>>>>>> The data the tree is displaying can change, meaning the defaults can >>>>>>>> change over time. >>>>>>>> >>>>>>>> I can't simply define the defaults on startup because the defaults >>>>>>>> will change over time. >>>>>>>> >>>>>>>> It can't be a subscription because the values need to be available in >>>>>>>> the handler. >>>>>>>> >>>>>>>> My current thinking is that the app-db state has a 'follow-defaults?' >>>>>>>> which is true by default but is set to false when the user explicitly >>>>>>>> changes the state (e.g. by expanding or collapsing). When the >>>>>>>> underlying hierarchy changes from the server, propagate that change to >>>>>>>> all of the parts of the app-state that are interested. >>>>>>>> >>>>>>>> To be explicit, imagine I have the following template for tree: >>>>>>>> {:expanded-ids [] :follow-defaults? true}. There are 6 instances of >>>>>>>> this template in the app-db (i.e. 6 distinct UI trees). When the >>>>>>>> server informs the client that the source-data has changed it then >>>>>>>> updates each instance where follow-defaults?. >>>>>>>> >>>>>>>> I understand the rationale as to why subscriptions can't be in the >>>>>>>> handlers but a subscription which switches on follow-defaults? seems >>>>>>>> ideal. >>>>>>>> >>>>>>>> Maybe I could hack a UI-less component which reacts to that >>>>>>>> subscription change by directly updating the underlying db... >>>>>>>> >>>>>>>> What would you all do? >>>>>>> >>>>>>> >>>>>>> Hi Colin, >>>>>>> >>>>>>> I'm not sure I'm clear on the problem. Here's my attempt to explain >>>>>>> back ... >>>>>>> >>>>>>> You have some setting (data) in your app (tree display state) which can >>>>>>> be: >>>>>>> 1. in a server-supplied state (you call this "default" state?) >>>>>>> 2. optionally, in user-supplied state (if present, overrides 1). >>>>>>> >>>>>>> Over time, new versions of 1. arrive. If 2. exists, it always >>>>>>> overrides 1. >>>>>>> >>>>>>> Have I understood? >>>>>>> >>>>>>> -- >>>>>>> Mike >>>>> >>>>> >>>>> Hmm. I don't get the specifics, but maybe we can talk abstractly ... >>>>> >>>>> You have values a and b which can change over time. And you have another >>>>> value c which is a function (f) of a and b. >>>>> >>>>> (f a b) => c >>>>> >>>>> And, a b and c are all stored in `app-db`. >>>>> >>>>> And then when, say, 'a' gets updated (server? user?), you want to >>>>> (reactively) modify the value in c again. After all, its value is a >>>>> function of a and b, and a just changed? >>>>> >>>>> And you are reaching for `subscribe` as a way to sorta know when to rerun >>>>> 'f' to recalculate new 'c'. >>>>> >>>>> Have I got it now? >>>>> >>>>> -- >>>>> Mike >>> >>> >>> >>> We have a similar situation with showing errors or warnings, because >>> showing them or not tends to be a function of multiple pieces of other >>> information in `app-db`. >>> >>> If 'this', but 'that' in set 'so-and-so', then show a warning saying "You >>> have duplicates on X". And the values in 'this' 'that' and 'so-and-so' >>> change over time. >>> >>> Going back to my abstract version of this involving 'a' 'b' 'c', our >>> situation is that 'c' is some set of warnings which should be displayed to >>> the user and 'a' & 'b' are the state which must be analyzed in order to >>> figure out if we have warnings. >>> >>> (f a b) -> c >>> >>> So 'f' is some calculation on 'a' and 'b' to determine the value of our >>> warning value 'c'. >>> >>> Our way of doing this is to run 'f' after every change. We use the >>> 'enrich' middleware and we put it on every handler which could effect the >>> values of 'a' or 'b' (we actually put it on every handler, just to be >>> sure). >>> >>> So we don't even try to detect that 'a' or 'b' has changed. We just >>> recalculate 'c' every single time. And we do it so that if the new 'c' >>> tests equal to the old 'c', it doesn't get assoc-ed into `app-db`. >>> >>> This approach collapses the problem to being almost trivial. BUT it comes >>> at the cost of re-running 'f' re-compuation each time anything changes (via >>> enrich). >>> >>> In our case, it has beautiful reduced a pretty tricky bit of dependency >>> updating. >> >> >> Hmm. The penny has just dropped for me. A kind of "reverse reaction" is >> possible via middleware. >> >> https://gist.github.com/mike-thompson-day8/76812d5452747bc79aac >> >> Seems so right. >> >> -- >> Mike >> >> >> -- >> Note that posts from new members are moderated - please be patient with your >> first post. >> --- >> You received this message because you are subscribed to the Google Groups >> "ClojureScript" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to [email protected]. >> To post to this group, send email to [email protected]. >> Visit this group at http://groups.google.com/group/clojurescript. > > -- > Note that posts from new members are moderated - please be patient with your > first post. > --- > You received this message because you are subscribed to the Google Groups > "ClojureScript" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To post to this group, send email to [email protected]. > Visit this group at http://groups.google.com/group/clojurescript. -- Note that posts from new members are moderated - please be patient with your first post. --- You received this message because you are subscribed to the Google Groups "ClojureScript" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To post to this group, send email to [email protected]. Visit this group at http://groups.google.com/group/clojurescript.
