-
-
Notifications
You must be signed in to change notification settings - Fork 419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Big API change for Views and Intents #73
Comments
I'm not using Cycle so my say probably doesn't count, but delegation by classes is painful. |
@gaearon Care to share a bit why and how it was painful in React? |
Rephrasing: I used to use event delegation a lot before React and it was painful. When I was using Backbone, I would often:
Most of these are solved by using a consistent prefix like Note: not all of these problems are applicable to Cycle at all. In fact, probably, there isn't much difference because in Cycle's approach, you can't have inlinish handlers anyway because Cycle is reactive. But at least, with I'd be against leaking CSS class names or even DOM structure to intents in any way. |
Thanks @gaearon for the valuable input, it's appreciated. Most of the problems you see arise from the facts that (1) CSS selectors can be used globally, (2) CSS files are used for styling. These problems are not relevant to Cycle, since (1) |
Yup, I understand about class search being confined to a single view. I don't see yet how this solves the issues I outlined but perhaps I miss something.. I agree (2) makes some of these problems go away though. |
I really like this approach! Separate those concerns 👍 Wouldn't adding a key to each element similar to what react does and virtual-hyperscript allows effectively eliminate the need and problem of using classNames? |
Not sure yet if we can use keys, I suspect we need to use class names because the Intent is the one selecting interaction events, not the Renderer nor the View, and the Intent would only have access to the DOM. And we cannot grab the vtree between View and Renderer since that would end up changing the API in leaky abstraction way. |
I should have been more precise :) So what we want to accomplish is to establish a mapping between a specific user interaction on an element and the resulting intent. The problem is that we have to be able to define which interactions on which element are relevant for a given intent. I believe people are quite confident using the querySelector API so why not just use this to it's full extend and also generate keys for our DOM tree ala react so that we have an index of our element within our (V)DOM. This would also allow us to easily define a click action on list items as such: var MovieSelectionView = Cycle.createView(model =>
({
vtree$: model.get('listofLists$')
.map(listOfLists =>
h('div', /*listOfLists.map( list => h('ul' .map('li' ...*/ [ //omitted the use of map to show the result:
h('ul#watchList', {attributes: {data-id: "0"}}, [
h('li', {attributes: {data-id: "0.0"}}),
h('li', {attributes: {data-id: "0.1"}}),
h('li', {attributes: {data-id: "0.2"}}),
]),
h('ul#recommendations', {attributes: {data-id: "1"}}, [
h('li', {attributes: {data-id: "1.0"}}),
h('li', {attributes: {data-id: "1.1"}}),
h('li', {attributes: {data-id: "1.2"}}),
])
])
)
})
);
var MovieSelectionIntent = Cycle.createIntent(user =>
({
watchListSelection$: user.events('#watchList', 'click').map(ev => ev.target.getAttribute('data-id')),
recommendationSelection$: user.events('#recommendations', 'click').map(ev => ev.target.getAttribute('data-id'))
})
//OR
({
movieSelection$: user.events('ul > li', 'click').map(ev => ev.target.getAttribute('data-id'))
})
); The query selection would of course be scoped to the vtree's top-level VirtualNode as you mentioned. The above example is perhaps not that pretty. Thinking out loud at this point. But one could perhaps prefix data-id with the model name? ex. "listOfLists$.watchList.0", where watchList would be object within our listOfLists object instead of an array named listOfLists. |
Currently in Cycle v0.11, you also need to remember to update in two locations, since a user event stream (such as |
OK, thanks for the clarification. On Sat, Feb 7, 2015, 17:05 André Staltz notifications@github.com wrote:
|
Note to self: Proposal for Model-View-"User"-Intent in custom elements: // function, not createView. And notice element here
var HelloComponent = function(element, Properties) {
var HelloModel = Cycle.createModel((Properties, Intent) =>
({
name$: Properties.get('name$')
.merge(Intent.get('changeName$'))
.startWith('')
})
);
var HelloView = Cycle.createView(Model =>
({
vtree$: Model.get('name$')
.map(name =>
h('div', [
h('label', 'Name:'),
h('input.myinput', {attributes: {type: 'text'}}),
h('h1', 'Hello ' + name)
])
)
})
);
// Notice createRenderer replaced with createDomUser
// essentially the same thing, just renamed, except it should also
// have the method .events(selector, eventName)
var HelloUser = Cycle.createDomUser(element);
// Notice the Intent is injected a User
var HelloIntent = Cycle.createIntent(User =>
({changeName$: User.events('.myinput', 'click').map(ev => ev.target.value)})
);
HelloIntent
.inject(HelloUser)
.inject(HelloView)
.inject(HelloModel)
.inject(Properties, HelloIntent);
return {
// notice no more vtree$ exported here
// this part is only used for custom events exported out of this component
};
}; |
Done in v0.12 |
Just an idea for now.
This
Notice no event concerns in View, and Intent using
view.events('.myinput', 'click')
. Implementing this in the framework will be a challenge, since (1) it is suicidal to make event streams for all possible events and all possible VirtualNodes in the vtree, (2) the intent doesn't have direct access to the Renderer, where we insert the actual callbacks that feed DOM events into the view's event streams.One early idea is to use event delegation using the vtree's top-level VirtualNode as parent to the querySelector happening in
view.events('.myinput', 'click')
. Let VP be the vtree's top-level VirtualNode, and P the rendered correspondent in the actual DOM. The View magic could give a uid to VP, the Renderer magic would set an attribute using uid as value on P, and Inject magic would do event delegation to catch events on the child matching '.myinput' under P, and forward them into an observable living in the view, which the intent dynamically generated when callingview.events()
. This implementation relies on the vtree of a View having always only one top-level VirtualNode. Intent magic would have to touch the DOM for doing event delegation, but that's Ok since anyway interaction events only make sense in the context of a DOM, and Intent anyway get raw DOM events.Things to keep in mind:
event.preventDefault()
?Another possible implementation: make the Renderer be a representative of the User. Maybe even rename Renderer to User, but that could be confusing. The idea is to make the Renderer a side-effectful DataFlowNode instead of a DataFlowSink. It would return event streams of raw interaction. Would be a simpler and less hacky way compared to the first approach above. Resulting usage example:
Things to consider:
References: http://davidwalsh.name/event-delegate
The text was updated successfully, but these errors were encountered: