From 4593d3d8c07dfbcf855d7db3516bac991c29d126 Mon Sep 17 00:00:00 2001 From: marty Date: Thu, 14 Aug 2014 18:19:30 -0700 Subject: [PATCH 01/14] bubble events on fire --- src/Ractive/prototype/fire.js | 22 +++++++++ test/modules/events.js | 84 +++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/src/Ractive/prototype/fire.js b/src/Ractive/prototype/fire.js index c3ec05929a..5f933a7655 100644 --- a/src/Ractive/prototype/fire.js +++ b/src/Ractive/prototype/fire.js @@ -1,6 +1,11 @@ export default function Ractive$fire ( eventName, event ) { var args, i, len, originalEvent, stopEvent = false, subscribers = this._subs[ eventName ]; + if ( this.component ) { + eventName = this.component.name + '.' + eventName; + subscribers = addParents( this._parent, subscribers, eventName ); + } + if ( !subscribers ) { return; } @@ -17,3 +22,20 @@ export default function Ractive$fire ( eventName, event ) { originalEvent.stopPropagation && originalEvent.stopPropagation(); } } + +function addParents ( ractive, subscribers, eventName ) { + if( !ractive ) { return subscribers; } + + var subs = addParents( ractive._parent, ractive._subs[ eventName ], eventName ); + + if ( subs && subs.length ) { + if ( subscribers && subscribers.length ) { + return subscribers.concat( subs ); + } else { + return subs; + } + } else { + return subscribers; + } + +} diff --git a/test/modules/events.js b/test/modules/events.js index 3cb253366b..00883b88b8 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -664,7 +664,91 @@ define([ 'ractive' ], function ( Ractive ) { simulant.fire( ractive.findAll( 'button' )[1], 'click' ); }); + module( 'Events bubble up components' ); + test( 'Parents fire component namespaced events', function ( t ) { + var ractive, Component, Middle; + + expect( 2 ); + + Component = Ractive.extend({ + template: 'click me' + }); + + Middle = Ractive.extend({ + template: '' + }); + + ractive = new Ractive({ + el: fixture, + template: '', + components: { + component: Component, + middle: Middle + } + }); + + ractive.on( 'component.someEvent', function ( event ) { + t.ok( true ); + t.equal( event.original.type, 'click' ); + }); + + simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); + }); + + /* + test( 'Component events fire both non-namespaced and namespaced events', function ( t ) { + var ractive, middle, Component, Middle; + + expect( 3 ); + + Component = Ractive.extend({ + template: 'click me', + init: function () { + this.on( 'someEvent', function () { + this.fire( 'newEvent' ); + }.bind( this ) ); + } + }); + + Middle = Ractive.extend({ + template: '' + }); + + ractive = new Ractive({ + el: fixture, + template: '', + components: { + component: Component, + middle: Middle + } + }); + + middle = ractive.findComponent( 'middle' ); + + middle.on( 'newEvent', function ( event ) { + t.ok( true ); + }); + + middle.on( 'component.newEvent', function ( event ) { + t.ok( true ); + }); + + ractive.on( 'newEvent', function ( event ) { + throw new Error( 'non-namespaced event should not fire above immediate parent' ); + }); + + ractive.on( 'component.newEvent', function ( event ) { + t.ok( true ); + }); + + ractive.on( 'middle.newEvent', function ( event ) { + throw new Error( 'namespaced event of immediate parent should not fire' ); + }); + + simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); + }); + */ }; }); From 65178c9481e4530fa938f2ec7740482025ba4617 Mon Sep 17 00:00:00 2001 From: marty Date: Sat, 16 Aug 2014 12:32:01 -0700 Subject: [PATCH 02/14] declarative * bubbling and component namespacing --- src/Ractive/prototype/fire.js | 46 +----- src/Ractive/prototype/reset.js | 3 +- src/Ractive/prototype/shared/fireEvent.js | 59 +++++++ src/Ractive/prototype/update.js | 3 +- src/global/runloop.js | 3 +- src/parse/converters/element.js | 10 ++ .../Component/initialise/propagateEvents.js | 22 ++- .../items/Component/prototype/unrender.js | 4 +- .../Element/EventHandler/prototype/fire.js | 4 +- .../Element/EventHandler/prototype/init.js | 5 +- test/modules/events.js | 153 +++++++++++++++++- 11 files changed, 260 insertions(+), 52 deletions(-) create mode 100644 src/Ractive/prototype/shared/fireEvent.js diff --git a/src/Ractive/prototype/fire.js b/src/Ractive/prototype/fire.js index 5f933a7655..32d9b89fb4 100644 --- a/src/Ractive/prototype/fire.js +++ b/src/Ractive/prototype/fire.js @@ -1,41 +1,11 @@ -export default function Ractive$fire ( eventName, event ) { - var args, i, len, originalEvent, stopEvent = false, subscribers = this._subs[ eventName ]; +import fireEvent from 'Ractive/prototype/shared/fireEvent'; - if ( this.component ) { - eventName = this.component.name + '.' + eventName; - subscribers = addParents( this._parent, subscribers, eventName ); - } - - if ( !subscribers ) { - return; - } - - args = Array.prototype.slice.call( arguments, 1 ); - - for ( i=0, len=subscribers.length; i]/, onPattern = /^on/, + namespacePattern = /^on-\\*/, proxyEventPattern = /^on-([a-zA-Z$_][a-zA-Z$_0-9\-]+)$/, reservedEventNames = /^(?:change|reset|teardown|update)$/, directives = { 'intro-outro': 't0', intro: 't1', outro: 't2', decorator: 'o' }, @@ -105,6 +106,15 @@ function getElement ( parser ) { addProxyEvent( match[1], directive ); } + // on-* namespace + // is this ok to create faux-directive? + // or is it hacky and should this be own template primitive??? + else if ( namespacePattern.test( attribute.name ) ) { + if ( !element.v ) element.v = {}; + directive = processDirective( attribute.value ); + addProxyEvent( '*', directive ); + } + else { if ( !parser.sanitizeEventAttributes || !onPattern.test( attribute.name ) ) { if ( !element.a ) element.a = {}; diff --git a/src/virtualdom/items/Component/initialise/propagateEvents.js b/src/virtualdom/items/Component/initialise/propagateEvents.js index f896f40e9a..82dc87a70b 100644 --- a/src/virtualdom/items/Component/initialise/propagateEvents.js +++ b/src/virtualdom/items/Component/initialise/propagateEvents.js @@ -1,3 +1,4 @@ +import fireEvent from 'Ractive/prototype/shared/fireEvent'; import log from 'utils/log'; // TODO how should event arguments be handled? e.g. @@ -6,12 +7,26 @@ import log from 'utils/log'; // when 'foo' fires on the child, but the 1,2,3 arguments // will be lost -export default function ( component, eventsDescriptor ) { +export default function propagateEvents ( component, eventsDescriptor ) { var eventName; for ( eventName in eventsDescriptor ) { if ( eventsDescriptor.hasOwnProperty( eventName ) ) { - propagateEvent( component.instance, component.root, eventName, eventsDescriptor[ eventName ] ); + if( eventName === '*' ) { + // TODO: allow dynamic fragments + let namespace = eventsDescriptor[ eventName ]; + if ( typeof namespace === 'string' ) { + namespace = namespace.trim(); + } + if ( !namespace || namespace === '*' || ( namespace.d && !namespace.d.length ) ) { + namespace = component.name; + } + + component.eventNamespace = namespace; + + } else { + propagateEvent( component.instance, component.root, eventName, eventsDescriptor[ eventName ] ); + } } } } @@ -28,8 +43,7 @@ function propagateEvent ( childInstance, parentInstance, eventName, proxyEventNa childInstance.on( eventName, function () { var args = Array.prototype.slice.call( arguments ); - args.unshift( proxyEventName ); - parentInstance.fire.apply( parentInstance, args ); + fireEvent( parentInstance, proxyEventName, { args: args } ); }); } diff --git a/src/virtualdom/items/Component/prototype/unrender.js b/src/virtualdom/items/Component/prototype/unrender.js index 3a56c598f4..f2fa3c49d2 100644 --- a/src/virtualdom/items/Component/prototype/unrender.js +++ b/src/virtualdom/items/Component/prototype/unrender.js @@ -1,5 +1,7 @@ +import fireEvent from 'Ractive/prototype/shared/fireEvent'; + export default function Component$unrender ( shouldDestroy ) { - this.instance.fire( 'teardown' ); + fireEvent( this.instance, 'teardown' ); this.shouldDestroy = shouldDestroy; this.instance.unrender(); diff --git a/src/virtualdom/items/Element/EventHandler/prototype/fire.js b/src/virtualdom/items/Element/EventHandler/prototype/fire.js index f91c9aaaf1..7a718b1cae 100644 --- a/src/virtualdom/items/Element/EventHandler/prototype/fire.js +++ b/src/virtualdom/items/Element/EventHandler/prototype/fire.js @@ -1,5 +1,7 @@ +import fireEvent from 'Ractive/prototype/shared/fireEvent'; + // This function may be overwritten, if the event directive // includes parameters export default function EventHandler$fire ( event ) { - this.root.fire( this.getAction(), event ); + fireEvent( this.root, this.getAction(), { event: event } ); } diff --git a/src/virtualdom/items/Element/EventHandler/prototype/init.js b/src/virtualdom/items/Element/EventHandler/prototype/init.js index 4a8e4f3056..3ddcc1579d 100644 --- a/src/virtualdom/items/Element/EventHandler/prototype/init.js +++ b/src/virtualdom/items/Element/EventHandler/prototype/init.js @@ -1,4 +1,5 @@ import circular from 'circular'; +import fireEvent from 'Ractive/prototype/shared/fireEvent'; var Fragment, getValueOptions = { args: true }; @@ -43,7 +44,7 @@ export default function EventHandler$init ( element, name, template ) { } function fireEventWithParams ( event ) { - this.root.fire.apply( this.root, [ this.getAction(), event ].concat( this.params ) ); + fireEvent( this.root, this.getAction(), { event: event, args: this.params } ); } function fireEventWithDynamicParams ( event ) { @@ -54,5 +55,5 @@ function fireEventWithDynamicParams ( event ) { args = args.substr( 1, args.length - 2 ); } - this.root.fire.apply( this.root, [ this.getAction(), event ].concat( args ) ); + fireEvent( this.root, this.getAction(), { event: event, args: args } ); } diff --git a/test/modules/events.js b/test/modules/events.js index 00883b88b8..0a0572a8a4 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -666,13 +666,13 @@ define([ 'ractive' ], function ( Ractive ) { module( 'Events bubble up components' ); - test( 'Parents fire component namespaced events', function ( t ) { + test( '"*" prefixed event handlers bubble', function ( t ) { var ractive, Component, Middle; expect( 2 ); Component = Ractive.extend({ - template: 'click me' + template: 'click me' }); Middle = Ractive.extend({ @@ -688,11 +688,158 @@ define([ 'ractive' ], function ( Ractive ) { } }); - ractive.on( 'component.someEvent', function ( event ) { + ractive.on( 'someEvent', function ( event ) { t.ok( true ); t.equal( event.original.type, 'click' ); }); + ractive.on( 'component.someEvent', function ( event ) { + throw new Error('event should not be namespaced'); + }); + + simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); + }); + + test( 'non "*" prefixed event handlers do not bubble', function ( t ) { + var ractive, Component, Middle; + + expect( 0 ); + + Component = Ractive.extend({ + template: 'click me' + }); + + ractive = new Ractive({ + el: fixture, + template: '', + components: { + component: Component + } + }); + + ractive.on( 'someEvent', function ( event ) { + throw new Error('event should not bubble'); + }); + + simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); + }); + + test( '"on-*" attribute sets namespace for component events', function ( t ) { + var ractive, Component, Middle; + + expect( 2 ); + + Component = Ractive.extend({ + template: 'click me' + }); + + Middle = Ractive.extend({ + template: '' + }); + + ractive = new Ractive({ + el: fixture, + template: '', + components: { + component: Component, + middle: Middle + } + }); + + ractive.on( 'foo.someEvent', function ( event ) { + t.ok( true ); + }); + + ractive.findComponent('middle').on( 'foo.someEvent', function ( event ) { + t.ok( true ); + }); + + ractive.on( 'someEvent', function ( event ) { + throw new Error('event should not be namespaced'); + }); + + simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); + }); + + test( '"on-*" attribute defaults to component name', function ( t ) { + var ractive, Component, Middle; + + expect( 1 ); + + Component = Ractive.extend({ + template: 'click me' + }); + + ractive = new Ractive({ + el: fixture, + template: '', + components: { + component: Component + } + }); + + ractive.on( 'component.someEvent', function ( event ) { + t.ok( true ); + }); + + simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); + }); + + test( '"on-*" attribute with "*" value defaults to component name', function ( t ) { + var ractive, Component, Middle; + + expect( 1 ); + + Component = Ractive.extend({ + template: 'click me' + }); + + ractive = new Ractive({ + el: fixture, + template: '', + components: { + component: Component + } + }); + + ractive.on( 'component.someEvent', function ( event ) { + t.ok( true ); + }); + + simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); + }); + + test( 'bubble events can be stopped with event.stopBubble()', function ( t ) { + var ractive, Component, Middle; + + expect( 1 ); + + Component = Ractive.extend({ + template: 'click me' + }); + + Middle = Ractive.extend({ + template: '' + }); + + ractive = new Ractive({ + el: fixture, + template: '', + components: { + component: Component, + middle: Middle + } + }); + + ractive.findComponent('middle').on( 'someEvent', function ( event ) { + t.ok( true ); + event.stopBubble(); + }); + + ractive.on( 'someEvent', function ( event ) { + throw new Error('event should not bubbled'); + }); + simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); }); From 2ae94a5ccb973e3efe8a43ee6e734c9d9061100e Mon Sep 17 00:00:00 2001 From: marty Date: Sat, 16 Aug 2014 12:47:49 -0700 Subject: [PATCH 03/14] fix broken test from merge --- src/parse/converters/element.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parse/converters/element.js b/src/parse/converters/element.js index 0dc578b802..cd19f75a32 100644 --- a/src/parse/converters/element.js +++ b/src/parse/converters/element.js @@ -111,7 +111,7 @@ function getElement ( parser ) { // or is it hacky and should this be own template primitive??? else if ( namespacePattern.test( attribute.name ) ) { if ( !element.v ) element.v = {}; - directive = processDirective( attribute.value ); + directive = attribute.value ? processDirective( attribute.value ) : ''; addProxyEvent( '*', directive ); } From 9dbfa07b619c9e9429e936f31f7a9cfee51c8187 Mon Sep 17 00:00:00 2001 From: marty Date: Sat, 16 Aug 2014 14:26:42 -0700 Subject: [PATCH 04/14] ractive.fire has event object with context --- src/Ractive/prototype/fire.js | 12 +- src/Ractive/prototype/shared/fireEvent.js | 22 +++- .../Component/initialise/propagateEvents.js | 16 ++- .../Element/EventHandler/prototype/listen.js | 3 +- .../EventHandler/shared/EventObject.js | 14 +++ .../EventHandler/shared/genericHandler.js | 6 +- test/modules/components.js | 19 +++ test/modules/events.js | 109 +++++++++++++----- 8 files changed, 156 insertions(+), 45 deletions(-) create mode 100644 src/virtualdom/items/Element/EventHandler/shared/EventObject.js diff --git a/src/Ractive/prototype/fire.js b/src/Ractive/prototype/fire.js index 32d9b89fb4..938fe19a06 100644 --- a/src/Ractive/prototype/fire.js +++ b/src/Ractive/prototype/fire.js @@ -1,10 +1,16 @@ +import EventObject from 'virtualdom/items/Element/EventHandler/shared/EventObject'; import fireEvent from 'Ractive/prototype/shared/fireEvent'; export default function Ractive$fire ( eventName ) { + var options = { - args: Array.prototype.slice.call( arguments, 1 ) - // TODO: create event object - // event: {}; + args: Array.prototype.slice.call( arguments, 1 ), + event: new EventObject({ + component: this, + keypath: '', + context: this.data + }), + isFire: true }; fireEvent( this, eventName, options ); diff --git a/src/Ractive/prototype/shared/fireEvent.js b/src/Ractive/prototype/shared/fireEvent.js index 624f40ee9d..49a8d3ed68 100644 --- a/src/Ractive/prototype/shared/fireEvent.js +++ b/src/Ractive/prototype/shared/fireEvent.js @@ -1,15 +1,20 @@ export default function fireEvent ( ractive, eventName, options = {} ) { var bubble = ( eventName[0] === '*' ); + if ( bubble ) { eventName = eventName.substr(1); } - fireEventAs( ractive, eventName, options.event, options.args, bubble, true ); + if ( options.event ) { + options.event._bubble = bubble; + } + + fireEventAs( ractive, eventName, options.event, options.args, bubble, true, options.isFire ); } -function fireEventAs ( ractive, eventName, event, args, bubble, rootInstance ) { +function fireEventAs ( ractive, eventName, event, args, bubble, rootInstance, isFire ) { if ( !ractive ) { return; } @@ -26,6 +31,13 @@ function fireEventAs ( ractive, eventName, event, args, bubble, rootInstance ) if ( eventNamespace = ractive.component.eventNamespace ) { eventName = eventNamespace + '.' + eventName; } + + if ( isFire && ractive._parent && ractive.component ) { + let fragment = ractive.component.parentFragment; + event.index = fragment.indexRefs; + event.keypath = fragment.context; + event.context = ractive._parent.get( event.keypath ) + } } fireEventAs( ractive._parent, eventName, event, args, bubble ); @@ -34,11 +46,9 @@ function fireEventAs ( ractive, eventName, event, args, bubble, rootInstance ) function notifySubscribers ( ractive, subscribers, event, args ) { - var i = 0, len = 0, originalEvent = null, stopEvent = false, bubble = true; + var i = 0, len = 0, originalEvent = null, stopEvent = false; if ( event ) { - // TODO: create eventobject with proto method - event.stopBubble = function () { bubble = false; }; args = [ event ].concat( args ); } @@ -53,7 +63,7 @@ function notifySubscribers ( ractive, subscribers, event, args ) { originalEvent.stopPropagation && originalEvent.stopPropagation(); } - return bubble; + return event ? event._bubble : true; } diff --git a/src/virtualdom/items/Component/initialise/propagateEvents.js b/src/virtualdom/items/Component/initialise/propagateEvents.js index 82dc87a70b..a091d0d978 100644 --- a/src/virtualdom/items/Component/initialise/propagateEvents.js +++ b/src/virtualdom/items/Component/initialise/propagateEvents.js @@ -1,4 +1,5 @@ import fireEvent from 'Ractive/prototype/shared/fireEvent'; +import EventObject from 'virtualdom/items/Element/EventHandler/shared/EventObject'; import log from 'utils/log'; // TODO how should event arguments be handled? e.g. @@ -42,8 +43,17 @@ function propagateEvent ( childInstance, parentInstance, eventName, proxyEventNa } childInstance.on( eventName, function () { - var args = Array.prototype.slice.call( arguments ); - - fireEvent( parentInstance, proxyEventName, { args: args } ); + var fragment = this.component.parentFragment, + options = { + event: new EventObject({ + component: this, + index: fragment.indexRefs, + keypath: fragment.context, + context: parentInstance.get( fragment.context ) + }), + args: Array.prototype.slice.call( arguments ), + }; + + fireEvent( parentInstance, proxyEventName, options ); }); } diff --git a/src/virtualdom/items/Element/EventHandler/prototype/listen.js b/src/virtualdom/items/Element/EventHandler/prototype/listen.js index 0623951fdd..17906832d1 100644 --- a/src/virtualdom/items/Element/EventHandler/prototype/listen.js +++ b/src/virtualdom/items/Element/EventHandler/prototype/listen.js @@ -1,5 +1,6 @@ import warn from 'utils/warn'; import config from 'config/config'; +import EventObject from 'virtualdom/items/Element/EventHandler/shared/EventObject'; import genericHandler from 'virtualdom/items/Element/EventHandler/shared/genericHandler'; var customHandlers = {}; @@ -32,7 +33,7 @@ function getCustomHandler ( name ) { event.keypath = storage.keypath; event.context = storage.root.get( storage.keypath ); - storage.events[ name ].fire( event ); + storage.events[ name ].fire( new EventObject( event ) ); }; } diff --git a/src/virtualdom/items/Element/EventHandler/shared/EventObject.js b/src/virtualdom/items/Element/EventHandler/shared/EventObject.js new file mode 100644 index 0000000000..b22e70c9c9 --- /dev/null +++ b/src/virtualdom/items/Element/EventHandler/shared/EventObject.js @@ -0,0 +1,14 @@ +function EventObject ( options ) { + this.node = options.node; + this.original = options.original; + this.index = options.index; + this.keypath = options.keypath; + this.context = options.context; + this.component = options.component; +} + +EventObject.prototype.stopBubble = function () { + this._bubble = false; +} + +export default EventObject diff --git a/src/virtualdom/items/Element/EventHandler/shared/genericHandler.js b/src/virtualdom/items/Element/EventHandler/shared/genericHandler.js index e8334b9ffd..4dcbeaa6ac 100644 --- a/src/virtualdom/items/Element/EventHandler/shared/genericHandler.js +++ b/src/virtualdom/items/Element/EventHandler/shared/genericHandler.js @@ -1,14 +1,16 @@ +import EventObject from 'virtualdom/items/Element/EventHandler/shared/EventObject'; + export default function genericHandler ( event ) { var storage, handler; storage = this._ractive; handler = storage.events[ event.type ]; - handler.fire({ + handler.fire( new EventObject({ node: this, original: event, index: storage.index, keypath: storage.keypath, context: storage.root.get( storage.keypath ) - }); + })); } diff --git a/test/modules/components.js b/test/modules/components.js index 5603083420..d55d6d3638 100644 --- a/test/modules/components.js +++ b/test/modules/components.js @@ -1512,6 +1512,25 @@ define([ 'ractive', 'helpers/Model', 'utils/log' ], function ( Ractive, Model, l console.warn = warn; }); + test( 'Component events use their own context and paramters', function ( t ) { + var ractive = new Ractive({ + el: fixture, + template: '{{#context}}{{/}}', + components: { + widget: Ractive.extend({ + template: 'works? {{works}}' + }) + }, + data: { + context: { + works: 'yes' + } + } + }); + + t.htmlEqual( fixture.innerHTML, 'works? yes' ); + }); + }; }); diff --git a/test/modules/events.js b/test/modules/events.js index 0a0572a8a4..854857727c 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -843,59 +843,108 @@ define([ 'ractive' ], function ( Ractive ) { simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); }); - /* - test( 'Component events fire both non-namespaced and namespaced events', function ( t ) { - var ractive, middle, Component, Middle; + test( 'ractive.fire events proxied through component have event object with context', function ( t) { + var ractive, Component, items, component; - expect( 3 ); + expect( 5 ); Component = Ractive.extend({ - template: 'click me', - init: function () { - this.on( 'someEvent', function () { - this.fire( 'newEvent' ); - }.bind( this ) ); - } + template: 'foo' }); - Middle = Ractive.extend({ - template: '' + items = [ { is: 'a' }, { is: 'b' }, { is: 'c' } ]; + + ractive = new Ractive({ + el: fixture, + template: '{{#items:i}}{{/}}', + data: { items: items }, + components: { component: Component } + }); + + component = ractive.findAllComponents('component')[1]; + + ractive.on( 'bar', function ( event ) { + t.ok( event ); + t.equal( event.component, component ); + t.equal( event.index.i, 1 ); + t.equal( event.context, items[1] ); + t.equal( event.keypath, 'items.1' ); + }); + component.fire('foo'); + + }); + + test( 'ractive.fire events have event object with root and bubble context', function ( t) { + var ractive, Component, items, component; + + expect( 10 ); + + Component = Ractive.extend({ + template: 'foo' + }); + + items = [ { is: 'a' }, { is: 'b' }, { is: 'c' } ]; + ractive = new Ractive({ el: fixture, - template: '', - components: { - component: Component, - middle: Middle - } + template: '{{#items:i}}{{/}}', + data: { items: items }, + components: { component: Component } }); - middle = ractive.findComponent( 'middle' ); + component = ractive.findAllComponents('component')[1]; - middle.on( 'newEvent', function ( event ) { - t.ok( true ); + component.on( 'foo', function ( event ) { + t.ok( event ); + t.equal( event.component, component ); + t.ok( !event.index ); + t.equal( event.context.foo, 'bar' ); + t.equal( event.keypath, '' ); }); - middle.on( 'component.newEvent', function ( event ) { - t.ok( true ); + ractive.on( 'foo', function ( event ) { + t.ok( event ); + t.equal( event.component, component ); + t.equal( event.index.i, 1 ); + t.equal( event.context, items[1] ); + t.equal( event.keypath, 'items.1' ); + + }); + + component.fire('*foo'); + }); + + test( 'ractive.fire events can have bubble cancelled', function ( t) { + var ractive, Component, items, component; + + expect( 1 ); + + Component = Ractive.extend({ + template: 'foo' }); - ractive.on( 'newEvent', function ( event ) { - throw new Error( 'non-namespaced event should not fire above immediate parent' ); + ractive = new Ractive({ + el: fixture, + template: '', + components: { component: Component } }); - ractive.on( 'component.newEvent', function ( event ) { - t.ok( true ); + component = ractive.findComponent('component'); + + component.on( 'foo', function ( event ) { + t.ok( event.stopBubble ); + event.stopBubble(); }); - ractive.on( 'middle.newEvent', function ( event ) { - throw new Error( 'namespaced event of immediate parent should not fire' ); + ractive.on( 'foo', function ( event ) { + throw new Error('event should not bubble') }); - simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); + component.fire('*foo'); }); - */ + }; }); From 9e9d896b4389b766a4494b441ecfd1690789c80e Mon Sep 17 00:00:00 2001 From: marty Date: Sat, 16 Aug 2014 14:29:08 -0700 Subject: [PATCH 05/14] linting --- src/Ractive/prototype/shared/fireEvent.js | 2 +- .../items/Element/EventHandler/shared/EventObject.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ractive/prototype/shared/fireEvent.js b/src/Ractive/prototype/shared/fireEvent.js index 49a8d3ed68..ccfe3fc36d 100644 --- a/src/Ractive/prototype/shared/fireEvent.js +++ b/src/Ractive/prototype/shared/fireEvent.js @@ -36,7 +36,7 @@ function fireEventAs ( ractive, eventName, event, args, bubble, rootInstance, i let fragment = ractive.component.parentFragment; event.index = fragment.indexRefs; event.keypath = fragment.context; - event.context = ractive._parent.get( event.keypath ) + event.context = ractive._parent.get( event.keypath ); } } diff --git a/src/virtualdom/items/Element/EventHandler/shared/EventObject.js b/src/virtualdom/items/Element/EventHandler/shared/EventObject.js index b22e70c9c9..6ae3bc6acf 100644 --- a/src/virtualdom/items/Element/EventHandler/shared/EventObject.js +++ b/src/virtualdom/items/Element/EventHandler/shared/EventObject.js @@ -9,6 +9,6 @@ function EventObject ( options ) { EventObject.prototype.stopBubble = function () { this._bubble = false; -} +}; -export default EventObject +export default EventObject; From 220c7b8dc78f3d58add7cec9fdcc46ef0ba35e56 Mon Sep 17 00:00:00 2001 From: marty Date: Sat, 16 Aug 2014 14:48:08 -0700 Subject: [PATCH 06/14] remove EventObject in lieu of adding method when bubbling --- src/Ractive/prototype/fire.js | 13 +++++------ src/Ractive/prototype/shared/fireEvent.js | 22 +++++++++++++------ .../Component/initialise/propagateEvents.js | 5 ++--- .../Element/EventHandler/prototype/listen.js | 3 +-- .../EventHandler/shared/EventObject.js | 14 ------------ .../EventHandler/shared/genericHandler.js | 6 ++--- 6 files changed, 26 insertions(+), 37 deletions(-) delete mode 100644 src/virtualdom/items/Element/EventHandler/shared/EventObject.js diff --git a/src/Ractive/prototype/fire.js b/src/Ractive/prototype/fire.js index 938fe19a06..7f828563d1 100644 --- a/src/Ractive/prototype/fire.js +++ b/src/Ractive/prototype/fire.js @@ -1,16 +1,15 @@ -import EventObject from 'virtualdom/items/Element/EventHandler/shared/EventObject'; import fireEvent from 'Ractive/prototype/shared/fireEvent'; export default function Ractive$fire ( eventName ) { var options = { args: Array.prototype.slice.call( arguments, 1 ), - event: new EventObject({ - component: this, - keypath: '', - context: this.data - }), - isFire: true + event: { + component: this, + keypath: '', + context: this.data + }, + changeBubbleContext: true }; fireEvent( this, eventName, options ); diff --git a/src/Ractive/prototype/shared/fireEvent.js b/src/Ractive/prototype/shared/fireEvent.js index ccfe3fc36d..951b9cbf06 100644 --- a/src/Ractive/prototype/shared/fireEvent.js +++ b/src/Ractive/prototype/shared/fireEvent.js @@ -4,17 +4,25 @@ export default function fireEvent ( ractive, eventName, options = {} ) { if ( bubble ) { eventName = eventName.substr(1); - } - if ( options.event ) { - options.event._bubble = bubble; + if ( options.event ) { + options.event._bubble = true; + options.event.stopBubble = function () { this._bubble = false; } + } } - fireEventAs( ractive, eventName, options.event, options.args, bubble, true, options.isFire ); + fireEventAs( + ractive, + eventName, + options.event, + options.args, + bubble, + true, // root fire + options.changeBubbleContext ); } -function fireEventAs ( ractive, eventName, event, args, bubble, rootInstance, isFire ) { +function fireEventAs ( ractive, eventName, event, args, bubble, rootInstance, changeContext ) { if ( !ractive ) { return; } @@ -32,7 +40,7 @@ function fireEventAs ( ractive, eventName, event, args, bubble, rootInstance, i eventName = eventNamespace + '.' + eventName; } - if ( isFire && ractive._parent && ractive.component ) { + if ( changeContext && ractive._parent && ractive.component ) { let fragment = ractive.component.parentFragment; event.index = fragment.indexRefs; event.keypath = fragment.context; @@ -46,7 +54,7 @@ function fireEventAs ( ractive, eventName, event, args, bubble, rootInstance, i function notifySubscribers ( ractive, subscribers, event, args ) { - var i = 0, len = 0, originalEvent = null, stopEvent = false; + var i = 0, len = 0, originalEvent = null, bubble = true, stopEvent = false; if ( event ) { args = [ event ].concat( args ); diff --git a/src/virtualdom/items/Component/initialise/propagateEvents.js b/src/virtualdom/items/Component/initialise/propagateEvents.js index a091d0d978..c22cfc8157 100644 --- a/src/virtualdom/items/Component/initialise/propagateEvents.js +++ b/src/virtualdom/items/Component/initialise/propagateEvents.js @@ -1,5 +1,4 @@ import fireEvent from 'Ractive/prototype/shared/fireEvent'; -import EventObject from 'virtualdom/items/Element/EventHandler/shared/EventObject'; import log from 'utils/log'; // TODO how should event arguments be handled? e.g. @@ -45,12 +44,12 @@ function propagateEvent ( childInstance, parentInstance, eventName, proxyEventNa childInstance.on( eventName, function () { var fragment = this.component.parentFragment, options = { - event: new EventObject({ + event: { component: this, index: fragment.indexRefs, keypath: fragment.context, context: parentInstance.get( fragment.context ) - }), + }, args: Array.prototype.slice.call( arguments ), }; diff --git a/src/virtualdom/items/Element/EventHandler/prototype/listen.js b/src/virtualdom/items/Element/EventHandler/prototype/listen.js index 17906832d1..0623951fdd 100644 --- a/src/virtualdom/items/Element/EventHandler/prototype/listen.js +++ b/src/virtualdom/items/Element/EventHandler/prototype/listen.js @@ -1,6 +1,5 @@ import warn from 'utils/warn'; import config from 'config/config'; -import EventObject from 'virtualdom/items/Element/EventHandler/shared/EventObject'; import genericHandler from 'virtualdom/items/Element/EventHandler/shared/genericHandler'; var customHandlers = {}; @@ -33,7 +32,7 @@ function getCustomHandler ( name ) { event.keypath = storage.keypath; event.context = storage.root.get( storage.keypath ); - storage.events[ name ].fire( new EventObject( event ) ); + storage.events[ name ].fire( event ); }; } diff --git a/src/virtualdom/items/Element/EventHandler/shared/EventObject.js b/src/virtualdom/items/Element/EventHandler/shared/EventObject.js deleted file mode 100644 index 6ae3bc6acf..0000000000 --- a/src/virtualdom/items/Element/EventHandler/shared/EventObject.js +++ /dev/null @@ -1,14 +0,0 @@ -function EventObject ( options ) { - this.node = options.node; - this.original = options.original; - this.index = options.index; - this.keypath = options.keypath; - this.context = options.context; - this.component = options.component; -} - -EventObject.prototype.stopBubble = function () { - this._bubble = false; -}; - -export default EventObject; diff --git a/src/virtualdom/items/Element/EventHandler/shared/genericHandler.js b/src/virtualdom/items/Element/EventHandler/shared/genericHandler.js index 4dcbeaa6ac..e8334b9ffd 100644 --- a/src/virtualdom/items/Element/EventHandler/shared/genericHandler.js +++ b/src/virtualdom/items/Element/EventHandler/shared/genericHandler.js @@ -1,16 +1,14 @@ -import EventObject from 'virtualdom/items/Element/EventHandler/shared/EventObject'; - export default function genericHandler ( event ) { var storage, handler; storage = this._ractive; handler = storage.events[ event.type ]; - handler.fire( new EventObject({ + handler.fire({ node: this, original: event, index: storage.index, keypath: storage.keypath, context: storage.root.get( storage.keypath ) - })); + }); } From 4dbda0cc5a1aba69e503b80d1b8d3cf12f61551d Mon Sep 17 00:00:00 2001 From: marty Date: Sun, 24 Aug 2014 11:55:21 -0700 Subject: [PATCH 07/14] event bubbling changes --- src/Ractive/prototype/shared/fireEvent.js | 11 ++-- src/config/defaults/options.js | 8 +-- src/parse/converters/element.js | 12 ++-- .../_ComponentEventHandler.js | 30 ++++++++++ .../ComponentEventHandler/prototype/listen.js | 15 +++++ .../Component/initialise/propagateEvents.js | 47 +++++++++++----- .../Element/EventHandler/_EventHandler.js | 21 ++++--- .../Element/EventHandler/prototype/init.js | 2 - .../animateStyle/createTransitions.js | 1 + .../shared/EventHandler/prototype/bubble.js | 10 ++++ .../shared/EventHandler/prototype/fire.js | 7 +++ .../EventHandler/prototype/getAction.js | 3 + .../shared/EventHandler/prototype/init.js | 56 +++++++++++++++++++ .../shared/EventHandler/prototype/rebind.js | 9 +++ .../shared/EventHandler/prototype/render.js | 10 ++++ .../shared/EventHandler/prototype/teardown.js | 11 ++++ .../shared/EventHandler/prototype/unrender.js | 15 +++++ test/modules/components.js | 19 ------- test/modules/events.js | 50 +++++++++++++++-- 19 files changed, 269 insertions(+), 68 deletions(-) create mode 100644 src/virtualdom/items/Component/ComponentEventHandler/_ComponentEventHandler.js create mode 100644 src/virtualdom/items/Component/ComponentEventHandler/prototype/listen.js create mode 100644 src/virtualdom/items/shared/EventHandler/prototype/bubble.js create mode 100644 src/virtualdom/items/shared/EventHandler/prototype/fire.js create mode 100644 src/virtualdom/items/shared/EventHandler/prototype/getAction.js create mode 100644 src/virtualdom/items/shared/EventHandler/prototype/init.js create mode 100644 src/virtualdom/items/shared/EventHandler/prototype/rebind.js create mode 100644 src/virtualdom/items/shared/EventHandler/prototype/render.js create mode 100644 src/virtualdom/items/shared/EventHandler/prototype/teardown.js create mode 100644 src/virtualdom/items/shared/EventHandler/prototype/unrender.js diff --git a/src/Ractive/prototype/shared/fireEvent.js b/src/Ractive/prototype/shared/fireEvent.js index 951b9cbf06..75dc1aa6af 100644 --- a/src/Ractive/prototype/shared/fireEvent.js +++ b/src/Ractive/prototype/shared/fireEvent.js @@ -1,9 +1,6 @@ export default function fireEvent ( ractive, eventName, options = {} ) { - var bubble = ( eventName[0] === '*' ); - - if ( bubble ) { - eventName = eventName.substr(1); + if ( ractive.bubble ) { if ( options.event ) { options.event._bubble = true; @@ -16,7 +13,7 @@ export default function fireEvent ( ractive, eventName, options = {} ) { eventName, options.event, options.args, - bubble, + ractive.bubble, true, // root fire options.changeBubbleContext ); @@ -29,7 +26,7 @@ function fireEventAs ( ractive, eventName, event, args, bubble, rootInstance, c var subscribers = ractive._subs[ eventName ]; if ( subscribers ) { - bubble = notifySubscribers( ractive, subscribers, event, args ) && bubble; + bubble = notifySubscribers( ractive, subscribers, event, args ) && bubble && ractive.bubble; } if ( bubble ) { @@ -54,7 +51,7 @@ function fireEventAs ( ractive, eventName, event, args, bubble, rootInstance, c function notifySubscribers ( ractive, subscribers, event, args ) { - var i = 0, len = 0, originalEvent = null, bubble = true, stopEvent = false; + var i = 0, len = 0, originalEvent = null, stopEvent = false; if ( event ) { args = [ event ].concat( args ); diff --git a/src/config/defaults/options.js b/src/config/defaults/options.js index 382d467fe8..667973c145 100644 --- a/src/config/defaults/options.js +++ b/src/config/defaults/options.js @@ -1,8 +1,3 @@ -// These are both the values for Ractive.defaults -// as well as the determination for whether an option -// value will be placed on Component.defaults -// (versus directly on Component) during an extend operation - var defaultOptions = { // render placement: @@ -27,6 +22,9 @@ var defaultOptions = { twoway: true, lazy: false, + //events: + bubble: false, + // transitions: noIntro: false, transitionsEnabled: true, diff --git a/src/parse/converters/element.js b/src/parse/converters/element.js index cd19f75a32..69ffb011ff 100644 --- a/src/parse/converters/element.js +++ b/src/parse/converters/element.js @@ -10,7 +10,7 @@ import processDirective from 'parse/converters/element/processDirective'; var tagNamePattern = /^[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/, validTagNameFollower = /^[\s\n\/>]/, onPattern = /^on/, - namespacePattern = /^on-\\*/, + // namespacePattern = /^on-\\*/, proxyEventPattern = /^on-([a-zA-Z$_][a-zA-Z$_0-9\-]+)$/, reservedEventNames = /^(?:change|reset|teardown|update)$/, directives = { 'intro-outro': 't0', intro: 't1', outro: 't2', decorator: 'o' }, @@ -109,11 +109,11 @@ function getElement ( parser ) { // on-* namespace // is this ok to create faux-directive? // or is it hacky and should this be own template primitive??? - else if ( namespacePattern.test( attribute.name ) ) { - if ( !element.v ) element.v = {}; - directive = attribute.value ? processDirective( attribute.value ) : ''; - addProxyEvent( '*', directive ); - } + // else if ( namespacePattern.test( attribute.name ) ) { + // if ( !element.v ) element.v = {}; + // directive = attribute.value ? processDirective( attribute.value ) : ''; + // addProxyEvent( '*', directive ); + // } else { if ( !parser.sanitizeEventAttributes || !onPattern.test( attribute.name ) ) { diff --git a/src/virtualdom/items/Component/ComponentEventHandler/_ComponentEventHandler.js b/src/virtualdom/items/Component/ComponentEventHandler/_ComponentEventHandler.js new file mode 100644 index 0000000000..cf19479896 --- /dev/null +++ b/src/virtualdom/items/Component/ComponentEventHandler/_ComponentEventHandler.js @@ -0,0 +1,30 @@ +import bubble from 'virtualdom/items/shared/EventHandler/prototype/bubble'; +import fire from 'virtualdom/items/shared/EventHandler/prototype/fire'; +import getAction from 'virtualdom/items/shared/EventHandler/prototype/getAction'; +import init from 'virtualdom/items/shared/EventHandler/prototype/init'; +import rebind from 'virtualdom/items/shared/EventHandler/prototype/rebind'; +import render from 'virtualdom/items/shared/EventHandler/prototype/render'; +import teardown from 'virtualdom/items/shared/EventHandler/prototype/teardown'; +import unrender from 'virtualdom/items/shared/EventHandler/prototype/unrender'; + + +import listen from 'virtualdom/items/Component/EventHandler/prototype/listen'; + +var ComponentEventHandler = function ( component, name, template ) { + this.component = component + this.init( component.root, name, template ); +}; + +ComponentEventHandler.prototype = { + bubble: bubble, + fire: fire, + getAction: getAction, + init: init, + listen: listen, + rebind: rebind, + render: render, + teardown: teardown, + unrender: unrender +}; + +export default ComponentEventHandler; diff --git a/src/virtualdom/items/Component/ComponentEventHandler/prototype/listen.js b/src/virtualdom/items/Component/ComponentEventHandler/prototype/listen.js new file mode 100644 index 0000000000..7fc336af61 --- /dev/null +++ b/src/virtualdom/items/Component/ComponentEventHandler/prototype/listen.js @@ -0,0 +1,15 @@ +import warn from 'utils/warn'; +import config from 'config/config'; +import genericHandler from 'virtualdom/items/Element/EventHandler/shared/genericHandler'; + +var customHandlers = {}; + +export default function EventHandler$listen () { + + var definition, name = this.name; + + // subscribe to child event + + // this.hasListener = true; + +} diff --git a/src/virtualdom/items/Component/initialise/propagateEvents.js b/src/virtualdom/items/Component/initialise/propagateEvents.js index c22cfc8157..af10ef7667 100644 --- a/src/virtualdom/items/Component/initialise/propagateEvents.js +++ b/src/virtualdom/items/Component/initialise/propagateEvents.js @@ -1,3 +1,4 @@ +import circular from 'circular'; import fireEvent from 'Ractive/prototype/shared/fireEvent'; import log from 'utils/log'; @@ -7,26 +8,44 @@ import log from 'utils/log'; // when 'foo' fires on the child, but the 1,2,3 arguments // will be lost +var Fragment, getValueOptions = { args: true }; + +circular.push( function () { + Fragment = circular.Fragment; +}); + export default function propagateEvents ( component, eventsDescriptor ) { var eventName; for ( eventName in eventsDescriptor ) { if ( eventsDescriptor.hasOwnProperty( eventName ) ) { - if( eventName === '*' ) { - // TODO: allow dynamic fragments - let namespace = eventsDescriptor[ eventName ]; - if ( typeof namespace === 'string' ) { - namespace = namespace.trim(); - } - if ( !namespace || namespace === '*' || ( namespace.d && !namespace.d.length ) ) { - namespace = component.name; - } - - component.eventNamespace = namespace; - - } else { + // if( eventName === '*' ) { + // To be determined, I don't thin this is right way go... + + // // TODO: allow dynamic fragments + // let namespace = eventsDescriptor[ eventName ]; + // namespace = namespace.n || namespace; + + // if ( typeof namespace !== 'string' ) { + // namespace = = new Fragment({ + // template: action, + // root: this.root, + // owner: this + // }); + + + // namespace = namespace.trim(); + // } + // else if ( namespace && namespace.n && namespace.n.length ) + // if ( !namespace || namespace === '*' || ( namespace.n && !namespace.n.length ) ) { + // namespace = component.name; + // } + + // component.eventNamespace = namespace; + + // } else { propagateEvent( component.instance, component.root, eventName, eventsDescriptor[ eventName ] ); - } + // } } } } diff --git a/src/virtualdom/items/Element/EventHandler/_EventHandler.js b/src/virtualdom/items/Element/EventHandler/_EventHandler.js index debd291f55..3c6df441b6 100644 --- a/src/virtualdom/items/Element/EventHandler/_EventHandler.js +++ b/src/virtualdom/items/Element/EventHandler/_EventHandler.js @@ -1,15 +1,18 @@ -import bubble from 'virtualdom/items/Element/EventHandler/prototype/bubble'; -import fire from 'virtualdom/items/Element/EventHandler/prototype/fire'; -import getAction from 'virtualdom/items/Element/EventHandler/prototype/getAction'; -import init from 'virtualdom/items/Element/EventHandler/prototype/init'; +import bubble from 'virtualdom/items/shared/EventHandler/prototype/bubble'; +import fire from 'virtualdom/items/shared/EventHandler/prototype/fire'; +import getAction from 'virtualdom/items/shared/EventHandler/prototype/getAction'; +import init from 'virtualdom/items/shared/EventHandler/prototype/init'; +import rebind from 'virtualdom/items/shared/EventHandler/prototype/rebind'; +import render from 'virtualdom/items/shared/EventHandler/prototype/render'; +import teardown from 'virtualdom/items/shared/EventHandler/prototype/teardown'; +import unrender from 'virtualdom/items/shared/EventHandler/prototype/unrender'; + + import listen from 'virtualdom/items/Element/EventHandler/prototype/listen'; -import rebind from 'virtualdom/items/Element/EventHandler/prototype/rebind'; -import render from 'virtualdom/items/Element/EventHandler/prototype/render'; -import teardown from 'virtualdom/items/Element/EventHandler/prototype/teardown'; -import unrender from 'virtualdom/items/Element/EventHandler/prototype/unrender'; var EventHandler = function ( element, name, template ) { - this.init( element, name, template ); + this.element = element; + this.init( element.root, name, template ); }; EventHandler.prototype = { diff --git a/src/virtualdom/items/Element/EventHandler/prototype/init.js b/src/virtualdom/items/Element/EventHandler/prototype/init.js index 3ddcc1579d..509618ebcd 100644 --- a/src/virtualdom/items/Element/EventHandler/prototype/init.js +++ b/src/virtualdom/items/Element/EventHandler/prototype/init.js @@ -14,8 +14,6 @@ export default function EventHandler$init ( element, name, template ) { this.root = element.root; this.name = name; - this.proxies = []; - // Get action ('foo' in 'on-click='foo') action = template.n || template; if ( typeof action !== 'string' ) { diff --git a/src/virtualdom/items/Element/Transition/prototype/animateStyle/createTransitions.js b/src/virtualdom/items/Element/Transition/prototype/animateStyle/createTransitions.js index 2ffaddbb28..833a76eb40 100644 --- a/src/virtualdom/items/Element/Transition/prototype/animateStyle/createTransitions.js +++ b/src/virtualdom/items/Element/Transition/prototype/animateStyle/createTransitions.js @@ -55,6 +55,7 @@ if ( !isClient ) { checkComplete = function () { if ( jsTransitionsComplete && cssTransitionsComplete ) { + // will changes to events and fire have an unexpected consequence here? t.root.fire( t.name + ':end', t.node, t.isIntro ); resolve(); } diff --git a/src/virtualdom/items/shared/EventHandler/prototype/bubble.js b/src/virtualdom/items/shared/EventHandler/prototype/bubble.js new file mode 100644 index 0000000000..e0d0ad5f93 --- /dev/null +++ b/src/virtualdom/items/shared/EventHandler/prototype/bubble.js @@ -0,0 +1,10 @@ +export default function EventHandler$bubble () { + var hasAction = this.getAction(); + + if( hasAction && !this.hasListener ) { + this.listen(); + } + else if ( !hasAction && this.hasListener ) { + this.unrender(); + } +} diff --git a/src/virtualdom/items/shared/EventHandler/prototype/fire.js b/src/virtualdom/items/shared/EventHandler/prototype/fire.js new file mode 100644 index 0000000000..7a718b1cae --- /dev/null +++ b/src/virtualdom/items/shared/EventHandler/prototype/fire.js @@ -0,0 +1,7 @@ +import fireEvent from 'Ractive/prototype/shared/fireEvent'; + +// This function may be overwritten, if the event directive +// includes parameters +export default function EventHandler$fire ( event ) { + fireEvent( this.root, this.getAction(), { event: event } ); +} diff --git a/src/virtualdom/items/shared/EventHandler/prototype/getAction.js b/src/virtualdom/items/shared/EventHandler/prototype/getAction.js new file mode 100644 index 0000000000..9d4dd38c31 --- /dev/null +++ b/src/virtualdom/items/shared/EventHandler/prototype/getAction.js @@ -0,0 +1,3 @@ +export default function EventHandler$getAction () { + return this.action.toString().trim(); +} diff --git a/src/virtualdom/items/shared/EventHandler/prototype/init.js b/src/virtualdom/items/shared/EventHandler/prototype/init.js new file mode 100644 index 0000000000..382aca6c51 --- /dev/null +++ b/src/virtualdom/items/shared/EventHandler/prototype/init.js @@ -0,0 +1,56 @@ +import circular from 'circular'; +import fireEvent from 'Ractive/prototype/shared/fireEvent'; + +var Fragment, getValueOptions = { args: true }; + +circular.push( function () { + Fragment = circular.Fragment; +}); + +export default function EventHandler$init ( root, name, template ) { + var action; + + this.root = root; + this.name = name; + + // Get action ('foo' in 'on-click='foo') + action = template.n || template; + if ( typeof action !== 'string' ) { + action = new Fragment({ + template: action, + root: this.root, + owner: this + }); + } + + this.action = action; + + // Get parameters + if ( template.d ) { + this.dynamicParams = new Fragment({ + template: template.d, + root: this.root, + owner: this.element + }); + + this.fire = fireEventWithDynamicParams; + } else if ( template.a ) { + this.params = template.a; + this.fire = fireEventWithParams; + } +} + +function fireEventWithParams ( event ) { + fireEvent( this.root, this.getAction(), { event: event, args: this.params } ); +} + +function fireEventWithDynamicParams ( event ) { + var args = this.dynamicParams.getValue( getValueOptions ); + + // need to strip [] from ends if a string! + if ( typeof args === 'string' ) { + args = args.substr( 1, args.length - 2 ); + } + + fireEvent( this.root, this.getAction(), { event: event, args: args } ); +} diff --git a/src/virtualdom/items/shared/EventHandler/prototype/rebind.js b/src/virtualdom/items/shared/EventHandler/prototype/rebind.js new file mode 100644 index 0000000000..5de673a750 --- /dev/null +++ b/src/virtualdom/items/shared/EventHandler/prototype/rebind.js @@ -0,0 +1,9 @@ +export default function EventHandler$rebind ( indexRef, newIndex, oldKeypath, newKeypath ) { + if ( typeof this.action !== 'string' ) { + this.action.rebind( indexRef, newIndex, oldKeypath, newKeypath ); + } + + if ( this.dynamicParams ) { + this.dynamicParams.rebind( indexRef, newIndex, oldKeypath, newKeypath ); + } +} diff --git a/src/virtualdom/items/shared/EventHandler/prototype/render.js b/src/virtualdom/items/shared/EventHandler/prototype/render.js new file mode 100644 index 0000000000..3c0e98bdc6 --- /dev/null +++ b/src/virtualdom/items/shared/EventHandler/prototype/render.js @@ -0,0 +1,10 @@ +export default function EventHandler$render () { + this.node = this.element.node; + // store this on the node itself, so it can be retrieved by a + // universal handler + this.node._ractive.events[ this.name ] = this; + + if ( this.getAction() ) { + this.listen(); + } +} diff --git a/src/virtualdom/items/shared/EventHandler/prototype/teardown.js b/src/virtualdom/items/shared/EventHandler/prototype/teardown.js new file mode 100644 index 0000000000..d8bb8fbd60 --- /dev/null +++ b/src/virtualdom/items/shared/EventHandler/prototype/teardown.js @@ -0,0 +1,11 @@ +export default function EventHandler$teardown () { + // Tear down dynamic name + if ( typeof this.action !== 'string' ) { + this.action.teardown(); + } + + // Tear down dynamic parameters + if ( this.dynamicParams ) { + this.dynamicParams.teardown(); + } +} diff --git a/src/virtualdom/items/shared/EventHandler/prototype/unrender.js b/src/virtualdom/items/shared/EventHandler/prototype/unrender.js new file mode 100644 index 0000000000..5dc641cc1c --- /dev/null +++ b/src/virtualdom/items/shared/EventHandler/prototype/unrender.js @@ -0,0 +1,15 @@ +import genericHandler from 'virtualdom/items/Element/EventHandler/shared/genericHandler'; + +export default function EventHandler$unrender () { + + if ( this.custom ) { + this.custom.teardown(); + } + + else { + this.node.removeEventListener( this.name, genericHandler, false ); + } + + this.hasListener = false; + +} diff --git a/test/modules/components.js b/test/modules/components.js index d55d6d3638..5603083420 100644 --- a/test/modules/components.js +++ b/test/modules/components.js @@ -1512,25 +1512,6 @@ define([ 'ractive', 'helpers/Model', 'utils/log' ], function ( Ractive, Model, l console.warn = warn; }); - test( 'Component events use their own context and paramters', function ( t ) { - var ractive = new Ractive({ - el: fixture, - template: '{{#context}}{{/}}', - components: { - widget: Ractive.extend({ - template: 'works? {{works}}' - }) - }, - data: { - context: { - works: 'yes' - } - } - }); - - t.htmlEqual( fixture.innerHTML, 'works? yes' ); - }); - }; }); diff --git a/test/modules/events.js b/test/modules/events.js index 854857727c..733a716087 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -664,15 +664,25 @@ define([ 'ractive' ], function ( Ractive ) { simulant.fire( ractive.findAll( 'button' )[1], 'click' ); }); - module( 'Events bubble up components' ); - test( '"*" prefixed event handlers bubble', function ( t ) { + function resetBubble(){ + Ractive.prototype.bubble = false; + } + + module( 'Events bubble up components', { + setup: resetBubble, + teardown: resetBubble + }); + + test( 'bubble = true causes event handlers to bubble under "eventname" and "component.eventname"', function ( t ) { var ractive, Component, Middle; - expect( 2 ); + expect( 4 ); + + Ractive.defaults.bubble = true; Component = Ractive.extend({ - template: 'click me' + template: 'click me' }); Middle = Ractive.extend({ @@ -694,7 +704,8 @@ define([ 'ractive' ], function ( Ractive ) { }); ractive.on( 'component.someEvent', function ( event ) { - throw new Error('event should not be namespaced'); + t.ok( true ); + t.equal( event.original.type, 'click' ); }); simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); @@ -723,7 +734,7 @@ define([ 'ractive' ], function ( Ractive ) { simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); }); - +/* test( '"on-*" attribute sets namespace for component events', function ( t ) { var ractive, Component, Middle; @@ -809,6 +820,33 @@ define([ 'ractive' ], function ( Ractive ) { simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); }); + test( '"on-*" attribute with dynamic value changes handler', function ( t ) { + var ractive, Component, Middle; + + expect( 1 ); + + Component = Ractive.extend({ + template: 'click me' + }); + + ractive = new Ractive({ + el: fixture, + template: '', + data: { + foo: 'bar' + }, + components: { + component: Component + } + }); + + ractive.on( 'bar.someEvent', function ( event ) { + t.ok( true ); + }); + + simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); + }); +*/ test( 'bubble events can be stopped with event.stopBubble()', function ( t ) { var ractive, Component, Middle; From 325815ec0c31265cc37f1484eecc74ce4ade588f Mon Sep 17 00:00:00 2001 From: marty Date: Fri, 29 Aug 2014 16:24:17 -0700 Subject: [PATCH 08/14] bubble by default, set namespace in options --- src/Ractive/prototype/fire.js | 6 + src/Ractive/prototype/shared/fireEvent.js | 50 ++- src/config/defaults/options.js | 3 +- src/virtualdom/Fragment/prototype/bubble.js | 2 +- .../_ComponentEventHandler.js | 2 +- .../ComponentEventHandler/prototype/listen.js | 12 +- .../Component/initialise/propagateEvents.js | 30 +- test/modules/events.js | 404 ++++++++---------- 8 files changed, 217 insertions(+), 292 deletions(-) diff --git a/src/Ractive/prototype/fire.js b/src/Ractive/prototype/fire.js index 7f828563d1..886125acae 100644 --- a/src/Ractive/prototype/fire.js +++ b/src/Ractive/prototype/fire.js @@ -2,6 +2,12 @@ import fireEvent from 'Ractive/prototype/shared/fireEvent'; export default function Ractive$fire ( eventName ) { + // no auto-add of event arg + // var options = { + // args: Array.prototype.slice.call( arguments, 1 ) + // }; + + // create event object var options = { args: Array.prototype.slice.call( arguments, 1 ), event: { diff --git a/src/Ractive/prototype/shared/fireEvent.js b/src/Ractive/prototype/shared/fireEvent.js index 75dc1aa6af..4a3a4e862e 100644 --- a/src/Ractive/prototype/shared/fireEvent.js +++ b/src/Ractive/prototype/shared/fireEvent.js @@ -4,48 +4,62 @@ export default function fireEvent ( ractive, eventName, options = {} ) { if ( options.event ) { options.event._bubble = true; - options.event.stopBubble = function () { this._bubble = false; } + options.event.stopBubble = function () { options.event._bubble = false; }; } } fireEventAs( ractive, - eventName, + [ eventName ], options.event, options.args, ractive.bubble, - true, // root fire - options.changeBubbleContext ); + true + ); } -function fireEventAs ( ractive, eventName, event, args, bubble, rootInstance, changeContext ) { +function fireEventAs ( ractive, eventNames, event, args, bubble, initialFire ) { if ( !ractive ) { return; } - var subscribers = ractive._subs[ eventName ]; + var subscribers, i; - if ( subscribers ) { - bubble = notifySubscribers( ractive, subscribers, event, args ) && bubble && ractive.bubble; + // eventNames has both the provided event name and potentially the namespaced event + for ( i = 0; i < eventNames.length; i++ ) { + subscribers = ractive._subs[ eventNames[ i ] ]; + + if ( subscribers ) { + // to bubble or not to bubble: + // 1. this event doesn't cancel: bubble = + // 2. not already cancelled: && bubble + // 3. ractive instance allows bubble: && ractive.bubble + bubble = notifySubscribers( ractive, subscribers, event, args ) && bubble && ractive.bubble; + } } if ( bubble ) { - if ( rootInstance ) { - let eventNamespace = ''; - if ( eventNamespace = ractive.component.eventNamespace ) { - eventName = eventNamespace + '.' + eventName; + if ( initialFire && ractive.bubble !== 'nameOnly' ) { + // use the explicit namespace option if provided, otherwise component name + let namespace = ractive.namespace; + if( !namespace && ractive.component ) { + namespace = ractive.component.name; } - if ( changeContext && ractive._parent && ractive.component ) { - let fragment = ractive.component.parentFragment; - event.index = fragment.indexRefs; - event.keypath = fragment.context; - event.context = ractive._parent.get( event.keypath ); + if ( namespace ) { + namespace += '.' + eventNames[ 0 ]; + + if ( ractive.bubble === 'nsOnly' ) { + eventNames = [ namespace ]; + } + else { + eventNames.push( namespace ); + } } } - fireEventAs( ractive._parent, eventName, event, args, bubble ); + fireEventAs( ractive._parent, eventNames, event, args, bubble ); } } diff --git a/src/config/defaults/options.js b/src/config/defaults/options.js index 667973c145..18c0ddcfe3 100644 --- a/src/config/defaults/options.js +++ b/src/config/defaults/options.js @@ -23,7 +23,8 @@ var defaultOptions = { lazy: false, //events: - bubble: false, + bubble: true, + namespace: '', // transitions: noIntro: false, diff --git a/src/virtualdom/Fragment/prototype/bubble.js b/src/virtualdom/Fragment/prototype/bubble.js index 05ec936275..e783304c33 100644 --- a/src/virtualdom/Fragment/prototype/bubble.js +++ b/src/virtualdom/Fragment/prototype/bubble.js @@ -1,7 +1,7 @@ export default function Fragment$bubble () { this.dirtyValue = this.dirtyArgs = true; - if ( this.inited && this.owner.bubble ) { + if ( this.inited && typeof this.owner.bubble === 'function' ) { this.owner.bubble(); } } diff --git a/src/virtualdom/items/Component/ComponentEventHandler/_ComponentEventHandler.js b/src/virtualdom/items/Component/ComponentEventHandler/_ComponentEventHandler.js index cf19479896..987e6c377d 100644 --- a/src/virtualdom/items/Component/ComponentEventHandler/_ComponentEventHandler.js +++ b/src/virtualdom/items/Component/ComponentEventHandler/_ComponentEventHandler.js @@ -11,7 +11,7 @@ import unrender from 'virtualdom/items/shared/EventHandler/prototype/unrender'; import listen from 'virtualdom/items/Component/EventHandler/prototype/listen'; var ComponentEventHandler = function ( component, name, template ) { - this.component = component + this.component = component; this.init( component.root, name, template ); }; diff --git a/src/virtualdom/items/Component/ComponentEventHandler/prototype/listen.js b/src/virtualdom/items/Component/ComponentEventHandler/prototype/listen.js index 7fc336af61..6818b0c9f6 100644 --- a/src/virtualdom/items/Component/ComponentEventHandler/prototype/listen.js +++ b/src/virtualdom/items/Component/ComponentEventHandler/prototype/listen.js @@ -1,15 +1,9 @@ -import warn from 'utils/warn'; -import config from 'config/config'; -import genericHandler from 'virtualdom/items/Element/EventHandler/shared/genericHandler'; +// import warn from 'utils/warn'; +// import config from 'config/config'; +// import genericHandler from 'virtualdom/items/Element/EventHandler/shared/genericHandler'; -var customHandlers = {}; export default function EventHandler$listen () { - var definition, name = this.name; - - // subscribe to child event - - // this.hasListener = true; } diff --git a/src/virtualdom/items/Component/initialise/propagateEvents.js b/src/virtualdom/items/Component/initialise/propagateEvents.js index af10ef7667..2daefa4f10 100644 --- a/src/virtualdom/items/Component/initialise/propagateEvents.js +++ b/src/virtualdom/items/Component/initialise/propagateEvents.js @@ -8,7 +8,7 @@ import log from 'utils/log'; // when 'foo' fires on the child, but the 1,2,3 arguments // will be lost -var Fragment, getValueOptions = { args: true }; +var Fragment; circular.push( function () { Fragment = circular.Fragment; @@ -19,33 +19,7 @@ export default function propagateEvents ( component, eventsDescriptor ) { for ( eventName in eventsDescriptor ) { if ( eventsDescriptor.hasOwnProperty( eventName ) ) { - // if( eventName === '*' ) { - // To be determined, I don't thin this is right way go... - - // // TODO: allow dynamic fragments - // let namespace = eventsDescriptor[ eventName ]; - // namespace = namespace.n || namespace; - - // if ( typeof namespace !== 'string' ) { - // namespace = = new Fragment({ - // template: action, - // root: this.root, - // owner: this - // }); - - - // namespace = namespace.trim(); - // } - // else if ( namespace && namespace.n && namespace.n.length ) - // if ( !namespace || namespace === '*' || ( namespace.n && !namespace.n.length ) ) { - // namespace = component.name; - // } - - // component.eventNamespace = namespace; - - // } else { - propagateEvent( component.instance, component.root, eventName, eventsDescriptor[ eventName ] ); - // } + propagateEvent( component.instance, component.root, eventName, eventsDescriptor[ eventName ] ); } } } diff --git a/test/modules/events.js b/test/modules/events.js index 733a716087..8d98ce027a 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -665,322 +665,258 @@ define([ 'ractive' ], function ( Ractive ) { }); - function resetBubble(){ - Ractive.prototype.bubble = false; - } + var Component, Middle, View, setup, eventName = 'someEvent'; - module( 'Events bubble up components', { - setup: resetBubble, - teardown: resetBubble - }); + setup = { + setup: function(){ + Component = Ractive.extend({ + template: 'click me' + }); - test( 'bubble = true causes event handlers to bubble under "eventname" and "component.eventname"', function ( t ) { - var ractive, Component, Middle; + Middle = Ractive.extend({ + template: '' + }); - expect( 4 ); + View = Ractive.extend({ + el: fixture, + template: '', + components: { + component: Component, + middle: Middle + } + }); - Ractive.defaults.bubble = true; + }, + teardown: function(){ + Component = Middle = View = void 0; + } + }; - Component = Ractive.extend({ - template: 'click me' - }); + function goodEvent( event ) { + ok( event.context ); + } - Middle = Ractive.extend({ - template: '' - }); + function goodEventWithArg( event, arg ) { + equal( arg, 'foo' ); + } - ractive = new Ractive({ - el: fixture, - template: '', - components: { - component: Component, - middle: Middle - } - }); + function wrongNamespace () { + throw new Error( 'Component name should not be used as namespace when explicit namespace option provided' ); + } - ractive.on( 'someEvent', function ( event ) { - t.ok( true ); - t.equal( event.original.type, 'click' ); - }); + function shouldNotFire () { + throw new Error( 'This event should not fire with current bubble option setting' ); + } - ractive.on( 'component.someEvent', function ( event ) { - t.ok( true ); - t.equal( event.original.type, 'click' ); - }); + function notOnOriginating () { + throw new Error( 'Namespaced event should not fire on originating component' ); + } - simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); - }); + function shouldBeNoBubbling () { + throw new Error( 'Event bubbling should not have happened' ); + } - test( 'non "*" prefixed event handlers do not bubble', function ( t ) { - var ractive, Component, Middle; + function testEventBubbling( name, fire ) { - expect( 0 ); + module( 'Component events bubbling ' + name, setup ) - Component = Ractive.extend({ - template: 'click me' - }); + test( 'default event bubbling under "eventname", plus "component.eventname" above firing component', function ( t ) { + var ractive, middle, component; - ractive = new Ractive({ - el: fixture, - template: '', - components: { - component: Component - } - }); + expect( 5 ); - ractive.on( 'someEvent', function ( event ) { - throw new Error('event should not bubble'); - }); + ractive = new View(); + middle = ractive.findComponent( 'middle' ); + component = ractive.findComponent( 'component' ); - simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); - }); -/* - test( '"on-*" attribute sets namespace for component events', function ( t ) { - var ractive, Component, Middle; + component.on( 'someEvent', goodEvent ); + component.on( 'component.someEvent', notOnOriginating ); - expect( 2 ); + middle.on( 'someEvent', goodEvent ); + middle.on( 'component.someEvent', goodEvent ); - Component = Ractive.extend({ - template: 'click me' - }); + ractive.on( 'someEvent', goodEvent ); + ractive.on( 'component.someEvent', goodEvent ); - Middle = Ractive.extend({ - template: '' + fire( ractive.findComponent( 'component' ) ); }); - ractive = new Ractive({ - el: fixture, - template: '', - components: { - component: Component, - middle: Middle - } - }); + test( 'arguments bubble', function ( t ) { + var ractive, middle, component; - ractive.on( 'foo.someEvent', function ( event ) { - t.ok( true ); - }); + expect( 5 ); - ractive.findComponent('middle').on( 'foo.someEvent', function ( event ) { - t.ok( true ); - }); + Component.prototype.template = 'click me' - ractive.on( 'someEvent', function ( event ) { - throw new Error('event should not be namespaced'); - }); + ractive = new View(); + middle = ractive.findComponent( 'middle' ); + component = ractive.findComponent( 'component' ); - simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); - }); + component.on( 'someEvent', goodEventWithArg ); + component.on( 'component.someEvent', notOnOriginating ); - test( '"on-*" attribute defaults to component name', function ( t ) { - var ractive, Component, Middle; + middle.on( 'someEvent', goodEventWithArg ); + middle.on( 'component.someEvent', goodEventWithArg ); - expect( 1 ); + ractive.on( 'someEvent', goodEventWithArg ); + ractive.on( 'component.someEvent', goodEventWithArg ); - Component = Ractive.extend({ - template: 'click me' + fire( ractive.findComponent( 'component' ) ); }); - ractive = new Ractive({ - el: fixture, - template: '', - components: { - component: Component - } - }); + test( 'namespace option on component used for bubbling', function ( t ) { + var ractive, middle, component; - ractive.on( 'component.someEvent', function ( event ) { - t.ok( true ); - }); + expect( 5 ); - simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); - }); + View.prototype.namespace = 'should_be_ignored' + Middle.prototype.namespace = 'should_also_be_ignored' + Component.prototype.namespace = 'foo' - test( '"on-*" attribute with "*" value defaults to component name', function ( t ) { - var ractive, Component, Middle; + ractive = new View(); + middle = ractive.findComponent( 'middle' ); + component = ractive.findComponent( 'component' ); - expect( 1 ); + component.on( 'someEvent', goodEvent ); + component.on( 'component.someEvent', notOnOriginating ); + component.on( 'foo.someEvent', notOnOriginating ); - Component = Ractive.extend({ - template: 'click me' - }); + middle.on( 'someEvent', goodEvent ); + middle.on( 'foo.someEvent', goodEvent ); + middle.on( 'component.someEvent', wrongNamespace ); - ractive = new Ractive({ - el: fixture, - template: '', - components: { - component: Component - } - }); + ractive.on( 'someEvent', goodEvent ); + ractive.on( 'foo.someEvent', goodEvent ); + ractive.on( 'component.someEvent', wrongNamespace ); - ractive.on( 'component.someEvent', function ( event ) { - t.ok( true ); + fire( ractive.findComponent( 'component' ) ); }); - simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); - }); + test( 'bubble events can be stopped with event.stopBubble()', function ( t ) { + var ractive, middle, component; - test( '"on-*" attribute with dynamic value changes handler', function ( t ) { - var ractive, Component, Middle; + expect( 2 ); - expect( 1 ); + ractive = new View(); + middle = ractive.findComponent( 'middle' ); + component = ractive.findComponent( 'component' ); - Component = Ractive.extend({ - template: 'click me' - }); + component.on( 'someEvent', goodEvent ); + component.on( 'component.someEvent', notOnOriginating ); - ractive = new Ractive({ - el: fixture, - template: '', - data: { - foo: 'bar' - }, - components: { - component: Component - } - }); + middle.on( 'someEvent', function( event ) { + event.stopBubble(); + }); + // still fires on same level + middle.on( 'component.someEvent', goodEvent ); - ractive.on( 'bar.someEvent', function ( event ) { - t.ok( true ); + ractive.on( 'someEvent', shouldBeNoBubbling ); + ractive.on( 'component.someEvent', shouldBeNoBubbling ); + + fire( ractive.findComponent( 'component' ) ); }); - simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); - }); -*/ - test( 'bubble events can be stopped with event.stopBubble()', function ( t ) { - var ractive, Component, Middle; + test( 'bubble = false prevents bubbling', function ( t ) { + var ractive, middle, component; - expect( 1 ); + expect( 1 ); - Component = Ractive.extend({ - template: 'click me' - }); + Component.prototype.bubble = false - Middle = Ractive.extend({ - template: '' - }); + ractive = new View(); + middle = ractive.findComponent( 'middle' ); + component = ractive.findComponent( 'component' ); - ractive = new Ractive({ - el: fixture, - template: '', - components: { - component: Component, - middle: Middle - } - }); + component.on( 'someEvent', goodEvent ); + component.on( 'component.someEvent', notOnOriginating ); - ractive.findComponent('middle').on( 'someEvent', function ( event ) { - t.ok( true ); - event.stopBubble(); - }); + middle.on( 'someEvent', shouldBeNoBubbling); + middle.on( 'component.someEvent', shouldBeNoBubbling ); + ractive.on( 'someEvent', shouldBeNoBubbling ); + ractive.on( 'component.someEvent', shouldBeNoBubbling ); - ractive.on( 'someEvent', function ( event ) { - throw new Error('event should not bubbled'); + fire( ractive.findComponent( 'component' ) ); }); - simulant.fire( ractive.findComponent('component').nodes.test, 'click' ); - }); + // is this the right thing to do? + // and what is relationship to "isolated"? + // or is that orthangonal? + test( 'bubble = false prevents bubbling when higher up in bubble chain', function ( t ) { + var ractive, middle, component; - test( 'ractive.fire events proxied through component have event object with context', function ( t) { - var ractive, Component, items, component; + expect( 3 ); - expect( 5 ); + Middle.prototype.bubble = false - Component = Ractive.extend({ - template: 'foo' - }); + ractive = new View(); + middle = ractive.findComponent( 'middle' ); + component = ractive.findComponent( 'component' ); - items = [ { is: 'a' }, { is: 'b' }, { is: 'c' } ]; + component.on( 'someEvent', goodEvent ); + component.on( 'component.someEvent', notOnOriginating ); - ractive = new Ractive({ - el: fixture, - template: '{{#items:i}}{{/}}', - data: { items: items }, - components: { component: Component } - }); + middle.on( 'someEvent', goodEvent); + middle.on( 'component.someEvent', goodEvent ); - component = ractive.findAllComponents('component')[1]; - - ractive.on( 'bar', function ( event ) { - t.ok( event ); - t.equal( event.component, component ); - t.equal( event.index.i, 1 ); - t.equal( event.context, items[1] ); - t.equal( event.keypath, 'items.1' ); + ractive.on( 'someEvent', shouldBeNoBubbling ); + ractive.on( 'component.someEvent', shouldBeNoBubbling ); + fire( ractive.findComponent( 'component' ) ); }); - component.fire('foo'); + test( 'bubble = "nameOnly" ONLY bubbles "eventname"', function ( t ) { + var ractive, middle, component; - }); + expect( 3 ); - test( 'ractive.fire events have event object with root and bubble context', function ( t) { - var ractive, Component, items, component; + Component.prototype.bubble = 'nameOnly'; - expect( 10 ); + ractive = new View(); + middle = ractive.findComponent( 'middle' ); + component = ractive.findComponent( 'component' ); - Component = Ractive.extend({ - template: 'foo' - }); + component.on( 'someEvent', goodEvent ); + component.on( 'component.someEvent', notOnOriginating ); - items = [ { is: 'a' }, { is: 'b' }, { is: 'c' } ]; + middle.on( 'someEvent', goodEvent ); + middle.on( 'component.someEvent', shouldNotFire ); - ractive = new Ractive({ - el: fixture, - template: '{{#items:i}}{{/}}', - data: { items: items }, - components: { component: Component } - }); - - component = ractive.findAllComponents('component')[1]; + ractive.on( 'someEvent', goodEvent ); + ractive.on( 'component.someEvent', shouldNotFire ); - component.on( 'foo', function ( event ) { - t.ok( event ); - t.equal( event.component, component ); - t.ok( !event.index ); - t.equal( event.context.foo, 'bar' ); - t.equal( event.keypath, '' ); + fire( ractive.findComponent( 'component' ) ); }); - ractive.on( 'foo', function ( event ) { - t.ok( event ); - t.equal( event.component, component ); - t.equal( event.index.i, 1 ); - t.equal( event.context, items[1] ); - t.equal( event.keypath, 'items.1' ); + test( 'bubble = "nsOnly" ONLY bubbles "component.eventname"', function ( t ) { + var ractive, middle, component; - }); + expect( 3 ); - component.fire('*foo'); - }); - - test( 'ractive.fire events can have bubble cancelled', function ( t) { - var ractive, Component, items, component; + Component.prototype.bubble = 'nsOnly'; - expect( 1 ); + ractive = new View(); + middle = ractive.findComponent( 'middle' ); + component = ractive.findComponent( 'component' ); - Component = Ractive.extend({ - template: 'foo' - }); + component.on( 'someEvent', goodEvent ); + component.on( 'component.someEvent', notOnOriginating ); - ractive = new Ractive({ - el: fixture, - template: '', - components: { component: Component } - }); + middle.on( 'someEvent', shouldNotFire ); + middle.on( 'component.someEvent', goodEvent ); - component = ractive.findComponent('component'); + ractive.on( 'someEvent', shouldNotFire ); + ractive.on( 'component.someEvent', goodEvent ); - component.on( 'foo', function ( event ) { - t.ok( event.stopBubble ); - event.stopBubble(); + fire( ractive.findComponent( 'component' ) ); }); + } - ractive.on( 'foo', function ( event ) { - throw new Error('event should not bubble') - }); + testEventBubbling( 'proxy events', function ( component ) { + simulant.fire( component.nodes.test, 'click' ); + }); - component.fire('*foo'); + testEventBubbling( 'fire() events', function ( component ) { + component.fire( 'someEvent', 'foo' ); }); }; From 8b37570771eceb96b236d88adf43ab256fba087a Mon Sep 17 00:00:00 2001 From: marty Date: Sat, 30 Aug 2014 12:53:33 -0700 Subject: [PATCH 09/14] preserve some code before I remove it --- src/Ractive/prototype/shared/fireEvent.js | 38 ++++------------------- test/modules/events.js | 4 +-- 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/src/Ractive/prototype/shared/fireEvent.js b/src/Ractive/prototype/shared/fireEvent.js index 4a3a4e862e..e235a8f8d0 100644 --- a/src/Ractive/prototype/shared/fireEvent.js +++ b/src/Ractive/prototype/shared/fireEvent.js @@ -1,12 +1,5 @@ export default function fireEvent ( ractive, eventName, options = {} ) { - if ( ractive.bubble ) { - - if ( options.event ) { - options.event._bubble = true; - options.event.stopBubble = function () { options.event._bubble = false; }; - } - } fireEventAs( ractive, @@ -30,33 +23,14 @@ function fireEventAs ( ractive, eventNames, event, args, bubble, initialFire ) subscribers = ractive._subs[ eventNames[ i ] ]; if ( subscribers ) { - // to bubble or not to bubble: - // 1. this event doesn't cancel: bubble = - // 2. not already cancelled: && bubble - // 3. ractive instance allows bubble: && ractive.bubble - bubble = notifySubscribers( ractive, subscribers, event, args ) && bubble && ractive.bubble; + bubble = notifySubscribers( ractive, subscribers, event, args ) && bubble; } } - if ( bubble ) { - - if ( initialFire && ractive.bubble !== 'nameOnly' ) { - // use the explicit namespace option if provided, otherwise component name - let namespace = ractive.namespace; - if( !namespace && ractive.component ) { - namespace = ractive.component.name; - } - - if ( namespace ) { - namespace += '.' + eventNames[ 0 ]; - - if ( ractive.bubble === 'nsOnly' ) { - eventNames = [ namespace ]; - } - else { - eventNames.push( namespace ); - } - } + if ( ractive._parent && bubble ) { + + if ( initialFire && ractive.component ) { + eventNames.push( ractive.component.name + eventNames[ 0 ] ); } fireEventAs( ractive._parent, eventNames, event, args, bubble ); @@ -82,7 +56,7 @@ function notifySubscribers ( ractive, subscribers, event, args ) { originalEvent.stopPropagation && originalEvent.stopPropagation(); } - return event ? event._bubble : true; + return !stopEvent; } diff --git a/test/modules/events.js b/test/modules/events.js index 8d98ce027a..d6974d2eec 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -792,7 +792,7 @@ define([ 'ractive' ], function ( Ractive ) { fire( ractive.findComponent( 'component' ) ); }); - test( 'bubble events can be stopped with event.stopBubble()', function ( t ) { + test( 'bubbling events can be stopped by returning false', function ( t ) { var ractive, middle, component; expect( 2 ); @@ -805,7 +805,7 @@ define([ 'ractive' ], function ( Ractive ) { component.on( 'component.someEvent', notOnOriginating ); middle.on( 'someEvent', function( event ) { - event.stopBubble(); + return false; }); // still fires on same level middle.on( 'component.someEvent', goodEvent ); From a943ac9638eb4451a4340b4dffa20846f0f91751 Mon Sep 17 00:00:00 2001 From: marty Date: Sat, 30 Aug 2014 15:54:10 -0700 Subject: [PATCH 10/14] beta cut of event bubbling --- src/Ractive/prototype/fire.js | 14 +- src/Ractive/prototype/shared/fireEvent.js | 37 ++- src/config/defaults/options.js | 4 - src/config/errors.js | 5 +- src/parse/converters/element.js | 2 +- src/shared/notifyPatternObservers.js | 70 +----- src/utils/getPotentialWildcardMatches.js | 72 ++++++ .../_ComponentEventHandler.js | 30 --- .../ComponentEventHandler/prototype/listen.js | 9 - .../Component/initialise/propagateEvents.js | 24 +- .../Element/EventHandler/_EventHandler.js | 21 +- .../Element/EventHandler/prototype/init.js | 14 ++ .../Element/EventHandler/prototype/listen.js | 13 +- .../shared/EventHandler/prototype/bubble.js | 10 - .../shared/EventHandler/prototype/fire.js | 7 - .../EventHandler/prototype/getAction.js | 3 - .../shared/EventHandler/prototype/init.js | 56 ----- .../shared/EventHandler/prototype/rebind.js | 9 - .../shared/EventHandler/prototype/render.js | 10 - .../shared/EventHandler/prototype/teardown.js | 11 - .../shared/EventHandler/prototype/unrender.js | 15 -- test/modules/elements.js | 10 + test/modules/events.js | 230 ++++++++++-------- 23 files changed, 291 insertions(+), 385 deletions(-) create mode 100644 src/utils/getPotentialWildcardMatches.js delete mode 100644 src/virtualdom/items/Component/ComponentEventHandler/_ComponentEventHandler.js delete mode 100644 src/virtualdom/items/Component/ComponentEventHandler/prototype/listen.js delete mode 100644 src/virtualdom/items/shared/EventHandler/prototype/bubble.js delete mode 100644 src/virtualdom/items/shared/EventHandler/prototype/fire.js delete mode 100644 src/virtualdom/items/shared/EventHandler/prototype/getAction.js delete mode 100644 src/virtualdom/items/shared/EventHandler/prototype/init.js delete mode 100644 src/virtualdom/items/shared/EventHandler/prototype/rebind.js delete mode 100644 src/virtualdom/items/shared/EventHandler/prototype/render.js delete mode 100644 src/virtualdom/items/shared/EventHandler/prototype/teardown.js delete mode 100644 src/virtualdom/items/shared/EventHandler/prototype/unrender.js diff --git a/src/Ractive/prototype/fire.js b/src/Ractive/prototype/fire.js index 886125acae..82728ae4e9 100644 --- a/src/Ractive/prototype/fire.js +++ b/src/Ractive/prototype/fire.js @@ -2,20 +2,8 @@ import fireEvent from 'Ractive/prototype/shared/fireEvent'; export default function Ractive$fire ( eventName ) { - // no auto-add of event arg - // var options = { - // args: Array.prototype.slice.call( arguments, 1 ) - // }; - - // create event object var options = { - args: Array.prototype.slice.call( arguments, 1 ), - event: { - component: this, - keypath: '', - context: this.data - }, - changeBubbleContext: true + args: Array.prototype.slice.call( arguments, 1 ) }; fireEvent( this, eventName, options ); diff --git a/src/Ractive/prototype/shared/fireEvent.js b/src/Ractive/prototype/shared/fireEvent.js index e235a8f8d0..9fe57285a3 100644 --- a/src/Ractive/prototype/shared/fireEvent.js +++ b/src/Ractive/prototype/shared/fireEvent.js @@ -1,25 +1,15 @@ -export default function fireEvent ( ractive, eventName, options = {} ) { - - - fireEventAs( - ractive, - [ eventName ], - options.event, - options.args, - ractive.bubble, - true - ); +import getPotentialWildcardMatches from 'utils/getPotentialWildcardMatches'; +export default function fireEvent ( ractive, eventName, options = {} ) { + var eventNames = getPotentialWildcardMatches( eventName ); + fireEventAs( ractive, eventNames, options.event, options.args, true ); } -function fireEventAs ( ractive, eventNames, event, args, bubble, initialFire ) { - - if ( !ractive ) { return; } +function fireEventAs ( ractive, eventNames, event, args, initialFire = false ) { - var subscribers, i; + var subscribers, i, bubble = true; - // eventNames has both the provided event name and potentially the namespaced event - for ( i = 0; i < eventNames.length; i++ ) { + for ( i = eventNames.length; i >= 0; i-- ) { subscribers = ractive._subs[ eventNames[ i ] ]; if ( subscribers ) { @@ -30,22 +20,27 @@ function fireEventAs ( ractive, eventNames, event, args, bubble, initialFire ) if ( ractive._parent && bubble ) { if ( initialFire && ractive.component ) { - eventNames.push( ractive.component.name + eventNames[ 0 ] ); + let fullName = ractive.component.name + '.' + eventNames[ eventNames.length-1 ]; + eventNames = eventNames.concat( getPotentialWildcardMatches( fullName ) ); + + if( event ) { + event.component = ractive; + } } - fireEventAs( ractive._parent, eventNames, event, args, bubble ); + fireEventAs( ractive._parent, eventNames, event, args ); } } function notifySubscribers ( ractive, subscribers, event, args ) { - var i = 0, len = 0, originalEvent = null, stopEvent = false; + var originalEvent = null, stopEvent = false; if ( event ) { args = [ event ].concat( args ); } - for ( i=0, len=subscribers.length; i component has a default `el` property; it has been disregarded' + 'The <{name}/> component has a default `el` property; it has been disregarded', + + noElementProxyEventWildcards: + 'Only component proxy-events may contain "*" wildcards, <{element} on-{event}/> is not valid.' }; diff --git a/src/parse/converters/element.js b/src/parse/converters/element.js index 9e5a69bee3..dfca4a1aed 100644 --- a/src/parse/converters/element.js +++ b/src/parse/converters/element.js @@ -11,7 +11,7 @@ var tagNamePattern = /^[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/, validTagNameFollower = /^[\s\n\/>]/, onPattern = /^on/, // namespacePattern = /^on-\\*/, - proxyEventPattern = /^on-([a-zA-Z$_][a-zA-Z$_0-9\-]+)$/, + proxyEventPattern = /^on-([a-zA-Z\\*\\.$_][a-zA-Z\\*\\.$_0-9\-]+)$/, reservedEventNames = /^(?:change|reset|teardown|update)$/, directives = { 'intro-outro': 't0', intro: 't1', outro: 't2', decorator: 'o' }, exclude = { exclude: true }, diff --git a/src/shared/notifyPatternObservers.js b/src/shared/notifyPatternObservers.js index 99782a8f24..20780466cf 100644 --- a/src/shared/notifyPatternObservers.js +++ b/src/shared/notifyPatternObservers.js @@ -1,5 +1,6 @@ -var lastKey = /[^\.]+$/, - starMaps = {}; +import getPotentialWildcardMatches from 'utils/getPotentialWildcardMatches'; + +var lastKey = /[^\.]+$/; // TODO split into two functions? i.e. one for the top-level call, one for the cascade export default function notifyPatternObservers ( ractive, registeredKeypath, actualKeypath, isParentOfChangedKeypath, isTopLevelCall ) { @@ -48,68 +49,3 @@ export default function notifyPatternObservers ( ractive, registeredKeypath, act } } -// This function takes a keypath such as 'foo.bar.baz', and returns -// all the variants of that keypath that include a wildcard in place -// of a key, such as 'foo.bar.*', 'foo.*.baz', 'foo.*.*' and so on. -// These are then checked against the dependants map (ractive.viewmodel.depsMap) -// to see if any pattern observers are downstream of one or more of -// these wildcard keypaths (e.g. 'foo.bar.*.status') -function getPotentialWildcardMatches ( keypath ) { - var keys, starMap, mapper, i, result, wildcardKeypath; - - keys = keypath.split( '.' ); - starMap = getStarMap( keys.length ); - - result = []; - - mapper = function ( star, i ) { - return star ? '*' : keys[i]; - }; - - i = starMap.length; - while ( i-- ) { - wildcardKeypath = starMap[i].map( mapper ).join( '.' ); - - if ( !result[ wildcardKeypath ] ) { - result.push( wildcardKeypath ); - result[ wildcardKeypath ] = true; - } - } - - return result; -} - -// This function returns all the possible true/false combinations for -// a given number - e.g. for two, the possible combinations are -// [ true, true ], [ true, false ], [ false, true ], [ false, false ]. -// It does so by getting all the binary values between 0 and e.g. 11 -function getStarMap ( num ) { - var ones = '', max, binary, starMap, mapper, i; - - if ( !starMaps[ num ] ) { - starMap = []; - - while ( ones.length < num ) { - ones += 1; - } - - max = parseInt( ones, 2 ); - - mapper = function ( digit ) { - return digit === '1'; - }; - - for ( i = 0; i <= max; i += 1 ) { - binary = i.toString( 2 ); - while ( binary.length < num ) { - binary = '0' + binary; - } - - starMap[i] = Array.prototype.map.call( binary, mapper ); - } - - starMaps[ num ] = starMap; - } - - return starMaps[ num ]; -} diff --git a/src/utils/getPotentialWildcardMatches.js b/src/utils/getPotentialWildcardMatches.js new file mode 100644 index 0000000000..6f0129cf7d --- /dev/null +++ b/src/utils/getPotentialWildcardMatches.js @@ -0,0 +1,72 @@ +var starMaps = {}; + +// This function takes a keypath such as 'foo.bar.baz', and returns +// all the variants of that keypath that include a wildcard in place +// of a key, such as 'foo.bar.*', 'foo.*.baz', 'foo.*.*' and so on. +// These are then checked against the dependants map (ractive.viewmodel.depsMap) +// to see if any pattern observers are downstream of one or more of +// these wildcard keypaths (e.g. 'foo.bar.*.status') +export function getPotentialWildcardMatches ( keypath ) { + var keys, starMap, mapper, i, result, wildcardKeypath; + + keys = keypath.split( '.' ); + if( !( starMap = starMaps[ keys.length ]) ) { + starMap = getStarMap( keys.length ); + } + + result = []; + + mapper = function ( star, i ) { + return star ? '*' : keys[i]; + }; + + i = starMap.length; + while ( i-- ) { + wildcardKeypath = starMap[i].map( mapper ).join( '.' ); + + if ( !result[ wildcardKeypath ] ) { + result.push( wildcardKeypath ); + result[ wildcardKeypath ] = true; + } + } + + return result; +} + +// This function returns all the possible true/false combinations for +// a given number - e.g. for two, the possible combinations are +// [ true, true ], [ true, false ], [ false, true ], [ false, false ]. +// It does so by getting all the binary values between 0 and e.g. 11 +function getStarMap ( num ) { + var ones = '', max, binary, starMap, mapper, i; + + if ( !starMaps[ num ] ) { + starMap = []; + + while ( ones.length < num ) { + ones += 1; + } + + max = parseInt( ones, 2 ); + + mapper = function ( digit ) { + return digit === '1'; + }; + + for ( i = 0; i <= max; i += 1 ) { + binary = i.toString( 2 ); + while ( binary.length < num ) { + binary = '0' + binary; + } + + starMap[i] = Array.prototype.map.call( binary, mapper ); + } + + starMaps[ num ] = starMap; + } + + return starMaps[ num ]; +} + + + diff --git a/src/virtualdom/items/Component/ComponentEventHandler/_ComponentEventHandler.js b/src/virtualdom/items/Component/ComponentEventHandler/_ComponentEventHandler.js deleted file mode 100644 index 987e6c377d..0000000000 --- a/src/virtualdom/items/Component/ComponentEventHandler/_ComponentEventHandler.js +++ /dev/null @@ -1,30 +0,0 @@ -import bubble from 'virtualdom/items/shared/EventHandler/prototype/bubble'; -import fire from 'virtualdom/items/shared/EventHandler/prototype/fire'; -import getAction from 'virtualdom/items/shared/EventHandler/prototype/getAction'; -import init from 'virtualdom/items/shared/EventHandler/prototype/init'; -import rebind from 'virtualdom/items/shared/EventHandler/prototype/rebind'; -import render from 'virtualdom/items/shared/EventHandler/prototype/render'; -import teardown from 'virtualdom/items/shared/EventHandler/prototype/teardown'; -import unrender from 'virtualdom/items/shared/EventHandler/prototype/unrender'; - - -import listen from 'virtualdom/items/Component/EventHandler/prototype/listen'; - -var ComponentEventHandler = function ( component, name, template ) { - this.component = component; - this.init( component.root, name, template ); -}; - -ComponentEventHandler.prototype = { - bubble: bubble, - fire: fire, - getAction: getAction, - init: init, - listen: listen, - rebind: rebind, - render: render, - teardown: teardown, - unrender: unrender -}; - -export default ComponentEventHandler; diff --git a/src/virtualdom/items/Component/ComponentEventHandler/prototype/listen.js b/src/virtualdom/items/Component/ComponentEventHandler/prototype/listen.js deleted file mode 100644 index 6818b0c9f6..0000000000 --- a/src/virtualdom/items/Component/ComponentEventHandler/prototype/listen.js +++ /dev/null @@ -1,9 +0,0 @@ -// import warn from 'utils/warn'; -// import config from 'config/config'; -// import genericHandler from 'virtualdom/items/Element/EventHandler/shared/genericHandler'; - - -export default function EventHandler$listen () { - - -} diff --git a/src/virtualdom/items/Component/initialise/propagateEvents.js b/src/virtualdom/items/Component/initialise/propagateEvents.js index 2daefa4f10..a7dd0a8649 100644 --- a/src/virtualdom/items/Component/initialise/propagateEvents.js +++ b/src/virtualdom/items/Component/initialise/propagateEvents.js @@ -27,7 +27,6 @@ export default function propagateEvents ( component, eventsDescriptor ) { function propagateEvent ( childInstance, parentInstance, eventName, proxyEventName ) { if ( typeof proxyEventName !== 'string' ) { - log.error({ debug: parentInstance.debug, message: 'noComponentEventArguments' @@ -35,17 +34,24 @@ function propagateEvent ( childInstance, parentInstance, eventName, proxyEventNa } childInstance.on( eventName, function () { - var fragment = this.component.parentFragment, + var options; + + // semi-weak test, but what else? tag the event obj ._isEvent ? + if ( arguments[0].node ) { options = { - event: { - component: this, - index: fragment.indexRefs, - keypath: fragment.context, - context: parentInstance.get( fragment.context ) - }, - args: Array.prototype.slice.call( arguments ), + event: Array.prototype.shift.call( arguments ), + args: arguments }; + } + else { + options = { + args: Array.prototype.slice.call( arguments ) + }; + } fireEvent( parentInstance, proxyEventName, options ); + + // cancel bubbling + return false; }); } diff --git a/src/virtualdom/items/Element/EventHandler/_EventHandler.js b/src/virtualdom/items/Element/EventHandler/_EventHandler.js index 3c6df441b6..afed55634a 100644 --- a/src/virtualdom/items/Element/EventHandler/_EventHandler.js +++ b/src/virtualdom/items/Element/EventHandler/_EventHandler.js @@ -1,18 +1,17 @@ -import bubble from 'virtualdom/items/shared/EventHandler/prototype/bubble'; -import fire from 'virtualdom/items/shared/EventHandler/prototype/fire'; -import getAction from 'virtualdom/items/shared/EventHandler/prototype/getAction'; -import init from 'virtualdom/items/shared/EventHandler/prototype/init'; -import rebind from 'virtualdom/items/shared/EventHandler/prototype/rebind'; -import render from 'virtualdom/items/shared/EventHandler/prototype/render'; -import teardown from 'virtualdom/items/shared/EventHandler/prototype/teardown'; -import unrender from 'virtualdom/items/shared/EventHandler/prototype/unrender'; +import bubble from 'virtualdom/items/Element/EventHandler/prototype/bubble'; +import fire from 'virtualdom/items/Element/EventHandler/prototype/fire'; +import getAction from 'virtualdom/items/Element/EventHandler/prototype/getAction'; +import init from 'virtualdom/items/Element/EventHandler/prototype/init'; +import listen from 'virtualdom/items/Element/EventHandler/prototype/listen'; +import rebind from 'virtualdom/items/Element/EventHandler/prototype/rebind'; +import render from 'virtualdom/items/Element/EventHandler/prototype/render'; +import teardown from 'virtualdom/items/Element/EventHandler/prototype/teardown'; +import unrender from 'virtualdom/items/Element/EventHandler/prototype/unrender'; -import listen from 'virtualdom/items/Element/EventHandler/prototype/listen'; var EventHandler = function ( element, name, template ) { - this.element = element; - this.init( element.root, name, template ); + this.init( element, name, template ); }; EventHandler.prototype = { diff --git a/src/virtualdom/items/Element/EventHandler/prototype/init.js b/src/virtualdom/items/Element/EventHandler/prototype/init.js index 509618ebcd..449f08a405 100644 --- a/src/virtualdom/items/Element/EventHandler/prototype/init.js +++ b/src/virtualdom/items/Element/EventHandler/prototype/init.js @@ -1,5 +1,6 @@ import circular from 'circular'; import fireEvent from 'Ractive/prototype/shared/fireEvent'; +import log from 'utils/log'; var Fragment, getValueOptions = { args: true }; @@ -14,6 +15,19 @@ export default function EventHandler$init ( element, name, template ) { this.root = element.root; this.name = name; + if( name.indexOf( '*' ) !== -1 ) { + log.error({ + debug: this.root.debug, + message: 'noElementProxyEventWildcards', + args: { + element: element.tagName, + event: name + } + }); + + this.invalid = true; + } + // Get action ('foo' in 'on-click='foo') action = template.n || template; if ( typeof action !== 'string' ) { diff --git a/src/virtualdom/items/Element/EventHandler/prototype/listen.js b/src/virtualdom/items/Element/EventHandler/prototype/listen.js index 0623951fdd..1203275730 100644 --- a/src/virtualdom/items/Element/EventHandler/prototype/listen.js +++ b/src/virtualdom/items/Element/EventHandler/prototype/listen.js @@ -1,6 +1,6 @@ -import warn from 'utils/warn'; import config from 'config/config'; import genericHandler from 'virtualdom/items/Element/EventHandler/shared/genericHandler'; +import log from 'utils/log'; var customHandlers = {}; @@ -8,12 +8,21 @@ export default function EventHandler$listen () { var definition, name = this.name; + if ( this.invalid ) { return; } + if ( definition = config.registries.events.find( this.root, name ) ) { this.custom = definition( this.node, getCustomHandler( name ) ); } else { // Looks like we're dealing with a standard DOM event... but let's check if ( !( 'on' + name in this.node ) && !( window && 'on' + name in window ) ) { - warn( 'Missing "' + this.name + '" event. You may need to download a plugin via http://docs.ractivejs.org/latest/plugins#events' ); + log.error({ + debug: this.root.debug, + message: 'missingPlugin', + args: { + plugin: 'event', + name: name + } + }); } this.node.addEventListener( name, genericHandler, false ); diff --git a/src/virtualdom/items/shared/EventHandler/prototype/bubble.js b/src/virtualdom/items/shared/EventHandler/prototype/bubble.js deleted file mode 100644 index e0d0ad5f93..0000000000 --- a/src/virtualdom/items/shared/EventHandler/prototype/bubble.js +++ /dev/null @@ -1,10 +0,0 @@ -export default function EventHandler$bubble () { - var hasAction = this.getAction(); - - if( hasAction && !this.hasListener ) { - this.listen(); - } - else if ( !hasAction && this.hasListener ) { - this.unrender(); - } -} diff --git a/src/virtualdom/items/shared/EventHandler/prototype/fire.js b/src/virtualdom/items/shared/EventHandler/prototype/fire.js deleted file mode 100644 index 7a718b1cae..0000000000 --- a/src/virtualdom/items/shared/EventHandler/prototype/fire.js +++ /dev/null @@ -1,7 +0,0 @@ -import fireEvent from 'Ractive/prototype/shared/fireEvent'; - -// This function may be overwritten, if the event directive -// includes parameters -export default function EventHandler$fire ( event ) { - fireEvent( this.root, this.getAction(), { event: event } ); -} diff --git a/src/virtualdom/items/shared/EventHandler/prototype/getAction.js b/src/virtualdom/items/shared/EventHandler/prototype/getAction.js deleted file mode 100644 index 9d4dd38c31..0000000000 --- a/src/virtualdom/items/shared/EventHandler/prototype/getAction.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function EventHandler$getAction () { - return this.action.toString().trim(); -} diff --git a/src/virtualdom/items/shared/EventHandler/prototype/init.js b/src/virtualdom/items/shared/EventHandler/prototype/init.js deleted file mode 100644 index 382aca6c51..0000000000 --- a/src/virtualdom/items/shared/EventHandler/prototype/init.js +++ /dev/null @@ -1,56 +0,0 @@ -import circular from 'circular'; -import fireEvent from 'Ractive/prototype/shared/fireEvent'; - -var Fragment, getValueOptions = { args: true }; - -circular.push( function () { - Fragment = circular.Fragment; -}); - -export default function EventHandler$init ( root, name, template ) { - var action; - - this.root = root; - this.name = name; - - // Get action ('foo' in 'on-click='foo') - action = template.n || template; - if ( typeof action !== 'string' ) { - action = new Fragment({ - template: action, - root: this.root, - owner: this - }); - } - - this.action = action; - - // Get parameters - if ( template.d ) { - this.dynamicParams = new Fragment({ - template: template.d, - root: this.root, - owner: this.element - }); - - this.fire = fireEventWithDynamicParams; - } else if ( template.a ) { - this.params = template.a; - this.fire = fireEventWithParams; - } -} - -function fireEventWithParams ( event ) { - fireEvent( this.root, this.getAction(), { event: event, args: this.params } ); -} - -function fireEventWithDynamicParams ( event ) { - var args = this.dynamicParams.getValue( getValueOptions ); - - // need to strip [] from ends if a string! - if ( typeof args === 'string' ) { - args = args.substr( 1, args.length - 2 ); - } - - fireEvent( this.root, this.getAction(), { event: event, args: args } ); -} diff --git a/src/virtualdom/items/shared/EventHandler/prototype/rebind.js b/src/virtualdom/items/shared/EventHandler/prototype/rebind.js deleted file mode 100644 index 5de673a750..0000000000 --- a/src/virtualdom/items/shared/EventHandler/prototype/rebind.js +++ /dev/null @@ -1,9 +0,0 @@ -export default function EventHandler$rebind ( indexRef, newIndex, oldKeypath, newKeypath ) { - if ( typeof this.action !== 'string' ) { - this.action.rebind( indexRef, newIndex, oldKeypath, newKeypath ); - } - - if ( this.dynamicParams ) { - this.dynamicParams.rebind( indexRef, newIndex, oldKeypath, newKeypath ); - } -} diff --git a/src/virtualdom/items/shared/EventHandler/prototype/render.js b/src/virtualdom/items/shared/EventHandler/prototype/render.js deleted file mode 100644 index 3c0e98bdc6..0000000000 --- a/src/virtualdom/items/shared/EventHandler/prototype/render.js +++ /dev/null @@ -1,10 +0,0 @@ -export default function EventHandler$render () { - this.node = this.element.node; - // store this on the node itself, so it can be retrieved by a - // universal handler - this.node._ractive.events[ this.name ] = this; - - if ( this.getAction() ) { - this.listen(); - } -} diff --git a/src/virtualdom/items/shared/EventHandler/prototype/teardown.js b/src/virtualdom/items/shared/EventHandler/prototype/teardown.js deleted file mode 100644 index d8bb8fbd60..0000000000 --- a/src/virtualdom/items/shared/EventHandler/prototype/teardown.js +++ /dev/null @@ -1,11 +0,0 @@ -export default function EventHandler$teardown () { - // Tear down dynamic name - if ( typeof this.action !== 'string' ) { - this.action.teardown(); - } - - // Tear down dynamic parameters - if ( this.dynamicParams ) { - this.dynamicParams.teardown(); - } -} diff --git a/src/virtualdom/items/shared/EventHandler/prototype/unrender.js b/src/virtualdom/items/shared/EventHandler/prototype/unrender.js deleted file mode 100644 index 5dc641cc1c..0000000000 --- a/src/virtualdom/items/shared/EventHandler/prototype/unrender.js +++ /dev/null @@ -1,15 +0,0 @@ -import genericHandler from 'virtualdom/items/Element/EventHandler/shared/genericHandler'; - -export default function EventHandler$unrender () { - - if ( this.custom ) { - this.custom.teardown(); - } - - else { - this.node.removeEventListener( this.name, genericHandler, false ); - } - - this.hasListener = false; - -} diff --git a/test/modules/elements.js b/test/modules/elements.js index 26a139db14..cf94146097 100644 --- a/test/modules/elements.js +++ b/test/modules/elements.js @@ -75,5 +75,15 @@ define([ 'ractive' ], function ( Ractive ) { t.equal( ractive.toHTML(), '' ); }); + + test( 'Wildcard proxy-events invalid on elements', function ( t ) { + throws( function () { + var ractive = new Ractive({ + el: fixture, + debug: true, + template: '

' + }); + }, /wildcards/ ); + }); }; }); diff --git a/test/modules/events.js b/test/modules/events.js index d6974d2eec..0bf2141b20 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -665,12 +665,12 @@ define([ 'ractive' ], function ( Ractive ) { }); - var Component, Middle, View, setup, eventName = 'someEvent'; + var Component, Middle, View, setup; setup = { setup: function(){ Component = Ractive.extend({ - template: 'click me' + template: 'click me' }); Middle = Ractive.extend({ @@ -693,11 +693,11 @@ define([ 'ractive' ], function ( Ractive ) { }; function goodEvent( event ) { - ok( event.context ); + ok( event.context || event === 'foo' ); } function goodEventWithArg( event, arg ) { - equal( arg, 'foo' ); + equal( arg || event, 'foo' ); } function wrongNamespace () { @@ -716,11 +716,9 @@ define([ 'ractive' ], function ( Ractive ) { throw new Error( 'Event bubbling should not have happened' ); } - function testEventBubbling( name, fire ) { + function testEventBubbling( fire ) { - module( 'Component events bubbling ' + name, setup ) - - test( 'default event bubbling under "eventname", plus "component.eventname" above firing component', function ( t ) { + test( 'Events bubble under "eventname", and also "component.eventname" above firing component', function ( t ) { var ractive, middle, component; expect( 5 ); @@ -764,34 +762,6 @@ define([ 'ractive' ], function ( Ractive ) { fire( ractive.findComponent( 'component' ) ); }); - test( 'namespace option on component used for bubbling', function ( t ) { - var ractive, middle, component; - - expect( 5 ); - - View.prototype.namespace = 'should_be_ignored' - Middle.prototype.namespace = 'should_also_be_ignored' - Component.prototype.namespace = 'foo' - - ractive = new View(); - middle = ractive.findComponent( 'middle' ); - component = ractive.findComponent( 'component' ); - - component.on( 'someEvent', goodEvent ); - component.on( 'component.someEvent', notOnOriginating ); - component.on( 'foo.someEvent', notOnOriginating ); - - middle.on( 'someEvent', goodEvent ); - middle.on( 'foo.someEvent', goodEvent ); - middle.on( 'component.someEvent', wrongNamespace ); - - ractive.on( 'someEvent', goodEvent ); - ractive.on( 'foo.someEvent', goodEvent ); - ractive.on( 'component.someEvent', wrongNamespace ); - - fire( ractive.findComponent( 'component' ) ); - }); - test( 'bubbling events can be stopped by returning false', function ( t ) { var ractive, middle, component; @@ -816,109 +786,177 @@ define([ 'ractive' ], function ( Ractive ) { fire( ractive.findComponent( 'component' ) ); }); - test( 'bubble = false prevents bubbling', function ( t ) { + test( 'bubbling events with event object have component reference', function ( t ) { var ractive, middle, component; - expect( 1 ); - - Component.prototype.bubble = false + expect( 5 ); ractive = new View(); middle = ractive.findComponent( 'middle' ); component = ractive.findComponent( 'component' ); - component.on( 'someEvent', goodEvent ); - component.on( 'component.someEvent', notOnOriginating ); + function hasComponentRef( event, arg ) { + event.original ? t.equal( event.component, component ) : t.ok( true ); + } - middle.on( 'someEvent', shouldBeNoBubbling); - middle.on( 'component.someEvent', shouldBeNoBubbling ); - ractive.on( 'someEvent', shouldBeNoBubbling ); - ractive.on( 'component.someEvent', shouldBeNoBubbling ); + component.on( 'someEvent', function( event ) { + t.ok( !event.component ); + }); + + middle.on( 'someEvent', hasComponentRef ); + middle.on( 'component.someEvent', hasComponentRef ); + + ractive.on( 'someEvent', hasComponentRef ); + ractive.on( 'component.someEvent', hasComponentRef ); fire( ractive.findComponent( 'component' ) ); }); - // is this the right thing to do? - // and what is relationship to "isolated"? - // or is that orthangonal? - test( 'bubble = false prevents bubbling when higher up in bubble chain', function ( t ) { - var ractive, middle, component; + } - expect( 3 ); - Middle.prototype.bubble = false + module( 'Component events bubbling proxy events', setup ) - ractive = new View(); - middle = ractive.findComponent( 'middle' ); - component = ractive.findComponent( 'component' ); + testEventBubbling( function ( component ) { + simulant.fire( component.nodes.test, 'click' ); + }); - component.on( 'someEvent', goodEvent ); - component.on( 'component.someEvent', notOnOriginating ); + module( 'Component events bubbling fire() events', setup ) - middle.on( 'someEvent', goodEvent); - middle.on( 'component.someEvent', goodEvent ); + testEventBubbling( function ( component ) { + component.fire( 'someEvent', 'foo' ); + }); - ractive.on( 'someEvent', shouldBeNoBubbling ); - ractive.on( 'component.someEvent', shouldBeNoBubbling ); + module( 'Event pattern matching' ); - fire( ractive.findComponent( 'component' ) ); - }); + function fired ( event ) { + ok( true ); + } - test( 'bubble = "nameOnly" ONLY bubbles "eventname"', function ( t ) { - var ractive, middle, component; + test( 'handlers can use pattern matching', function ( t ) { + var ractive; - expect( 3 ); + expect( 4 ); - Component.prototype.bubble = 'nameOnly'; + ractive = new Ractive({ + el: fixture, + template: 'click me' + }); - ractive = new View(); - middle = ractive.findComponent( 'middle' ); - component = ractive.findComponent( 'component' ); + ractive.on( '*.*', fired); + ractive.on( 'some.*', fired); + ractive.on( '*.event', fired); + ractive.on( 'some.event', fired); - component.on( 'someEvent', goodEvent ); - component.on( 'component.someEvent', notOnOriginating ); + simulant.fire( ractive.nodes.test, 'click' ); + }); - middle.on( 'someEvent', goodEvent ); - middle.on( 'component.someEvent', shouldNotFire ); + test( 'bubbling handlers can use pattern matching', function ( t ) { + var Component, component, ractive; - ractive.on( 'someEvent', goodEvent ); - ractive.on( 'component.someEvent', shouldNotFire ); + expect( 5 ); - fire( ractive.findComponent( 'component' ) ); + Component = Ractive.extend({ + template: 'click me' }); - test( 'bubble = "nsOnly" ONLY bubbles "component.eventname"', function ( t ) { - var ractive, middle, component; + ractive = new Ractive({ + el: fixture, + template: '', + components: { + component: Component + } + }); - expect( 3 ); + ractive.on( '*', fired); + ractive.on( '*.*', fired); + ractive.on( 'component.*', fired); + ractive.on( '*.foo', fired); + ractive.on( 'component.foo', fired); - Component.prototype.bubble = 'nsOnly'; + component = ractive.findComponent( 'component' ); + simulant.fire( component.nodes.test, 'click' ); - ractive = new View(); - middle = ractive.findComponent( 'middle' ); - component = ractive.findComponent( 'component' ); + // otherwise we get cross test failure due to "teardown" event + // becasue we're reusing fixture element + ractive.off(); + }); - component.on( 'someEvent', goodEvent ); - component.on( 'component.someEvent', notOnOriginating ); + test( 'component "on-someEvent" implicitly cancels bubbling', function ( t ) { + var Component, component, ractive; - middle.on( 'someEvent', shouldNotFire ); - middle.on( 'component.someEvent', goodEvent ); + expect( 1 ); - ractive.on( 'someEvent', shouldNotFire ); - ractive.on( 'component.someEvent', goodEvent ); + Component = Ractive.extend({ + template: 'click me' + }); - fire( ractive.findComponent( 'component' ) ); + ractive = new Ractive({ + el: fixture, + template: '', + components: { + component: Component + } }); - } - testEventBubbling( 'proxy events', function ( component ) { + ractive.on( 'foo', fired); + ractive.on( 'someEvent', shouldBeNoBubbling); + ractive.on( 'component.someEvent', shouldBeNoBubbling); + + component = ractive.findComponent( 'component' ); simulant.fire( component.nodes.test, 'click' ); }); - testEventBubbling( 'fire() events', function ( component ) { - component.fire( 'someEvent', 'foo' ); + test( 'component "on-" wildcards match', function ( t ) { + var Component, component, ractive; + + expect( 3 ); + + Component = Ractive.extend({ + template: 'click me' + }); + + ractive = new Ractive({ + el: fixture, + template: '', + components: { + component: Component + } + }); + + ractive.on( 'foo', fired); + ractive.on( 'bar', fired); + ractive.on( 'both', fired); + + component = ractive.findComponent( 'component' ); + simulant.fire( component.nodes.test, 'click' ); }); + test( 'component "on-" do not get auto-namespaced events', function ( t ) { + var Component, component, ractive; + + expect( 1 ); + + Component = Ractive.extend({ + template: 'click me' + }); + + ractive = new Ractive({ + el: fixture, + template: '', + components: { + component: Component + } + }); + + ractive.on( 'foo', shouldNotFire); + + component = ractive.findComponent( 'component' ); + simulant.fire( component.nodes.test, 'click' ); + t.ok( true ); + }); + + }; }); From 48398e2498c68bd0158650dac8e1a205d4c424a4 Mon Sep 17 00:00:00 2001 From: marty Date: Sun, 31 Aug 2014 07:49:43 -0700 Subject: [PATCH 11/14] reserved event names bubble under namespace only --- src/Ractive/prototype/reset.js | 5 +- src/Ractive/prototype/shared/fireEvent.js | 10 +- src/Ractive/prototype/teardown.js | 3 +- src/Ractive/prototype/update.js | 5 +- src/global/runloop.js | 5 +- .../items/Component/prototype/unrender.js | 2 +- test/modules/events.js | 105 ++++++++++++------ 7 files changed, 89 insertions(+), 46 deletions(-) diff --git a/src/Ractive/prototype/reset.js b/src/Ractive/prototype/reset.js index 764e93faf5..3a9b661c4f 100644 --- a/src/Ractive/prototype/reset.js +++ b/src/Ractive/prototype/reset.js @@ -79,7 +79,10 @@ export default function Ractive$reset ( data, callback ) { runloop.end(); } - fireEvent( this, 'reset', { args: [ data ] } ); + fireEvent( this, 'reset', { + args: [ data ], + reserved: true + }); if ( callback ) { promise.then( callback ); diff --git a/src/Ractive/prototype/shared/fireEvent.js b/src/Ractive/prototype/shared/fireEvent.js index 9fe57285a3..48ab183be5 100644 --- a/src/Ractive/prototype/shared/fireEvent.js +++ b/src/Ractive/prototype/shared/fireEvent.js @@ -2,10 +2,10 @@ import getPotentialWildcardMatches from 'utils/getPotentialWildcardMatches'; export default function fireEvent ( ractive, eventName, options = {} ) { var eventNames = getPotentialWildcardMatches( eventName ); - fireEventAs( ractive, eventNames, options.event, options.args, true ); + fireEventAs( ractive, eventNames, options.event, options.args, options.reserved, true ); } -function fireEventAs ( ractive, eventNames, event, args, initialFire = false ) { +function fireEventAs ( ractive, eventNames, event, args, reserved = false, initialFire = false ) { var subscribers, i, bubble = true; @@ -20,8 +20,10 @@ function fireEventAs ( ractive, eventNames, event, args, initialFire = false ) if ( ractive._parent && bubble ) { if ( initialFire && ractive.component ) { - let fullName = ractive.component.name + '.' + eventNames[ eventNames.length-1 ]; - eventNames = eventNames.concat( getPotentialWildcardMatches( fullName ) ); + let fullName = ractive.component.name + '.' + eventNames[ eventNames.length-1 ], + nsEventNames = getPotentialWildcardMatches( fullName ) ; + + eventNames = reserved ? nsEventNames : eventNames.concat( nsEventNames ); if( event ) { event.component = ractive; diff --git a/src/Ractive/prototype/teardown.js b/src/Ractive/prototype/teardown.js index a577f91fc7..934a7ed4c5 100644 --- a/src/Ractive/prototype/teardown.js +++ b/src/Ractive/prototype/teardown.js @@ -1,3 +1,4 @@ +import fireEvent from 'Ractive/prototype/shared/fireEvent'; import removeFromArray from 'utils/removeFromArray'; import Promise from 'utils/Promise'; @@ -7,7 +8,7 @@ import Promise from 'utils/Promise'; export default function Ractive$teardown ( callback ) { var promise; - this.fire( 'teardown' ); + fireEvent( this, 'teardown', { reserved: true } ); this.fragment.unbind(); this.viewmodel.teardown(); diff --git a/src/Ractive/prototype/update.js b/src/Ractive/prototype/update.js index 9523d39b45..95a4306fb3 100644 --- a/src/Ractive/prototype/update.js +++ b/src/Ractive/prototype/update.js @@ -16,7 +16,10 @@ export default function Ractive$update ( keypath, callback ) { this.viewmodel.mark( keypath ); runloop.end(); - fireEvent( this, 'update', { args: [ keypath ] } ); + fireEvent( this, 'update', { + args: [ keypath ], + reserved: true + }); if ( callback ) { promise.then( callback.bind( this ) ); diff --git a/src/global/runloop.js b/src/global/runloop.js index b02f6669e6..b7130e8fa5 100644 --- a/src/global/runloop.js +++ b/src/global/runloop.js @@ -89,7 +89,10 @@ function flushChanges () { changeHash = thing.applyChanges(); if ( changeHash ) { - fireEvent( thing.ractive, 'change', { args: [ changeHash ] } ); + fireEvent( thing.ractive, 'change', { + args: [ changeHash ], + reserved: true + }); } } batch.viewmodels.length = 0; diff --git a/src/virtualdom/items/Component/prototype/unrender.js b/src/virtualdom/items/Component/prototype/unrender.js index f2fa3c49d2..16a13d052d 100644 --- a/src/virtualdom/items/Component/prototype/unrender.js +++ b/src/virtualdom/items/Component/prototype/unrender.js @@ -1,7 +1,7 @@ import fireEvent from 'Ractive/prototype/shared/fireEvent'; export default function Component$unrender ( shouldDestroy ) { - fireEvent( this.instance, 'teardown' ); + fireEvent( this.instance, 'teardown', { reserved: true }); this.shouldDestroy = shouldDestroy; this.instance.unrender(); diff --git a/test/modules/events.js b/test/modules/events.js index 0bf2141b20..e7c1584106 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -1,8 +1,3 @@ -// EVENT TESTS -// =========== -// -// TODO: add moar tests - define([ 'ractive' ], function ( Ractive ) { return function () { @@ -11,7 +6,7 @@ define([ 'ractive' ], function ( Ractive ) { module( 'Events' ); - test( 'on-click="someEvent" fires an event when user clicks the element', function ( t ) { + test( 'on-click="someEvent" fires an event when user clicks the element', t => { var ractive; expect( 2 ); @@ -104,7 +99,7 @@ define([ 'ractive' ], function ( Ractive ) { }); - test( 'Standard events have correct properties: node, original, keypath, context, index', function ( t ) { + test( 'Standard events have correct properties: node, original, keypath, context, index', t => { var ractive, fakeEvent; expect( 5 ); @@ -128,7 +123,7 @@ define([ 'ractive' ], function ( Ractive ) { }); - test( 'preventDefault and stopPropagation if event handler returned false', function ( t ) { + test( 'preventDefault and stopPropagation if event handler returned false', t => { var ractive, preventedDefault = false, stoppedPropagation = false; expect( 9 ); @@ -187,7 +182,7 @@ define([ 'ractive' ], function ( Ractive ) { }); - test( 'event.keypath is set to the innermost context', function ( t ) { + test( 'event.keypath is set to the innermost context', t => { var ractive; expect( 2 ); @@ -208,7 +203,7 @@ define([ 'ractive' ], function ( Ractive ) { simulant.fire( ractive.nodes.test, 'click' ); }); - test( 'event.index stores current indices against their references', function ( t ) { + test( 'event.index stores current indices against their references', t => { var ractive; expect( 4 ); @@ -231,7 +226,7 @@ define([ 'ractive' ], function ( Ractive ) { simulant.fire( ractive.nodes.item_2, 'click' ); }); - test( 'event.index reports nested indices correctly', function ( t ) { + test( 'event.index reports nested indices correctly', t => { var ractive; expect( 2 ); @@ -261,7 +256,7 @@ define([ 'ractive' ], function ( Ractive ) { simulant.fire( ractive.nodes.test_001, 'click' ); }); - test( 'proxy events can have dynamic names', function ( t ) { + test( 'proxy events can have dynamic names', t => { var ractive, last; expect( 2 ); @@ -290,7 +285,7 @@ define([ 'ractive' ], function ( Ractive ) { t.equal( last, 'bar' ); }); - test( 'proxy event parameters are correctly parsed as JSON, or treated as a string', function ( t ) { + test( 'proxy event parameters are correctly parsed as JSON, or treated as a string', t => { var ractive, last; expect( 3 ); @@ -316,7 +311,7 @@ define([ 'ractive' ], function ( Ractive ) { t.deepEqual( last, [ 1, 2, 3 ] ); }); - test( 'proxy events can have dynamic arguments', function ( t ) { + test( 'proxy events can have dynamic arguments', t => { var ractive; ractive = new Ractive({ @@ -336,7 +331,7 @@ define([ 'ractive' ], function ( Ractive ) { simulant.fire( ractive.nodes.foo, 'click' ); }); - test( 'proxy events can have multiple arguments', function ( t ) { + test( 'proxy events can have multiple arguments', t => { var ractive; ractive = new Ractive({ @@ -368,7 +363,7 @@ define([ 'ractive' ], function ( Ractive ) { simulant.fire( ractive.nodes.baz, 'click' ); }); - test( 'Splicing arrays correctly modifies proxy events', function ( t ) { + test( 'Splicing arrays correctly modifies proxy events', t => { var ractive; expect( 4 ); @@ -395,7 +390,7 @@ define([ 'ractive' ], function ( Ractive ) { t.equal( ractive.findAll( 'button' ).length, 2 ); }); - test( 'Splicing arrays correctly modifies two-way bindings', function ( t ) { + test( 'Splicing arrays correctly modifies two-way bindings', t => { var ractive, items; expect( 25 ); @@ -468,7 +463,7 @@ define([ 'ractive' ], function ( Ractive ) { t.equal( ractive.findAll( 'input' ).length, 2 ); }); - test( 'Calling ractive.off() without a keypath removes all handlers', function ( t ) { + test( 'Calling ractive.off() without a keypath removes all handlers', t => { var ractive = new Ractive({ el: fixture, template: 'doesn\'t matter' @@ -495,7 +490,7 @@ define([ 'ractive' ], function ( Ractive ) { ractive.fire( 'baz' ); }); - test( 'Changes triggered by two-way bindings propagate properly (#460)', function ( t ) { + test( 'Changes triggered by two-way bindings propagate properly (#460)', t => { var changes, ractive = new Ractive({ el: fixture, template: '{{#items}}{{/items}}

{{ items.filter( completed ).length }}

{{# items.filter( completed ).length }}

foo

{{/ items.filter( completed ).length }}', @@ -527,7 +522,7 @@ define([ 'ractive' ], function ( Ractive ) { t.htmlEqual( ractive.find( '.result' ).innerHTML, '0' ); }); - test( 'Multiple events can share the same directive', function ( t ) { + test( 'Multiple events can share the same directive', t => { var ractive, count = 0; ractive = new Ractive({ @@ -546,7 +541,7 @@ define([ 'ractive' ], function ( Ractive ) { t.equal( count, 2 ); }); - test( 'Superfluous whitespace is ignored', function ( t ) { + test( 'Superfluous whitespace is ignored', t => { var ractive, fooCount = 0, barCount = 0; ractive = new Ractive({ @@ -574,7 +569,7 @@ define([ 'ractive' ], function ( Ractive ) { t.equal( barCount, 1 ); }); - test( 'Multiple space-separated events can be handled with a single callback (#731)', function ( t ) { + test( 'Multiple space-separated events can be handled with a single callback (#731)', t => { var ractive, count = 0; ractive = new Ractive({}); @@ -611,7 +606,7 @@ define([ 'ractive' ], function ( Ractive ) { t.equal( returnedValue, ractive ); }); - test( 'Events really do not call addEventListener when no proxy name', function ( t ) { + test( 'Events really do not call addEventListener when no proxy name', t => { var ractive, addEventListener = Element.prototype.addEventListener, errorAdd = function(){ @@ -648,7 +643,7 @@ define([ 'ractive' ], function ( Ractive ) { }); - test( '@index can be used in proxy event directives', function ( t ) { + test( '@index can be used in proxy event directives', t => { var ractive = new Ractive({ el: fixture, template: '{{#each letters}}{{/each}}', @@ -692,6 +687,10 @@ define([ 'ractive' ], function ( Ractive ) { } }; + function fired ( event ) { + ok( true ); + } + function goodEvent( event ) { ok( event.context || event === 'foo' ); } @@ -718,7 +717,7 @@ define([ 'ractive' ], function ( Ractive ) { function testEventBubbling( fire ) { - test( 'Events bubble under "eventname", and also "component.eventname" above firing component', function ( t ) { + test( 'Events bubble under "eventname", and also "component.eventname" above firing component', t => { var ractive, middle, component; expect( 5 ); @@ -739,7 +738,7 @@ define([ 'ractive' ], function ( Ractive ) { fire( ractive.findComponent( 'component' ) ); }); - test( 'arguments bubble', function ( t ) { + test( 'arguments bubble', t => { var ractive, middle, component; expect( 5 ); @@ -762,7 +761,7 @@ define([ 'ractive' ], function ( Ractive ) { fire( ractive.findComponent( 'component' ) ); }); - test( 'bubbling events can be stopped by returning false', function ( t ) { + test( 'bubbling events can be stopped by returning false', t => { var ractive, middle, component; expect( 2 ); @@ -786,7 +785,7 @@ define([ 'ractive' ], function ( Ractive ) { fire( ractive.findComponent( 'component' ) ); }); - test( 'bubbling events with event object have component reference', function ( t ) { + test( 'bubbling events with event object have component reference', t => { var ractive, middle, component; expect( 5 ); @@ -827,13 +826,43 @@ define([ 'ractive' ], function ( Ractive ) { component.fire( 'someEvent', 'foo' ); }); - module( 'Event pattern matching' ); + module( 'Event bubbling' ); - function fired ( event ) { - ok( true ); - } + test( 'Reserved events do not bubble under non-namespaced name', t => { + var Component, component, ractive; + + expect( 6 ); + + Component = Ractive.extend({ + template: '{{foo}}', + data: { + foo: '' + } + }); - test( 'handlers can use pattern matching', function ( t ) { + ractive = new Ractive({ + el: fixture, + template: '', + components: { + component: Component + } + }); + + 'change reset teardown update'.split(' ').forEach( function( name ) { + ractive.on( name, shouldNotFire); + ractive.on( 'component.' + name, fired); + }) + + component = ractive.findComponent( 'component' ); + component.reset( component.data ); // also fires change + component.update(); // also fires change + component.set( 'foo', 'bar' ); // change + component.teardown(); + }); + + module( 'Event pattern matching' ); + + test( 'handlers can use pattern matching', t => { var ractive; expect( 4 ); @@ -851,7 +880,7 @@ define([ 'ractive' ], function ( Ractive ) { simulant.fire( ractive.nodes.test, 'click' ); }); - test( 'bubbling handlers can use pattern matching', function ( t ) { + test( 'bubbling handlers can use pattern matching', t => { var Component, component, ractive; expect( 5 ); @@ -882,7 +911,7 @@ define([ 'ractive' ], function ( Ractive ) { ractive.off(); }); - test( 'component "on-someEvent" implicitly cancels bubbling', function ( t ) { + test( 'component "on-someEvent" implicitly cancels bubbling', t => { var Component, component, ractive; expect( 1 ); @@ -907,7 +936,7 @@ define([ 'ractive' ], function ( Ractive ) { simulant.fire( component.nodes.test, 'click' ); }); - test( 'component "on-" wildcards match', function ( t ) { + test( 'component "on-" wildcards match', t => { var Component, component, ractive; expect( 3 ); @@ -932,7 +961,7 @@ define([ 'ractive' ], function ( Ractive ) { simulant.fire( component.nodes.test, 'click' ); }); - test( 'component "on-" do not get auto-namespaced events', function ( t ) { + test( 'component "on-" do not get auto-namespaced events', t => { var Component, component, ractive; expect( 1 ); @@ -957,6 +986,8 @@ define([ 'ractive' ], function ( Ractive ) { }); + + }; }); From 2194d7872bd6e3ca721ad3e11d6cc32a9cb6c8db Mon Sep 17 00:00:00 2001 From: marty Date: Sun, 31 Aug 2014 08:27:00 -0700 Subject: [PATCH 12/14] fire("") safe to call, though no event --- test/modules/events.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/modules/events.js b/test/modules/events.js index e7c1584106..5827f3c886 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -122,6 +122,16 @@ define([ 'ractive' ], function ( Ractive ) { simulant.fire( ractive.nodes.test, fakeEvent ); }); + test( 'Empty event names are safe, though do not fire', t => { + var ractive = new Ractive(); + + expect( 1 ); + ractive.on( '', function ( event ) { + throw new Error( 'Empty event name should not fire' ); + }); + ractive.fire( '' ); + t.ok( true ); + }); test( 'preventDefault and stopPropagation if event handler returned false', t => { var ractive, preventedDefault = false, stoppedPropagation = false; From 12b42c5b781ec67eb05586d4347e118431c0905c Mon Sep 17 00:00:00 2001 From: marty Date: Sun, 31 Aug 2014 08:29:42 -0700 Subject: [PATCH 13/14] forgot file with early return when no event name --- src/Ractive/prototype/shared/fireEvent.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Ractive/prototype/shared/fireEvent.js b/src/Ractive/prototype/shared/fireEvent.js index 48ab183be5..9452474a61 100644 --- a/src/Ractive/prototype/shared/fireEvent.js +++ b/src/Ractive/prototype/shared/fireEvent.js @@ -1,6 +1,7 @@ import getPotentialWildcardMatches from 'utils/getPotentialWildcardMatches'; export default function fireEvent ( ractive, eventName, options = {} ) { + if ( !eventName ) { return; } var eventNames = getPotentialWildcardMatches( eventName ); fireEventAs( ractive, eventNames, options.event, options.args, options.reserved, true ); } From a2a52efea0315b037344f55bd230b5f6703f4c06 Mon Sep 17 00:00:00 2001 From: marty Date: Sun, 31 Aug 2014 08:37:47 -0700 Subject: [PATCH 14/14] file clean-up, remove commented code and whitespace --- src/parse/converters/element.js | 10 ---------- .../items/Element/EventHandler/_EventHandler.js | 2 -- 2 files changed, 12 deletions(-) diff --git a/src/parse/converters/element.js b/src/parse/converters/element.js index dfca4a1aed..319563321d 100644 --- a/src/parse/converters/element.js +++ b/src/parse/converters/element.js @@ -10,7 +10,6 @@ import processDirective from 'parse/converters/element/processDirective'; var tagNamePattern = /^[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/, validTagNameFollower = /^[\s\n\/>]/, onPattern = /^on/, - // namespacePattern = /^on-\\*/, proxyEventPattern = /^on-([a-zA-Z\\*\\.$_][a-zA-Z\\*\\.$_0-9\-]+)$/, reservedEventNames = /^(?:change|reset|teardown|update)$/, directives = { 'intro-outro': 't0', intro: 't1', outro: 't2', decorator: 'o' }, @@ -125,15 +124,6 @@ function getElement ( parser ) { addProxyEvent( match[1], directive ); } - // on-* namespace - // is this ok to create faux-directive? - // or is it hacky and should this be own template primitive??? - // else if ( namespacePattern.test( attribute.name ) ) { - // if ( !element.v ) element.v = {}; - // directive = attribute.value ? processDirective( attribute.value ) : ''; - // addProxyEvent( '*', directive ); - // } - else { if ( !parser.sanitizeEventAttributes || !onPattern.test( attribute.name ) ) { if ( !element.a ) element.a = {}; diff --git a/src/virtualdom/items/Element/EventHandler/_EventHandler.js b/src/virtualdom/items/Element/EventHandler/_EventHandler.js index afed55634a..debd291f55 100644 --- a/src/virtualdom/items/Element/EventHandler/_EventHandler.js +++ b/src/virtualdom/items/Element/EventHandler/_EventHandler.js @@ -8,8 +8,6 @@ import render from 'virtualdom/items/Element/EventHandler/prototype/render'; import teardown from 'virtualdom/items/Element/EventHandler/prototype/teardown'; import unrender from 'virtualdom/items/Element/EventHandler/prototype/unrender'; - - var EventHandler = function ( element, name, template ) { this.init( element, name, template ); };