Opened 10 years ago

Closed 8 years ago

#9692 closed enhancement (fixed)

[patch/cla] Include trigger.js in Dojo Core

Reported by: dante Owned by: dante
Priority: high Milestone: 1.7
Component: Core Version: 1.3.2
Keywords: jburke Cc: Eugene Lazutkin
Blocked By: Blocking:

Description

I have written a cross-browser trigger function, available in plugd. It has been suggested as an addition to Core. It is already namespaced dojo.trigger() and it fires Dom and Obj events.

http://code.google.com/p/plugd/source/browse/trunk/trigger.js

is the current trunk version.

http://code.google.com/p/plugd/source/diff?path=/trunk/trigger.js&format=side&r=128

is the revision where I exposed dojo._trigger as a fast path to dom triggering.

http://code.google.com/p/plugd/source/diff?path=/trunk/trigger.js&format=side&r=129

adds custom IE event support and mouseenter/leave support in FF

Still proposed:

Mixing extra event properties into the dispatched event (being able to force a keyCode for example, or other arbitrary data)

Otherwise, it seems to work well. The try{}catch{} in the IE fireEvent branch is less than ideal, but was unable to determine a way to see if an event was native or otherwise.

the .replace to fix for mouseenter/leave is the best I could do.

Comments welcome.

Attachments (1)

trigger.patch (19.6 KB) - added by dante 10 years ago.
code, unit tests and patch to base/event.js with trunk at r21589

Download all attachments as: .zip

Change History (28)

comment:1 Changed 10 years ago by Les

I'm impressed that firing native events is supported, which e.g. is not supported in Prototype.

Prototype requires that custom events be name-spaced, see this example: http://ajaxian.com/archives/pseudo-custom-events-in-prototype-16

Would it be helpful to name-space custom events?

Also, in Prototype I can associate a payload with an event. Is this supported?

Finally, the name 'trigger'... no offense, but it's long and difficult to spell. Can you rename it to just dojo.fire?

comment:2 Changed 10 years ago by Les

Custom events should take arguments, e.g.

jQuery(document).trigger('stuffHappened', ['Hello!']);

jQuery, MooTools?, Prototype and Ext support passing data with custom events. I don't see that it's possible to do this with dojo.trigger.

comment:3 Changed 10 years ago by Les

Why would it be useful to indirectly invoke a function using dojo.trigger?

// fire obj.method() in scope of obj
dojo.trigger(obj, "method");

I can do this instead, which is easier/shorter and I can pass args.

obj.method(args);

comment:4 Changed 10 years ago by dante

re: arguments ... it "sorta" already does this (in the obj-firing case)

dojo.trigger(foo, "bar", 1,2,3,4,5); calls foo.bar(1,2,3,4,5);

the plan was to allow those args to be passed along to the event listeners as well, but I've not investigated the requirements in code to accomplish this. Another option was to simply mix an object into the event object passed around, perhaps something like meta eg:

dojo.trigger("someId", "onclick", { a:"b" }); 

would trigger listeners like:

connect(node, "onclick", function(e){
    e.a == "b"
});

also note: dojo.publish/subscribe already passes along arguments like you describe.

{{{
dojo.subscribe("/some/topic", function(){ console.dir(arguments); });
dojo.publish("/some/topic", [1,2,3,4,5]);
}}}

re name: not sure, but fire() sounds like an appropriate name. Would prefer to get the details of the implementation out before renaming. Task is also a simple search/replace, or worst case, alias, to preserve plugd users using the function already.

with the availability of publish/subscribe and allowing _any_ non-namespaced custom event, I don't see the need to require/allow namespaced events.

re: "why would it be useful" - simply to mimic all the other functions in dojo that allow the hitch pattern (eg connect, subscribe, etc) and work on either nodes or objects. This was the reasoning behind exposing _trigger on dojo, so experienced users could skip the various isFunction tests leading to deciding if it is a dom event or object event.

comment:5 Changed 10 years ago by dante

re: foo.bar(a) being easier to add args.

dojo.trigger(foo, bar.baz, 1, 2, 3, 4, 5)

is the same as:

bar.baz.apply(foo, [1,2,3,4,5]);

or w/ anonymous function, to create closure:

