PDA

View Full Version : New Event API - Input before finalized



jack.slocum
4 Feb 2007, 5:33 AM
This will eventually find it's way to a blog post after the release, but in the meantime I am looking for feedback.

Currently on Element and EventManager, there are plethora of functions for attaching event handlers. IMO, this not only adds redundancy in the code, but makes the API cluttered. For the new release, there will be a new syntax that I believe will be both more powerful and easier to use. The the idea is to combine all the various listener functions into 1 function addListener() with the standard shorthand of on().

There are helper functions in the 1.0 compatibility file so existing code doesn't break.

Using the new syntax, different types of handlers can be combined and new options could be introduced without changing the API.

Also, all handlers would be called with 3 arguments: the standard "e" argument (like now) plus the target of the event, and the options object passed in. This way you can pass custom arguments to handlers:

Let's looks at some examples, assuming el is an Ext.Element and this.onClick is a handler function.

Custom Args are slightly different

function onClick(e, target, options){
alert(options.foo);
}
el.on('click', this.onClick, this, {foo: 'wtf'});

Note: In the new version, all events will be normalized by default (unlike now). Although the example below sets normalized to false, I really can't imagine anywhere you would want to do this in real code and it would only be there for backwards compatibility. Also, in the new version, you do not have to keep track of "wrappedFn" in order to remove normalized listeners. It handles this automatically.

Because I am lazy, all the examples below use the shorthanded "on()". ;)

Standard YUI Handlers
Current:

el.addListener('click', this.onClick, this, true);
el.on('click', this.onClick, this, true);

New:

el.on('click', this.onClick, this, {normalized: false}); <-- just for backwards compat

Ext Normalized Events
Current:

el.addManagedListener('click', this.onClick, this, true);
el.mon('click', this.onClick, this, true);

New:

el.on('click', this.onClick, this);

Delayed Listeners (delayed event firing)
Current:

el.delayedListener('click', this.onClick, this, true, 250);

New:

el.on('click', this.onClick, this, {delay: 250});

Buffered Listeners (buffers an event so it only fires once in the defined interval).
Current:

el.bufferedListener('click', this.onClick, this, 100);

New:

el.on('click', this.onClick, this, {buffer: 100});

"Handler" Listeners (prevents default and optionally stops propagation).
Current:

// prevent default
el.addHandler('click', false, this.onClick, this, true);
// prevent default and stop propagation
el.addHandler('click', true, this.onClick, this, true);

New:

// prevent default
el.on('click', this.onClick, this, {preventDefault: true});
// prevent default and stop propagation
el.on('click', this.onClick, this, {stopEvent: true});
// only stop propagation (not supported before)
el.on('click', this.onClick, this, {stopPropagation: true});

New Options
I was able to add the following options without additional functions and keeping the API clean. This is really the best part about the new syntax.

One time listeners removed automatically after the first fire:

el.on('click', this.onClick, this, {single: true});

Automatic event delegation!

el.on('click', this.onClick, this, {delegate: 'li.some-class'});

Combining Options
Using this new syntax, it would also be possible to combine different types of listeners:

// a normalized, delayed, one-time listener that auto stops the event and passes a custom argument (forumId)

el.on('click', this.onClick, this, {
single: true,
delay: 100,
stopEvent : true,
forumId: 4
});

Attaching multiple handlers in 1 call
This also opens the door for attaching multiple listeners in one shot, which I really wanted:


el.on({
'click' : {
fn: this.onClick
scope: this,
delay: 100
},
'mouseover' : {
fn: this.onMouseOver
scope: this
},
'mouseout' : {
fn: this.onMouseOut
scope: this
}
});

Or a shorthand syntax:


el.on({
'click' : this.onClick,
'mouseover' : this.onMouseOver,
'mouseout' : this.onMouseOut
scope: this
});

Observable
This new syntax is also supported by Observable. In an effort to clean up the code, all legacy YUI CustomEvent objects (e.g. splitter.onMoved.subscribe(...)) have finally been removed (they were deprecated long ago). If you are using the old deprecated events, your code will break (sorry).

