Open kumavis opened 9 years ago
About the third and the fourth... Are there some techniques about immutables with object oriented programming?
We have a (static) render
function with a state
argument. Event handler is a (static) function with a state
argument. Can we use classes here?
state
is an immutable object - can't we have methods in it? But can we use classes here?
I had an idea for Rendering into modals, but haven't tried it
If your components that want inject stuff into modals registered their modal content rendering functions at definition time, then they could set the content state of the modal-component to use their rendering function. Bad pseudo code:
var SaveChangesComponent = function(){
app.modalController.register( 'save-changes', function(){ return h('.modal-content') } )
var channels = {
confirm: function(){ app.modalController.content.set('save-changes') }
}
}
Components... Is set of channels the one for the whole app? Or can events be component-specific?
@neonstalwart differentiates between channels and events. Both are component specific and don't bubble.
typically for generic components (like a text input component) they either generate generic events (like change
) that a parent element can listen to (textInput.events.change(state.firstName.set)
) or they accept an observable (like value
) that will be updated by the input (state.firstNameEditor.set(textInput({ value: state.firstName }))
). usually i've implemented both options but maybe i should just choose one and be consistent with it - that's the kind of thing to decide as part of trying to come up with a component API. if we can agree on a suitable API, i could probably start contributing a few generic components to use as a starting point.
i also use events for my application-level events (loginComponent.events.success(app.events.login)
i.e. when a generic login component has a success
event, trigger the more specific application-level login
event) which is something that can't be done with channels since channels can only be triggered from DOM events. you may wonder why i have a generic loginComponent.events.success
and a more specific app.events.login
and the reason is that i prefer not to embed application logic directly in components if i can avoid it. this makes it easier to isolate components from the application logic when it comes to testing the components and it also means that i could have multiple places/components that may trigger the same application event - e.g. ctrl+s, File->Save menu, Save button, could all trigger app.events.save(thing)
without embedding the save logic in any of those places.
Is set of channels the one for the whole app? Or can events be component-specific?
so, i have events for both - some are for the whole app and some are component-specific and the component-specific ones trigger the ones for the whole app.
@kuraga i don't know if i understand your question properly... no part of that line of code is a handler.
textInput
is a constructor/factory for a text input componentstate
is a parent component which embeds a textInput
component. state.firstNameEditor
is the embedded state of the textInput
componentstate.firstName
is the observable value for a firstName
which is shared between state
and state.firstNameEditor
- i.e. state.fistNameEditor.value === state.firstName
parentComponent
constructor/factory e.g. (i've expanded the one line above into the following few lines to add more context)function parentComponent(options) {
var events = hg.input([ ... ]),
firstName = hg.value(options.firstName),
state = hg.struct({
events: events,
firstName: firstName,
// equivalent to: state.firstNameEditor.set(textInput({ value: state.firstName }))
firstNameEditor: textInput({ value: firstName })
}),
...
return state;
})
does that help at all?
@neonstalwart thanks, very useful! But sorry my question/thought is different.
Is here state
- state of the whole app, or is it component's part only?
Acccording to this, context
(i.e. state
above) is set by channels
function. Who does call it? state
function does and nobody calls it in examples explicity.
So, channels' context is set by state
function. And we're calling it when we're creating whole app state. So, here state
is state of the whole app, it's not component specific.
Correct?
@kuraga are you talking about my example code or...
@kumavis no. See links in the message.
P.S. Originally it wasn't a FAQ, I wanted to talk about "good way to isolate parts of state when handling a channel event". But seems like we don't understand each other. Just re-read my last message, please... Thanks.
Is here state - state of the whole app, or is it component's part only?
the example is trivial so it's no surprise that it doesn't help answer the question. the general pattern would be that state
is a single component's state. in such a trivial example, the whole application is just one component so that example doesn't help clarify whether this pattern is for the whole app or just a single component.
Acccording to this, context (i.e. state above) is set by channels function. Who does call it? state function does and nobody calls it in examples explicity.
right, again, trivial examples are misleading. think of that hg.state
function as a component factory which wires together state
and channels. the todomvc example is less trivial and it shows a TodoItem
component which uses hg.state
to produce a component. the state
which gets passed to each channel is the relevant state
for a single TodoItem
. at the application level, TodoApp
also uses hg.state
but embeds a number of TodoItem
states at state.todos
So, channels' context is set by state function. And we're calling it when we're creating whole app state. So, here state is state of the whole app, it's not component specific.
this is the beauty of all of this... a simple pattern is applicable to a single component and to the whole application (which is really just another component). so, there is no rule that hg.state
is only for components or only for the whole app - it is a pattern that can be applied at the macro and micro level because the idea of a component is applicable at both levels. everything is a component.
@neonstalwart
the todomvc example is less trivial and it shows a
TodoItem
component which useshg.state
to produce a component. thestate
which gets passed to each channel is the relevantstate
for a singleTodoItem
. at the application level,TodoApp
also useshg.state
but embeds a number ofTodoItem
states atstate.todos
This is a key. Thanks very much! So, there were nothinng to discuss - it was my misunderstanding only. Feel free to clean up our messages. Thanks!
Can we add routing to the list of standing challenges? Switch statements in views leads to lots of duplicated code... IMO component-based routing approaches like react-router avoid this problem.
Rendering into modals or other places
You can put a render function or vdom into state to hand it off to a modal component, but it doesnt serialize.
Why would you ever need to change the contents? If you need two different modal contents, render two modals and enable whichever of them is appropriate.
Why would you ever need to change the contents?
its more about a deeper component (widgets list) interacting with a component higher in the DOM ( widgets deletion confirm modal )
Right now im thinking of a top-level modal controller that other components that need modals register with when they are defined. Having a single modal controller also guarantees you wont endup with two modals showing at once, overlapping.
Something I've come to realise is related to #132. Mercury doesn't just have a problem dealing with non-DOM events, but also non-DOM rendering. For example, you could view notifications as a result of rendering your application state, but virtual-dom doesn't include notifications, so you have to manage them manually somehow. Similarly, network calls are a result of 'rendering' your application state to the network, and the events that come back from the network are analogous to DOM events.
Having a single modal controller also guarantees you wont endup with two modals showing at once, overlapping.
Fair enough, but if that happens unintentionally you probably have a bug elsewhere you'd need to fix anyway.
@eightyeight very insightful, hadnt thought of notifications / network requests as a sort of rendering
I've been trying to attach audio to the page by rendering an <audio>
tag when certain parts of the state exist, but ran into the problem that requestAnimationFrame
is often paused when a tab is unfocused. If you're using audio as a notification of, for example, chat messages appearing, then rendering audio as part of your application rendering isn't going to cut it, because that rendering won't happen in the RAF until the user visits the application tab.
I haven't decided how we'll solve this yet; probably just hang a listener on the app state that uses the Audio API directly, rather than rendering an audio tag. Not sure if this is enough of a problem to warrant thoughts about an entire non-DOM 'rendering' pipeline.
just hang a listener on the app state that uses the Audio API directly
Though it's a good idea I believe the Audio API exposes a subset of what an audio tag is capable of doing, so attaching a tag might be what you want to do regardless. I think having a direct listener on the state outside mercury
might be the best solution for this.
The audio API does enough for our purposes that I don't want to bother with the DOM.
Side-note, observ
makes it really easy to listen to state changes accurately, versus Angular's listeners which basically just dump the entire new object on you and let you diff it yourself.
Here are a few things we're still trying to figure out how to do best, in no particular order
Animations / CSSTransitionGroup
e.g. a removal animation that keeps a removed item in the dom long enough to animate out. mostly solved
Not duplicating state serialization
With shared state passed down the hierarchy you end up with duplicated serialization
Rendering into modals or other places
You can put a render function or vdom into state to hand it off to a modal component, but it doesnt serialize.
Formalized component interface
If we can set expectations of component interface, it will be easier to make reusable pieces for the community.
Non-DOM events
Triggering events on components that don't come from the DOM. see thread.
cc: @neonstalwart @Raynos some chat logs