dojo.trigger(this, function(a,b){

}, this.a, this.b);

as opposed to:

dojo.hitch(this, function(a,b){ ... }, this.a, this.b)();

comment:6 Changed 10 years ago by Les

Do custom events bubble up the DOM tree (as in jQuery or Prototype)? Can I prevent the bubbling? Please mention this in the inline doc.

comment:7 Changed 10 years ago by Les

Having custom events that bubble would allow removing event stubs, which are used frequently in dijit.

Here’s an example of dijit.Tree event stubs that could be removed.

onClick: function(/* dojo.data */ item, /*TreeNode*/ node, /*Event*/ evt){
	// summary:
	//		Callback when a tree node is clicked
	// tags:
	//		callback
},
onDblClick: function(/* dojo.data */ item, /*TreeNode*/ node, /*Event*/ evt){
	// summary:
	//		Callback when a tree node is double-clicked
	// tags:
	//		callback
}

I could use this code instead (or in addition to event stubs):

dojo.connect(tree.domNode, "onClick", function(evt) {
	var item = evt.data.item;
	var node = evt.data.node;
});

The 'onClick' event would have to be fired instead of calling the onClick() stub.

comment:8 Changed 10 years ago by Les

I'm pretty sure custom events would put an end to the empty stub technique.

http://www.sitepen.com/blog/2009/03/03/event-driven-design/

"Though dead simple, the concept of creating stub functions to act as connection targets is a handy tool to have at hand. Using it to fire your own events by spreading stubs throughout your code can make your systems very easy to extend."

Custom events are even simpler and more elegant because there's no need to create empty stubs.

comment:9 Changed 10 years ago by dante

re: custom events. I can mix in metadata into the event object no problem. Passing additional payload data is much more complicated (though i've not tried simply .apply on the fireEvent call, etc, but it isn't looking promising)

All browsers bubble custom events except IE. Even with createEventObject, the bubbles/cancleBubble property is not r/w, so no bubbling takes place. The only thing I can think of atm to accomplish this would be to iterate over node.parentNode firing _trigger from the new node each time to simulate bubbling, but that is a whole lot of cost ...

comment:10 Changed 10 years ago by Les

All browsers bubble custom events except IE.

I tried this jQuery code in IE6, and it works fine including bubbling and additional data being passed. I really hope you can get it done in Dojo :)

http://archive.dojotoolkit.org/nightly/dojotoolkit/dojo/tests/NodeList-manipulate.html

jQuery('#inputForm').bind('myEvent', function(evt, data) {alert(data);});
jQuery('#inputTextArea').trigger('myEvent', 'Hi there!');

comment:11 Changed 10 years ago by James Burke

For the .trigger in dojox.jq, I ended up having to call parent nodes to match the expected bubbling behavior in jquery:

http://bugs.dojotoolkit.org/browser/dojox/trunk/jq.js#L1147

It is ugly though.

comment:12 Changed 10 years ago by dante

Milestone: 1.4future
Status: newassigned

I don't want to walk to DOM to do this. Will need to come up with better API. not ready for core.

comment:13 Changed 10 years ago by dante

FYI: plugd/trunk trigger() function now handles custom bubbling in IE. still lacks curried data in all cases, but that could be next. as of r141

comment:14 Changed 10 years ago by dante

plugd r142 contains the capability to mix in custom data.

comment:15 Changed 10 years ago by James Burke

Neato! Some feedback:

1) In plugd source, it says this is supported:

dojo.trigger(d.global, function(){ /* stuff */ });

Is that just (function(){}).call(d.global)?

2) I am not sure the dojo.trigger on normal JS objects is that useful, seems simpler to just say obj.methodName() vs dojo.trigger(obj, "methodName"); If that saves some bytes to remove that support, then I would be OK with that.

3) The mixin of extraArgs, should that just be like an evt.data = extraArgs instead? That would allow extraArgs to have properties that may conflict with the event names, and avoid a mixin cost. Not sure if evt.data is safe to use, probably worth a spec check, but something like that.

4) For the NodeList? tie-in, it may be more efficient to create the event once, then pass that to some function that does the dispatchEvent/fireEvent work. That implies maybe splitting the existing _trigger into two functions perhaps. Will that work? Maybe we cannot reuse events across node.dispatchEvent() calls?