Observable no longer uses YUI CustomEvent object. It uses Ext.util.Event, a new class similar to CustomEvent, but that supports the new syntax, is lightweight and significantly faster. The latest release of YUI included a dual logic in CustomEvent (FLAT/LIST) that slowed it down. There's also a bunch of logic in the CustomEvent constructor that makes them slow to create. Removing them gave a noticeable improvement (there are a lot of events, so any improvement is noticeable).

Component Support
The multi event attachment is also supported by Ext components:


grid.on({
rowclick : function(...){
// do something
},

rowcontextmenu : function(){
// do something else
},

scope: someObject,
arg: someArgument
});

Input Appreciated
I am pretty happy with how it turned out. A unified API for attaching listeners will help newbies getting started, and Ext pros get more done. What do you guys think?

lstroud
4 Feb 2007, 8:28 AM
simple and elegant....nice refactor

BernardChhun
4 Feb 2007, 8:31 AM
Love it. The "Attaching multiple handlers in 1 call" part is divine! No more of all that time spent on copying-pasting the addListener line!

mikegiddens
4 Feb 2007, 8:33 AM
This resturcture really makes it consistant on working with listeners. I agree it was better to have done it now then to wait and have things even more crazy later. Good Job. Can't wait to start using the new code.

manugoel2003
4 Feb 2007, 8:58 AM
simply gr8!!!

Stephan
4 Feb 2007, 9:01 AM
Wow, brilliant. Seems to be well thought out with easy and clean syntax.

Stroker
4 Feb 2007, 9:23 AM
Really great. Can't wait to use this new syntax!
As I understand it, if I wan't to add a listener to a dom element but not initiate a new Ext.Element I use:


Ext.fly('element-id').on('click', this.onClick, this);

jack.slocum
4 Feb 2007, 2:23 PM
Thanks guys. Is there anything I am missing I should add that you can think of?

Ext.fly('element-id').on('click', this.onClick, this);

Yes, that's right. It also works with groups elements, e.g.

Ext.select('#my-div a').on('click', this.onClick, this);

jack.slocum
4 Feb 2007, 2:37 PM
Btw, there's also another new class in this build "KeyNav", that simplifies and normalizes key navigation. Before the release I will need to go and put it in everywhere. What it does is check which event to listen for (keydown or keypress) based on browser, and fixes safari's bad key codes (it's nice to be on a Mac!). Also, repeated firing (holding the button) now works in all browsers!

Using it is something like this (from DatePicker.js):



var kn = new Ext.KeyNav(this.el, {
'left' : function(e){
e.ctrlKey ?
this.showPrevMonth() :
this.update(this.activeDate.add('d', -1));
},

'right' : function(e){
e.ctrlKey ?
this.showNextMonth() :
this.update(this.activeDate.add('d', 1));
},

'up' : function(e){
e.ctrlKey ?
this.showNextYear() :
this.update(this.activeDate.add('d', -7));
},

'down' : function(e){
e.ctrlKey ?
this.showPrevYear() :
this.update(this.activeDate.add('d', 7));
},

'pageUp' : function(e){
this.showNextMonth();
},

'pageDown' : function(e){
this.showPrevMonth();
},

scope : this
});



It also supports more keys, but those are the only ones used by the DatePicker (enter becomes a click).

By default it stops any key with a registered handler unless your handler returns true, it which it allows the event to continue. This means no more need 10 if statements in a switch to determine if the event should be stopped.

Using it is so easy that having anything that doesn't support key navigation IMO is unacceptable!

Bobafart
4 Feb 2007, 2:42 PM
Love the fact that Listeners are converged

don't like the shorthand on()

love Ext.KeyNav -- awesome!

jack.slocum
4 Feb 2007, 2:52 PM
don't like the shorthand on()

You probably won't like the other new shorthand then 'un'. ;) It removes a listener. Personally I like them because I think they read as a word:

el.on('click', this.onClick, this); to me reads as 'onclick' and

el.un('click', this.onClick); reads as 'unclick'.

This is just my preference though and the addListener/removeListener functions will always be the default. :)

MarcC
4 Feb 2007, 10:15 PM
Nice work, Jack. Always hated how there were so many functions that all did almost the same thing (soooo Java...) its much cleaner now!

