Re: [ClojureScript] Trying to wrap my head around the latest Om API changes
Om's current API more or less follows this line of thinking - components are machines. However both the opaque nature of machines and the lack of fine grained control around immutable value storage (app state wrapped in an atom) have their downsides. I want Om components to have knobs. So while a component designer may decide to split their state in a certain fashion - users of the components should be able to reconfigure this without the system falling apart. On Thu, Jan 23, 2014 at 12:57 PM, Brandon Bloom brandon.d.bl...@gmail.comwrote: Not true. React has exactly the same problem. There's no public way to get at component state deep in the render tree. Fair enough. I'm still in the local state is poison camp though. To be fair, React really doesn't implement component state as local state. Such a thing can't exist because components are virtualized objects which are only hydrated when they need to do computation. The implementation utilizes a global state map, precisely because such local state is poison: It's what necessitates removeChild and dispose mechanisms in other UI frameworks (essentially reference counting). The two distinctions relevant to component state are between 1) encapsulated and exposed state and 2) durable and ephemeral state. React's state model provides components with encapsulated, emphemeral state. Encapsulated meaning that other components can't reach in there and tinker with it. Ephemeral meaning it isn't permitted to be captured to be stored and if it is somehow lost, it's not a big deal because it can be recovered. The ephemeral nature of the data is somewhat intrinsic because the DOM is ephemeral: If you were to serialize your application and teleport it to another browser, you'd have to rebuild/recover the DOM's state. There is much DOM state that shouldn't be ephemeral, but we're stuck with it. However, there is much model/view state that should be ephemeral and we need that option. I'll quote Rich on encapsulation: [If] you have a notion of “private”, you need corresponding notions of privilege and trust. The fact that components are encapsulated is part of what makes larger apps even possible. It's the wrong default for values, but it's the right default for machines (which components are). With values, we can get pseudo-encapsulation with a deftype wrapper. With machines, we can get unencapsulation by other means... but that's a larger topic I won't go in to unless prompted. I haven't kept up with Om's API evolution, so I can't comment on how this line of thinking can/should influence the design, but hopefully thinking about these properties is useful. -- 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 clojurescript+unsubscr...@googlegroups.com. To post to this group, send email to clojurescript@googlegroups.com. 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 clojurescript+unsubscr...@googlegroups.com. To post to this group, send email to clojurescript@googlegroups.com. Visit this group at http://groups.google.com/group/clojurescript.
Re: [ClojureScript] Trying to wrap my head around the latest Om API changes
It'd be a nice to get a detailed writeup about your approach if you have the time and feel inclined :) David On Thu, Jan 23, 2014 at 4:42 PM, David Pidcock eraz0rh...@gmail.com wrote: On Thursday, January 23, 2014 10:13:49 AM UTC-8, David Nolen wrote: Om's current API more or less follows this line of thinking - components are machines. However both the opaque nature of machines and the lack of fine grained control around immutable value storage (app state wrapped in an atom) have their downsides. I want Om components to have knobs. So while a component designer may decide to split their state in a certain fashion - users of the components should be able to reconfigure this without the system falling apart. I am enjoying the ability to add behaviours to components via merging data into local component state. It's quite handy when trying to achieve orthogonal separation of concerns. For example - one might have several components on a page. Perhaps their primary purpose is to display certain items in various ways. Perhaps you have a group of items you want to split into different lists based on classification rules. One way to do this is to extend your sub-components, or to wrap them in a bigger component that manages the communication between them. Using Om's opts, local-state, and shared, lets me put all the logic (and inter-component communication necessary) for drag-n-drop into a separate namespace, and then I can pull into the components the DnD behaviour, and implement the few handler functions for the stuff that's specific to each component's concern (e.g. mutate app-state on a drop). Now, I've been dwelling in OO/Imperative land for a long time, so I'm sure there are cooler ways to do all this.. but I'm having fun with it :D -- 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 clojurescript+unsubscr...@googlegroups.com. To post to this group, send email to clojurescript@googlegroups.com. 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 clojurescript+unsubscr...@googlegroups.com. To post to this group, send email to clojurescript@googlegroups.com. Visit this group at http://groups.google.com/group/clojurescript.
Re: [ClojureScript] Trying to wrap my head around the latest Om API changes
Well, at the moment the code is a little scatter-brained, and I'm not sure I can make it work for 100% of the intended use-cases, yet. For one, I want to better support collaborative events -- for example, distinguishing drag-enter and drag-leave events requires tracking across multiple components. This requires coordination at the master component level which is currently missing. Additionally, as of 0.2.3, I'm noticing some effects from the differences between render state and out-of-render-loop states, which I'll need to account for. Those caveats aside: Here's the way it hangs together. Components are defined in one (or more) namespaces. Then there's the ux namespace, which is required by those wishing to get DnD features (and future universal features, such as add item to selection) The Root component of the application calls (ux/setup-ux-master [app owner opts] ...) during will-mount. (ux/setup ...) creates some channels. One for putting drag-events onto, one for reporting bounds upwards, and a disperser (a mult over drag-events) so that we can direct drag events to interested listeners. When root om/builds sub-components, it just passes down the data that is created by ux in opts. Right now that's a bit hacky, but I hope I can alleviate some of that by using the new data locations (init-state, state, shared) in 0.2.3 When a sub-component is interested in receiving drag-events, it calls (ux/register [owner ... handler-fn] The handler function is for drag events that the sub-component is receiving. (Right now, this also reports bounds upwards, which I intend to separate out for readability/compos-ability) The neat part about keeping all this in the ux ns, is that we only need to look in the one file for where we're storing all this data (the channels etc). This makes changing any of that a little friendlier, and the components don't need to care (aside from having to call some functions in the right places) It also means I could potentially use ::qualified-keys for collision protection. (I plan on trying that out soon). ux/register finds the disperser multi-chan and taps it with a filter that only receives drag-events that are within the calling-components bounds, adding a (go..) loop with the handler-function it was passed in. The fact that every component controls it's own filter (via updating it's own :bounds on Did-Update) means we get targeted drag-events for free. Unfortunately, we don't get drag-enter and -leave, which made me sad, because the current solution seems so elegant. The drag-start, drag-end, dragging functions are also in ux/ and are bound to the appropriate components with (om/bind ..) as usual. These update the local state of the owner where appropriate, and push an event on the command channel when decision states are hit (e.g. drag-end). I *could* create them with handler functions as parameters, but I figured I'd rather have a command handler at the Root component level to manage app-state. For me, I like having the logic of the components in the components, the logic of the application at the root level, and the logic of the orthogonal-user-interactivity in a separate namespace. Now, I have a specific application I was building this for - so this would need some work to be made truly bolt-on. (there are assumptions about the names of refs, which'd have to be injected, for example). As an aside: In my application, the app-state revolves around a list of items. These items are displayed in multiple lists based on a filter, and sorted based on an attribute of the data. This means the drop handlers between the two lists simply set an attribute on the data, and React just puts them in the appropriate position of the relevant list in the next render pass automatically. On Thursday, January 23, 2014 1:52:58 PM UTC-8, David Nolen wrote: It'd be a nice to get a detailed writeup about your approach if you have the time and feel inclined :) David On Thu, Jan 23, 2014 at 4:42 PM, David Pidcock eraz0...@gmail.com wrote: On Thursday, January 23, 2014 10:13:49 AM UTC-8, David Nolen wrote: Om's current API more or less follows this line of thinking - components are machines. However both the opaque nature of machines and the lack of fine grained control around immutable value storage (app state wrapped in an atom) have their downsides. I want Om components to have knobs. So while a component designer may decide to split their state in a certain fashion - users of the components should be able to reconfigure this without the system falling apart. I am enjoying the ability to add behaviours to components via merging data into local component state. It's quite handy when trying to achieve orthogonal separation of concerns. For example - one might have several components on a page. Perhaps their primary purpose is to display certain items in various ways. Perhaps
Re: [ClojureScript] Trying to wrap my head around the latest Om API changes
On Tue, Jan 21, 2014 at 9:14 PM, kovas boguta kovas.bog...@gmail.comwrote: I understand all these mechanisms exist for a reason, but is there hope for simplifying things? The distinctions between :init-state and :state, and between :opts and get-shared are pretty subtle. Additionally, there is also the vanilla cljs way of defining constant global data, which people will think about reaching for. :init-state is an optimization and not strictly necessary. :opts is useful. om.core/get-shared leaves the door open for components requesting some global service without resorting to cljs.core/exists?. Point 1: the pathways to access the local state are very limited in Om, and seem likely to lead to complexity. For instance, passing data into the parent so that it can set the state of its child. Or having to manage channels to read/write to the local state via go loops on the owner. Unlike OO/React, there is no object graph to navigate in order to get access to the local state of some component. I fear for my render functions, if they have to be conduits for some deep subcomponent's local state update. Not true. React has exactly the same problem. There's no public way to get at component state deep in the render tree. Point 2: The APIs for app-state and local state are completely different, for ideas that are not that much different. They are both data for the pure rendering function; data which we want to be able to update from both within the component and from above it. application state and component local state are very different with respect to consistency. In principle, view-model data need not be much different from app-state data; the differences are: a) it cannot be co-located with app-state data, not because of clutter, but because there might be more than 1 component built from the same cursor position; and b) it needs to be initialized as the components are initialized. It should not be co-located if it is not something you intend to snapshot. There is no requirement that the view-model data be encapsulated in the component, or even that packets of view-model data correspond 1-to-1 with components. (One of the awesome things about Om is that the app-state data can be relatively freely reshaped into components, rather than the rigid 1-to-1 mapping in traditional MVC) Given these requirements, I think there is an alternative design, though the margin is too small to contain it. Definitely open to alternate ideas but I won't budge on the fundamental split: a) application state b) component local state c) side information I do think that that being to able force a child component to write it's state into a) instead of b) is an interesting direction to pursue and perhaps can shed some light on how a) and b) can conceptually unified. David -- 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 clojurescript+unsubscr...@googlegroups.com. To post to this group, send email to clojurescript@googlegroups.com. Visit this group at http://groups.google.com/group/clojurescript.
Re: [ClojureScript] Trying to wrap my head around the latest Om API changes
On Wed, Jan 22, 2014 at 1:42 PM, David Nolen dnolen.li...@gmail.com wrote: om.core/get-shared leaves the door open for components requesting some global service without resorting to cljs.core/exists?. Not clear to me what that means. Is there more to it than get-shared is for things that don't change, :opts is for things that can change? Not true. React has exactly the same problem. There's no public way to get at component state deep in the render tree. Fair enough. I'm still in the local state is poison camp though. Hard to get at, and against the grain of the value that Om provides. Being able to hold the complete state in your hand, rather than scattered across implementation details, is awesome. application state and component local state are very different with respect to consistency. I think I recall seeing a discussion about this, could you provide a pointer or re-summarize? The way I think about it is, look at atoms, refs and agents. Yes they are all a bit different, yet somehow it still feels like the same API. Similar deal with channels that having different buffers. It should not be co-located if it is not something you intend to snapshot. This is an interesting point. How useful are snapshots if you don't capture local state? I would expect that undo would bring back exactly back to the state I had before. a) application state b) component local state c) side information I agree there are about 3 kinds of data The most interesting question to me is, how should b) be conceptualized. I do think that that being to able force a child component to write it's state into a) instead of b) is an interesting direction to pursue and perhaps can shed some light on how a) and b) can conceptually unified. I totally agree. Seems join would be useful here. -- 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 clojurescript+unsubscr...@googlegroups.com. To post to this group, send email to clojurescript@googlegroups.com. Visit this group at http://groups.google.com/group/clojurescript.
Re: [ClojureScript] Trying to wrap my head around the latest Om API changes
On Wed, Jan 22, 2014 at 6:34 PM, kovas boguta kovas.bog...@gmail.comwrote: On Wed, Jan 22, 2014 at 1:42 PM, David Nolen dnolen.li...@gmail.com wrote: om.core/get-shared leaves the door open for components requesting some global service without resorting to cljs.core/exists?. Not clear to me what that means. Is there more to it than get-shared is for things that don't change, :opts is for things that can change? With :opts you can chose to pass things down or not. Not useful for global information or services. Not true. React has exactly the same problem. There's no public way to get at component state deep in the render tree. Fair enough. I'm still in the local state is poison camp though. Hard to get at, and against the grain of the value that Om provides. Being able to hold the complete state in your hand, rather than scattered across implementation details, is awesome. In the Om model people can choose how they divide up their state. If you want to save everything in application state, fine. But many applications will not want to do this. That said, I think it's useful to be able to force a component to write its local state to some more durable location and this is what I want to explore. application state and component local state are very different with respect to consistency. I think I recall seeing a discussion about this, could you provide a pointer or re-summarize? Application state is only consistent during renders. Anything outside the render loop (event handlers, go loops) is not guaranteed to have a consistent picture of the application state. This not true for component local state, it's always consistent. The way I think about it is, look at atoms, refs and agents. Yes they are all a bit different, yet somehow it still feels like the same API. Similar deal with channels that having different buffers. It should not be co-located if it is not something you intend to snapshot. This is an interesting point. How useful are snapshots if you don't capture local state? I would expect that undo would bring back exactly back to the state I had before. Extremely useful. I don't need to save every mouse movement when I'm sorting a sortable. I just want to undo the actual sort states. David -- 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 clojurescript+unsubscr...@googlegroups.com. To post to this group, send email to clojurescript@googlegroups.com. Visit this group at http://groups.google.com/group/clojurescript.
Re: [ClojureScript] Trying to wrap my head around the latest Om API changes
Conrad, Your understanding is mostly right, with the exception of :opts. :opts is just a modular way to push side information down the render tree - information that doesn't belong in application state, nor component locate state. :opts can in fact change and perhaps people will find that useful. However, most of the uses of :opts I've seen can and probably should be replaced with :init-state and :state usage instead. One thing you didn't mention is om.core/get-shared. Setting shared data via om.core/root + om.core/get-shared allows shared values across the entire render tree that will never change. David On Tue, Jan 21, 2014 at 3:43 PM, Conrad Barski drc...@gmail.com wrote: Hi everyone, there are now four different ways to pass data from parent-child components in the build function defined in the 0.2.3 library: - Via the cursor - Via the optional map, using on of three different map keys: :init-state :state :opts Let me take a stab to see if I can explain the difference between these four, and if anyone knows if this explanation is flawed, let me know. Let's assume we're buying socks through a web store: If the data passed to the child is part of the main application state, we do it via the cursor. For instance, our application state might contain the fact that we've put two pairs of red socks into our shopping cart. If we're rendering a component for each item in our shopping cart, the item for the red socks would get passed a cursor into the main application state that ends up pointing to a branch in this state containing something like {:item_id 32423 :amount 2 :desc Red Socks} If the component has to maintain some transient state and this transient state has a default value, we set it via :init-state. For instance, if the customer tries to purchase their socks, we might display a shipping address component, with a submit button. But on top there might be a checkbox called Same as billing address that is checked by default. Therefore the component managing the checkbox might be given an :init-state of {:same-as-billing true} which establishes this default state. If React then decides to re-render this component, the :init-state in the re-render will be ignored. Also, since the user hasn't hit the submit button yet, we want to keep this state local instead of polluting our application state (which is why we didn't use the cursor.) The :state option is used to merge new state information into the child component, triggered by the parent. For instance, if the shipping address component is already visible, but the user has decided via another Om component that they want to pay with bitcoin, the root application state may now have a new key set inside it of {:payment-method :bitcoin} which will trigger a rerender of the root component. As part of that, the parent component would now set the :state key the shipping address component of {:billing-address-available false} so that the Same as billing address checkbox is disabled (since a bitcoin payment is pseudonymous and has no physical address.) Finally, if a component needs to be passed a value that will never change through the lifetime of the component, we do it by passing in :opts. For instance, our web store might have a component called fine-print, that's instantiated in different parts of the store to display some context-appropriate legalese. Since the text in this component may be different in different parts of the store, but remains constant through the life span of a given component, we can pass in the string of legal text via :opts. Anyway, I'd love to hear anybody's opinions as to whether this description matches the behavior and intention of the latest Om changes. -- 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 clojurescript+unsubscr...@googlegroups.com. To post to this group, send email to clojurescript@googlegroups.com. 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 clojurescript+unsubscr...@googlegroups.com. To post to this group, send email to clojurescript@googlegroups.com. Visit this group at http://groups.google.com/group/clojurescript.
Re: [ClojureScript] Trying to wrap my head around the latest Om API changes
I understand all these mechanisms exist for a reason, but is there hope for simplifying things? The distinctions between :init-state and :state, and between :opts and get-shared are pretty subtle. Additionally, there is also the vanilla cljs way of defining constant global data, which people will think about reaching for. Besides the number of not-totally-orthogonal constructs, the use of local state makes me uneasy. Point 1: the pathways to access the local state are very limited in Om, and seem likely to lead to complexity. For instance, passing data into the parent so that it can set the state of its child. Or having to manage channels to read/write to the local state via go loops on the owner. Unlike OO/React, there is no object graph to navigate in order to get access to the local state of some component. I fear for my render functions, if they have to be conduits for some deep subcomponent's local state update. Point 2: The APIs for app-state and local state are completely different, for ideas that are not that much different. They are both data for the pure rendering function; data which we want to be able to update from both within the component and from above it. In reality, our usage of the term local state is putting the cart before the horse. What we are really talking about is view-model data; that it might be implemented as local state is technology-technology to manage the additional information. In principle, view-model data need not be much different from app-state data; the differences are: a) it cannot be co-located with app-state data, not because of clutter, but because there might be more than 1 component built from the same cursor position; and b) it needs to be initialized as the components are initialized. There is no requirement that the view-model data be encapsulated in the component, or even that packets of view-model data correspond 1-to-1 with components. (One of the awesome things about Om is that the app-state data can be relatively freely reshaped into components, rather than the rigid 1-to-1 mapping in traditional MVC) Given these requirements, I think there is an alternative design, though the margin is too small to contain it. -- 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 clojurescript+unsubscr...@googlegroups.com. To post to this group, send email to clojurescript@googlegroups.com. Visit this group at http://groups.google.com/group/clojurescript.
Re: [ClojureScript] Trying to wrap my head around the latest Om API changes
Here's an attempt to outline some categories of Om data and their characteristics - 1. Computed data. Read only, not subject to direct update. N.b. get-in qualifies as a computation; constant data is a degenerate case. It can be argued, on the grounds of programmer convenience, performance, or architectural simplicity, that constant data should be split off into a separate case. However it has the characteristics just outlined. 2. Model data. Can be read, and directly updated. 3. Default data. This is a combination of 1 2. It is computed initially, but is subject to direct update. After direct update, its characteristics are identical to model data. After initial computation, there are 2 possibilities: A) promote default data to model data immediately after the initial computation B) promote default data to model data upon first direct update. The difference is that in flavor B), the value of the default may continue to vary, as inputs to the default computation vary - this exactly how computed data works. They continue to be linked. In flavor A), the default is computed once, and if the data that was the input to the computation changes, it has no effect an the already-generated default data. Flavor A shares this characteristic with the current scheme for local state. An example where flavor B might be useful: setting the default font size for a list of notes, based on variable global parameters. You want to explicetly set the font size for one note, while letting all the others continue with default behavior. There are some more axis along which we can analyze the categories of data, such as visibility, lifecycle time-of-update, etc. On Tue, Jan 21, 2014 at 8:14 PM, kovas boguta kovas.bog...@gmail.com wrote: I understand all these mechanisms exist for a reason, but is there hope for simplifying things? The distinctions between :init-state and :state, and between :opts and get-shared are pretty subtle. Additionally, there is also the vanilla cljs way of defining constant global data, which people will think about reaching for. Besides the number of not-totally-orthogonal constructs, the use of local state makes me uneasy. Point 1: the pathways to access the local state are very limited in Om, and seem likely to lead to complexity. For instance, passing data into the parent so that it can set the state of its child. Or having to manage channels to read/write to the local state via go loops on the owner. Unlike OO/React, there is no object graph to navigate in order to get access to the local state of some component. I fear for my render functions, if they have to be conduits for some deep subcomponent's local state update. Point 2: The APIs for app-state and local state are completely different, for ideas that are not that much different. They are both data for the pure rendering function; data which we want to be able to update from both within the component and from above it. In reality, our usage of the term local state is putting the cart before the horse. What we are really talking about is view-model data; that it might be implemented as local state is technology-technology to manage the additional information. In principle, view-model data need not be much different from app-state data; the differences are: a) it cannot be co-located with app-state data, not because of clutter, but because there might be more than 1 component built from the same cursor position; and b) it needs to be initialized as the components are initialized. There is no requirement that the view-model data be encapsulated in the component, or even that packets of view-model data correspond 1-to-1 with components. (One of the awesome things about Om is that the app-state data can be relatively freely reshaped into components, rather than the rigid 1-to-1 mapping in traditional MVC) Given these requirements, I think there is an alternative design, though the margin is too small to contain it. -- 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 clojurescript+unsubscr...@googlegroups.com. To post to this group, send email to clojurescript@googlegroups.com. Visit this group at http://groups.google.com/group/clojurescript.