comment:16 Changed 10 years ago by dante

Thanks for the feedback @jbruke

1) yes.

2) I don't know that it is super useful, but is a minimal # of bytes to support the "pattern" that is put forth in other apis. eg: hitch.

dojo.trigger(obj, "method") === dojo.hitch(obj, "method")();
//but also
obj.method();
// but cleaner than:
var m = "methodName";
obj[m](); == d.trigger(obj, m);
// also if you want to execute a method of a seperate obj in that scope:
dojo.trigger(obj, other.method) == other.method.apply(obj, arguments); 
// with the bonus of currying. concat() is shorthand for more boilerplate.
dojo.trigger(obj, other.method, 1) == other.method.apply(obj, arguments.concat(1)) 

3) Perhaps mixing into a defined member is best. maybe 'customData'? 'data' seems safe, but yah should check the spec. Seemed cool it just comes in as the event object though. how often are you connecting to an event of type and trying to overload that type?

4) you are probably right, I was going with the plain'ole _adaptAs things we use elsewhere for compactness. I'd already considered breaking _trigger into a 2 fn branch based on document.createEvent sniff (just as it is), but it doesn't really avoid the try{} when doing bubbling, and would increase the byte size significantly

comment:17 Changed 10 years ago by dante

also liking dojo.fire() better.

comment:18 Changed 10 years ago by liucougar

about curried data, instead of:

dojo.trigger(foo, "bar", 1,2,3,4,5); // calls foo.bar(1,2,3,4,5); 

how about:

dojo.trigger(foo, "bar", [1,2,3,4,5]); // calls foo.bar(1,2,3,4,5); 

I think this is more inline with dojo.global

comment:19 Changed 10 years ago by liucougar

dojo.global -> dojo.withGlobal

comment:20 Changed 10 years ago by dante

so after a long evening of attempting to get a stoppable propagation in IE, I came up with a small solution that involves adding a line to Base.

@@ -486,6 +487,7 @@
 			// Called in Event scope
 			_stopPropagation: function(){
 				this.cancelBubble = true; 
+				this._customStopper && this._customStopper();
 			},

by triggering this "customStopper", I am able to make the dom-_trigger function exit it's parent-traversal logic, simulating a real stopPropagation.

Any objections?

comment:21 Changed 10 years ago by James Burke

I'm OK with the change, although I prefer a more specific name verb-based name, _stopProp() or _customStopProp(), but that is just bikeshedding. None of them sound great.

comment:22 in reply to:  15 Changed 10 years ago by Les

Replying to jburke:

2) I am not sure the dojo.trigger on normal JS objects is that useful

This feature is available in jQuery and I find it useful. I'd like to have it in Dojo :)

$({}).bind("myEvent", function(evt, param) {
   alert(param);
}).trigger("myEvent", "test")

comment:23 Changed 10 years ago by bill

Oh, I didn't think about calling trigger from a NodeList, I think James and I were envisioning doing a

dojo.trigger(context, function, arg);

rather than the current dojo way of

dojo.hitch(context, function, arg)();

or the native way of

function.call(context, arg);

Changed 10 years ago by dante

Attachment: trigger.patch added

code, unit tests and patch to base/event.js with trunk at r21589

comment:24 Changed 9 years ago by Les

jQuery supports trigerring custom events on JavaScript? objects. Some developers use this feature although it's not documented, see this:

http://forum.jquery.com/topic/triggering-custom-events-on-js-objects

In my particular case, I'd like to write a Dojo adapter for Highcharts. Currently, there are jQuery, Prototype, MooTools? and Ext JS adapters available for Highcharts. Having the ability to trigger custom events on JS objects would help me write this adapter.

Thank you and I hope we will see this feature in the 1.6 release.

comment:25 Changed 9 years ago by Eugene Lazutkin

Cc: Eugene Lazutkin added

comment:26 Changed 8 years ago by Kris Zyp

Can this be marked as resolve since dojo/on.js include event dispatching support? Anything missing?

comment:27 Changed 8 years ago by dante

Milestone: future1.7
Resolution: fixed
Status: assignedclosed

jep. doc pages seem in place, too. thanks!

Note: See TracTickets for help on using tickets.