One thing that bothers me is the 'options' ... passing "standard" options as well as "custom" options in the same object seems messy (and can get someone in trouble if they're not paying attention, or if new "standard" options are introduced later on). I usually try to avoid that kind of situation, but I don't know have any suggestions on how to fix that right now ...

Rock on!

Condor70
4 Feb 2007, 11:30 PM
One thing that bothers me is the 'options' ... passing "standard" options as well as "custom" options in the same object seems messy (and can get someone in trouble if they're not paying attention, or if new "standard" options are introduced later on). I usually try to avoid that kind of situation, but I don't know have any suggestions on how to fix that right now ...

I agree. How about:


el.on('click', this.onClick, this, {
single: true,
delay: 100,
stopEvent: true,
args: {
forumId: 4
}
});

Animal
5 Feb 2007, 12:35 AM
Being able to explicitly specify an argument list as opposed to just any nonstandard option properties becoming arguments is a good idea. Passing nonstandard properties in will lead to problems.

But there's no point in specifying a object with named propetries. The best solution would be



el.on('click', this.onClick, this, {
single: true,
delay: 100,
stopEvent: true,
args: [ 4 ]
}
});

Which would take the place of



el.on('click', this.onClick.createCallback(4), this, {
single: true,
delay: 100,
stopEvent: true
}
});

JeffHowden
5 Feb 2007, 12:41 AM
Being able to explicitly specify an argument list as opposed to just any nonstandard option properties becoming arguments is a good idea. Passing nonstandard properties in will lead to problems.

I've got to weigh in to say I completely agree with this.

manugoel2003
5 Feb 2007, 12:44 AM
I disagree Animal..... when the number of nonstandard arguments increase it might be handy to have a named object.... it is much easier to access the arguments by name than using arguments[3]..... and I like the idea of a separate array for nonstandard arguments....

Animal
5 Feb 2007, 12:49 AM
What would be the significance of names though? Nobody is going to use them. They're just going to be accessed in sequence and added to an Array which will be used as the parameter to Function.apply()

Might as well just specify an Array which can be passed straight into Function.apply()

manugoel2003
5 Feb 2007, 1:50 AM
If I am not wrong, if I want 2 arguments namely URL and CUSTOMERID, then with named arguments I will get private variables with same name so I can use them as regular variables, like

alert(URL);
or
alert(URL.split("?")[1]);
dont u think that it is better than

alert(arguments[0]);
or
alert(arguments[0].split("?")[1]);
but I guess I can still live with arguments array if I have to do something as simple as illustrated above..... but what if I have to do complex calculations and have to make repeated references to the argument.... and it will get increasingly confusing if there are many arguments.... moreover, it is very difficult to make out what is happening by someone else who is reading my code.... I think both methods should be there, or at least named arguments should not be left out, they are pretty handy

Animal
5 Feb 2007, 2:00 AM
No, they're just arguments to a function. They don't have names until you are inside your function!



el.on('click', this.myFunc, this, {
single: true,
delay: 100,
stopEvent: true,
args: [ 4, "foo", {bar:1} ]
}
});


will call



myFunc: function(blather, wibble, gump)
{
}


blather will be passed as the number 4, wibble will be the string "foo", and gump will be an object containing the property "bar" as the nubber 1.

There's no point at all in giving names to them in the on() call.

manugoel2003
5 Feb 2007, 3:29 AM
In that case I buy your argument.... I cant think of any particular use of named parameters now, but if I come up with something I'll continue here

jack.slocum
5 Feb 2007, 3:51 AM
You are welcome to wrap your non-standard arguments in an object as you did to prevent future collisions. This isn't a terrible idea either. However, it will always pass in the the whole options object, so you will be accessing your args as:


function foo(e, target, options){
var foo = options.args.foo;
}

That's your choice of course. :)

Animal, passing an array and having it applied would be interesting but it would overwrite the default arguments. I will look at the code and see what it would take to put it in. Reserving a named item for args doesn't seem like a bad idea. I just wonder if there are any benefits vs using a an object with named items.

Animal
5 Feb 2007, 4:04 AM
Animal, passing an array and having it applied would be interesting but it would overwrite the default arguments.

I thought this was the idea: that we could remove the need to call createDelegate/createCallback with a list of override arguments. We could even have an append boolean/position just like createDelegate, so you could have



