-
Notifications
You must be signed in to change notification settings - Fork 396
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
Bubble events on fire #1117
Bubble events on fire #1117
Conversation
Awesome, just what I was looking for. That takes care of the hundred lines "bridging events" code. 👍 |
Is this related to #1099 ? |
@fskreuz yes, though newer PR from @evs-chris is #1116. it is using a |
@martypdx I think bubbling should be similar to the DOM's bubbling, which bubbles all the way up unless something prevents it in the middle, like I imagine it looking like this on the DOM. <body>
<middle>
<component on-click="someEvent">
</middle>
</body> And the JS for it // Component that fires event
Component = Ractive.extend({
template: '<span id="test" on-click="someEvent">click me</span>'
});
// Middle component that uses component
Middle = Ractive.extend({
template: '<component />',
components: {
component: Component
},
init: function() {
this.on({
someEvent: function() {
// someEvent will not bubble anymore
event.stopPropagation();
}
});
}
});
ractive = new Ractive({
el: fixture,
template: '<middle/>',
components: {
middle: Middle
}
});
// someEvent, regardless of who fired it (no namespace)
ractive.on('someEvent', function(event) {
// normally should fire
// but will not because of a stopPropagation() in Middle
}); |
@fskreuz @evs-chris One of the current issues is that the It will be a BREAKING CHANGE, but I think would be good idea to add it. It would make things consistent with event plugins and we could construct the context for the ractive instance. I can never find existing gh issues like @Rich-Harris, but there was also some talk of moving the
|
Just some wild suggestions based on other libraries. An event object should be present anywhere to carry event details like who triggered, event name, targets, delegated targets etc. (like jQuery). Data transport could vary (args vs data object). this.on('event',function(eventObj, arg1, arg2, arg3){
//eventObj houses all event data
});
this.fire('eventName',arg1,arg2,arg3...); or this.on('event',function(event){
//event object puts the passed data into a data property
//event.data.foo;
//event.data.bar
});
this.fire('eventName',{
foo : 'hello',
bar : 'world'
}); |
@fskreuz yes, I'm coming to the same conclusion. We just need to add the |
I added in '*' notation idea from @evs-chris, though with more limited meaning of indicated whether or not event should bubble outside of component.
It seems like right thing for internally in the component to decide whether event is private or should be bubbled. I like Initially, I implemented <component on-*="foo"/> This will cause bubbled events to be Still need to add event object for |
@martypdx How about a "bubble : true || false" in the component's configuration. By default, components bubble up, like DOM elements. Setting it to As for namespace some ideas: A special namespace attributehow about placing a special <timewidget ractive-eventns="some.event.namespace" on-someevent="someHandler" /> So the component only knows that it needs to fire When listening, namespaces can also be wildcarded, similar to observers (yey consistency!):
this.on({
'widgets.*.time' : function(){
...
}
}); The Define namespace in parent implementationI personally hate the idea of adding too much doodads in the HTML (hence abandoned Angular for Ractive). So the So to avoid template clutter, how about delegating it to the parent implementation? Something like: namespace : {
'some.namespaceName1' : ['evtName1','evtName2',...],
'some.namespaceName2' : ['evtName3','evtName4',...],
...
} while the component will remain normal: <timewidget on-someevent="evtName1" /> |
to bubble or not to bubble?Defaulting to bubbling with no default namespace just seems to open ended as events would be everywhere and prone to inadvertent clashes. (Side note: performance is not really an issue. Informal measurements show that checking if a parent if subscribed takes .02ms). So I've tried both: 1) default namespacing outside the component, 2) Opt-in event bubbling. Thus far, I prefer #2:
<ul>
{{#items:i}}
<li><a on-tap='*select'>{{name}}</a>
<div on-tap='showDetails'>{{address}}, {{city}} {{state}}<div>
</li>
{{/}}
</ul> contextAnother key issue is context for the event. Currently the events exposed by I've added an event object to Both approaches could make sense for a component namespacingAgree on many of your points. I do think that putting it on the template component declaration is often the right place, though your spot on that we need to be careful to not obstruct data params. Need to think some more on this one... |
One other note, I went with |
I don't like the idea of using <component on-*="foo"/> to add a namespace. It seems more like "call <component ractive-namespace="foo"/> or something else but the more I think about this, the more I like DOM/jQuery-like way of doing this:
This doesn't require adding a new syntax and/or config options and it's very flexible. |
Thanks for the feedback @MartinKolarik.
Sounds like all events bubbling has support. Let's give that a try.
What about
@MartinKolarik not sure what you mean here. How/where is this happening? Do you just mean component attribute can be used to do whatever you want? |
Fair enough :-) If we went with this approach, I'd say it's always a prefix and
I meant that instead of adding a namespace on component level ( Component = Ractive.extend({
template: '<span id="test" on-click="someEvent.component">click me</span>'
});
Middle = Ractive.extend({
template: '<component/>'
});
ractive = new Ractive({
el: fixture,
template: '<middle/>',
components: {
component: Component,
middle: Middle
}
});
ractive.on( 'someEvent.component', function ( event ) {
t.ok( true );
t.equal( event.original.type, 'click' );
});
simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); Listener is called if:
|
<span id="test" on-click="someEvent.component">click me</span> Components should have no idea of the outside world and the namespaces it should use. Makes them more portable by only defining events they emit. The parent component handles namespacing. That way, if you borrow components from another project (like we do now in ours), we just slap it in, change the namespace in the parent template, and we're good to go. We need not tinker with the component code. This following might be better (I think): In the component, we just fire of events. Component internals have no idea of the outside world. All the outside world needs to know is that the component fires this kind of event. For instance, a "Play" button for a custom audio player. <i class="glyphicon glyphicon-play" on-click="play">Play</i> Using the component, we can assign handlers to the events fired from the component. In a jQuery manner, we assign <component on-play="eventName.namespace.subnamespace" /> Listening for the event is as follows. It's similar to jQuery as well. this.on({
// Listens to all namespaces for eventName
'eventName' : function(event,arg1,arg2,...){...},
// Listens to all subnamespaces for eventName
'eventName.namespace' : function(event,arg1,arg2,...){...},
// Listens only to subnamespace for eventName
'eventName.namespace.subnamespace' : function(event,arg1,arg2,...){...},
// Listens to any namespace with subnamespace "foo"
'eventName.*.foo' : function(event,arg1,arg2,...){...}
}) Firing would be similar as well: // Triggering above
this.fire('eventName',arg1,arg2,...);
this.fire('eventName.namespace',arg1,arg2,...);
this.fire('eventName.namespace.subnamespace',arg1,arg2,...);
this.fire('eventName.someunrelatednamespace.foo',arg1,arg2,...);
// Multiple events - comma separated
this.fire('eventName, anotherEvent, anotherEvent.with.namespace',arg1,arg2,...); |
That's true, but only as long as the "borrowed" component doesn't contain another component: Component = Ractive.extend({
template: '<span id="test" on-click="someEvent">click me</span>'
});
Middle = Ractive.extend({
template: '<component/>'
});
ractive = new Ractive({
template: '<middle/>', // You can't set a namespace for `someEvent` here.
...
}); At least, that's how I understood this so far - that there's only one namespace. Do we want to add a namespace on each level, so that
This is basically the same as manually bubbling the events, i.e. what we have now. You don't have to do it on every level, but still... Thinking about this more, I do agree that adding namespaces on event level wasn't such a good idea. In most cases you'll want to namespace all events with component's name which means you'd have to repeat it all over your template. Adding a This brings me back to the question of attribute's name. @martypdx suggested @fskreuz idea of adding |
Okay, another go, thanks for all the feedback thus far. Keep in mind that#1141 makes a number of event "bubbling" use cases obsolete. I finally hit that point where I started taking options away to reduce complexity, which is usually a good sign. Here's where current code stands: all events bubbleOutside of the component events bubble as both the provided event name and with the added prefix of the component tag name. var ractive = new Ractive({
template: '<widget/>',
components: { widget: Ractive.extend({ template: '<p on-click="foo"/>' }),
})
// both good
ractive.on('foo', function(){})
ractive.on('widget.foo', function(){}) Inside the component instance itself, only the handler "as named" fires. Likewise for proxy events attached to the template component declaration. cancel bubblingThe whole adding a method ( There was one idea out of it which I did incorporate. Bubbled events, if they did come from a proxy-event, get a component property added with a ref to the component that fires the event. ractive.on('widget.foo', function(e){
// hello component
e.component.observe('bar', function(){...})
}) So how do you cancel event bubbling? There's two ways, the first is:
|
I just realized that per @fskreuz 's comment that jquery goes right to left. With ractive keypaths, I'm use to going left to right in the hierarchy, which is why I went that way. Not really attached one way or the other. |
Another thing to point out is that the lifecycle events bubble as well. |
@martypdx I never really liked the way jQuery handled namespacing :P |
@fskreuz reading up on jQuery's event namespacing, they think of it more as adding a css class where any namespace can match, rather than a hierarchy: |
How can we subscribe to events from the main instance, without subscribing to events from child components? var ractive = new Ractive({
template: '<widget/><p on-click="foo"/>',
components: { widget: Ractive.extend({ template: '<p on-click="foo"/>' }),
});
// No way to subscribe only to our "foo", not "widget.foo"?
ractive.on('foo', function () {}); I thought this was one of the reasons to implement namespacing. |
@MartinKolarik I'm on board with that as I would also prefer that the events not bubble under their name as declared in the proxy event. I think we would be unleashing ͢z̯̘̱̣̣a̙͔͕̦̬͍l̡̖̼̣̥̰̥̗ģ͍͚̖͈̹o͓̩̼̤͖̗̥ . What if we did bubble declared name if it containes a var ractive = new Ractive({
template: '<widget/><p on-click="foo"/>',
components: { widget: Ractive.extend({
template: '<p on-click="foo"/><p on-click="pickle.berry"/>'
}),
});
// handles only foo on top-level instance as expected
ractive.on('foo', function () {});
// these all work to get to component events
ractive.on('widget.foo', function () {});
ractive.on('pickle.berry', function () {});
ractive.on('widget.pickle.berry', function () {}); |
I would prefer always adding a namespace to bubbled events: var ractive = new Ractive({
template: '<widget/><p on-click="foo"/>',
components: { widget: Ractive.extend({
template: '<p on-click="foo"/><p on-click="pickle.berry"/>'
}),
});
// handles only foo on top-level instance as expected
ractive.on('foo', function () {});
// these all work to get to component events
ractive.on('widget.foo', function () {});
ractive.on('widget.pickle.berry', function () {});
// if you want to listen to "pickle.berry" on all components, not only widgets, you can still do this
ractive.on('*.pickle.berry', function () {});
// Is there any other use case I missed? Deciding whether the event should bubble or not based on it's name doesn't really appeal to me. |
@MartinKolarik I'm down with that. Only other thing to add is that you can always change the event anywhere up the stack if you want it to be something different: var ractive = new Ractive({
template: '<widget on-foo="flower"/><p on-click="foo"/>',
components: { widget: Ractive.extend({
template: '<p on-click="foo"/><p on-click="pickle.berry"/>'
}),
});
ractive.on('flower', function () {}); So it feels covered to me. @fskreuz seem to be advocating more for auto-bubbling, but I have not been entirely comfortable with that. I think that |
👍 |
awesome! |
Start of event bubbling very simple implementation. Component events propagate under
component.eventName
. Test sums it up:There's a second commented out test for events declared by parent on component. Need some more thought on what should happen with these.
Also no consideration for additional context beyond original event.