el.on('click', this.myFunc, this, {
single: true,
delay: 100,
stopEvent: true,
args: [ 4, "foo", {bar:1} ],
append:true
}
});


Which would mean that those args are appended to any existing argument list

or



el.on('click', this.myFunc, this, {
single: true,
delay: 100,
stopEvent: true,
args: [ 4, "foo", {bar:1} ],
append:1
}
});


Which would insert those args into the existing argument list at position 1

tony.summerville
13 Mar 2007, 12:16 PM
So what was decided? How can I pass custom arguments to my event handler?

jon.whitcraft
13 Mar 2007, 4:57 PM
So what was decided? How can I pass custom arguments to my event handler?

In my working with Ext 1.0 i just did this:



el.on('click', this.myFunc, this, { args: {elm: el, 'foo': 'bar'}});


then i accessed them by doing this



myFunc : function(e,el,args) {
console.log(args);
}

SteveEisner
13 Mar 2007, 7:14 PM
I constantly have to wrap my on() handlers in a createDelegate to make sure that the "this" paramter stays the same in the callback. Has anything changed in this regard in 1.0?

Example:
pageEl.select('.CodePicker', true).each(this.makeCodePicker.createDelegate(this));
or
layout.getRegion('center').on('panelactivated', this.showSection.createDelegate(this));

brian.moeskau
13 Mar 2007, 8:21 PM
I constantly have to wrap my on() handlers in a createDelegate to make sure that the "this" paramter stays the same in the callback. Has anything changed in this regard in 1.0?

Example:
pageEl.select('.CodePicker', true).each(this.makeCodePicker.createDelegate(this));
or
layout.getRegion('center').on('panelactivated', this.showSection.createDelegate(this));

Reading the original post, it seems that with the new options you could do:


layout.getRegion('center').on('panelactivated', this.showSection, {scope: this});
I haven't tried it myself, but I think that's right. In and of itself it's not much shorter, but I think once you start combining other options it becomes significantly more powerful.

aefitzhugh
14 Mar 2007, 1:04 PM
Reading the original post, it seems that with the new options you could do:


layout.getRegion('center').on('panelactivated', this.showSection, {scope: this});


Jack's first example in the orginal post shows it like this:


el.on('click', this.onClick, this, {foo: 'wtf'});

I.e., the third arg specifies the scope. That's what I'm doing and works nicely.

-- Andy

jheid
16 Apr 2007, 1:45 AM
I am missing the inline eventhandlers:



new Ext.Grid ({
listeners: [
{
name: 'click',
handler: function () {}
}
]
});

(or something like that)

I think this was posted by Jack before. I would like it very much as I build grid editors dynamically and have to work with several iterations instead of a big constructor.

JOERN

jack.slocum
16 Apr 2007, 2:21 AM
It's already in 1.0. ;)

http://extjs.com/forum/showthread.php?t=4553

jheid
16 Apr 2007, 2:34 AM
It's already in 1.0. ;)

http://extjs.com/forum/showthread.php?t=4553

Okay, great!

I thought it's basis for discussion again ;)

Symbi0nt
16 Apr 2007, 3:30 AM
@jack: I'm a bit nervous about your CSS naming for further work. If I see it correct it's today almost everywhere .x-name1-name2 I would suggest (even if you get 2 chars more per identifier) to rename it to .ext-name1-name2 .

I did'nt searched the web for interoperatable JS / CSS Framework naming conventions. But I think it would make more sence to have a "reminder" which fits the Framework name.

Some day in the furture pp might start using ExtJs with 5 other Frameworks and the mess starts.

And btw. (topic) I like the new way too! B)

XASD
3 May 2007, 12:25 AM
Good changes IMO,about what is missing...I believe all API changes could be justified only by appearing successful use patterns,so erlier this changes find its way into main codebase,faster you'll get constructive feedback.By the way,in JQuery "multiple handlers" solved through "chaining" functional pattern,not bad at all and could be option too.

Thanks a lot for hard work.

GalaxySong
8 May 2007, 1:46 AM
What a great leap! I love it!
As to customer parameters, I suggest using prefix "_" (for example, "_rowCount") which is always safe.

tryanDLS
8 May 2007, 7:35 AM
The API changes are final, so this thread is closed to new posts. Questions should be posted in Help.