Adding custom events to widget

A

Anthony Levensalor

Hey guys!

I'm writing a widget wherein I want the pages using it to be able to
subscribe to events on it. I did my own research, not asking anyone to
write it for me, just want to make sure I'm on the right track here.

Basically, I'm creating a private collection of listeners, then
providing a mechanism for adding functions to that event.

A dispatch function loops through the listeners, finds the right one,
and calls the observer.


// Constructor
function mySampleWidget () {
// Collection of listeners (private)
var listeners = [];

// Function for adding listeners
this.addEventListener =
function (evName, observer, stopHere) {
listeners.push(
{
"event":evName,
"observer":eek:bserver,
"stop":stopHere} );
};

// the dispatch function.
this.dispatch =
function (calEvent) {
for (var i = 0; i < listeners.length; ++i) {
var listener = listeners;
if (listener.event == "on" + calEvent) {
listener.observer();
if (listener.stop) {
break;
}
}
}
}

mySampleWidget.prototype.show =
function() {
this.dispatch("beforeshow");
// code to display widget
this.dispatch("aftershow");
}

I'm a little worried about the dispatch method, right now it has
pseudo-protected access, which means someone could call it from outside
on a whim. I would like to make it private, but I was unable to wrap my
head all the way around Crockford's article on public/private/protected
pseudo access sufficiently.

The code works in my tests so far (unless I pass in some nulls, it
breaks real easy because I'm not type checking and making sure
everything is right before I assign.

The usage would be:

var w = new myWidget();
w.addEventListener(
"onbeforeshow",
function() {alert("Before Show");},
false};
w.show();

At this point, the application does display the proper message/call the
proper function, and does so for several different listeners, unless and
until one has a stop property that evaluates to true, and it will not
process listeners after that.

A few questions:
1. Is this a good path to be on? Is there something in there I'm doing
that's ingorant of the possible repercussions beyond the type and value
checking I've not yet implemented?

2. Can you provide any insight into how I can effectively hide the
dispatch method while still giving the other prototyped functions access
to him?

3. What other issues/caveats have you run into with this kind of thing
that I should be thinking about?

4. OT for this post, but IE7 seems not to like the
"application/javascript" type attribute. Just more of the same?

Thanks a million guys, you are appreciated.

~A!

--
Anthony Levensalor
(e-mail address removed)

Only two things are infinite, the universe and human stupidity,
and I'm not sure about the former. - Albert Einstein
 
D

David Mark

Hey guys!

I'm writing a widget wherein I want the pages using it to be able to
subscribe to events on it. I did my own research, not asking anyone to

I take it that YUI was part of that research.
write it for me, just want to make sure I'm on the right track here.

It depends on your intended destination.
Basically, I'm creating a private collection of listeners, then
providing a mechanism for adding functions to that event.

A dispatch function loops through the listeners, finds the right one,
and calls the observer.

// Constructor
function mySampleWidget () {

Constructor names should be capitalized.
  // Collection of listeners (private)
  var listeners = [];

  // Function for adding listeners
  this.addEventListener =
   function (evName, observer, stopHere) {
    listeners.push(
     {
       "event":evName,
       "observer":eek:bserver,
       "stop":stopHere} );
    };

This is a bad name for this as it has nothing to do with adding event
listeners. Something like "registerCallback" would make more sense.
I know YUI calls these "custom events", and bundles this functionality
with their event module, but this seems like an odd choice to me.
  // the dispatch function.
  this.dispatch =
   function (calEvent) {
    for (var i = 0; i < listeners.length; ++i) {
     var listener = listeners;
     if (listener.event == "on" + calEvent) {


Why store the "on?"
      listener.observer();

You should have a context parameter to the registration function that
allows you to set the "this" identifier for the callback. Then you
could do something like this:

listener.observer.call(listener.context || this);

YUI calls this the "scope" of the event, which makes no sense at all.
      if (listener.stop) {
        break;                          
      }

Why not use the return value of the observer (callback) function?
     }
    }
  }            

mySampleWidget.prototype.show =
   function() {
     this.dispatch("beforeshow");
     // code to display widget
     this.dispatch("aftershow");
   }

I'm a little worried about the dispatch method, right now it has
pseudo-protected access, which means someone could call it from outside

It doesn't seem protected at all to me. The only thing hidden is the
listeners array.
on a whim. I would like to make it private, but I was unable to wrap my
head all the way around Crockford's article on public/private/protected
pseudo access sufficiently.

In this example, you would have to move the creation of the show
method inside the constructor.
The code works in my tests so far (unless I pass in some nulls, it
breaks real easy because I'm not type checking and making sure
everything is right before I assign.

There is no point in doing that anyway. Just stipulate the required
types in the documentation.
The usage would be:

var w = new myWidget();
w.addEventListener(
   "onbeforeshow",
   function() {alert("Before Show");},
   false};
w.show();

At this point, the application does display the proper message/call the
proper function, and does so for several different listeners, unless and
until one has a stop property that evaluates to true, and it will not
process listeners after that.

A few questions:
1. Is this a good path to be on? Is there something in there I'm doing

Personally, I think this sort of syntactic sugar is overkill for all
but the most complex applications. If a page is so complex that you
need to assign multiple teams of developers to it, then perhaps
something like this would make sense. Otherwise, why would the
widgets involved need more than one callback per "event."
that's ingorant of the possible repercussions beyond the type and value
checking I've not yet implemented?

The only repercussions of that would be if a developer fails to read
the documentation or mistakenly passes invalid parameters, they will
get an error. That is how it should be.
2. Can you provide any insight into how I can effectively hide the
dispatch method while still giving the other prototyped functions access
to him?

You can't.
3. What other issues/caveats have you run into with this kind of thing
that I should be thinking about?

Do you plan to create one base widget that all other widgets inherit
from? Will all widgets really need this sort of event system?
4. OT for this post, but IE7 seems not to like the
"application/javascript" type attribute. Just more of the same?

Use text/javascript or nothing.
 
A

Anthony Levensalor

David Mark said:
[snip]
I take it that YUI was part of that research.
No, should it have been? I was looking for articles and examples on
events as they relate to classes, but I largely eschewed the big
libraries. Subscribe was a term I picked up from one of the articles I
read, though. I've worked with YUI a little, so I understand why you'd
say that.
It depends on your intended destination.
Just for the science of it, really. I just like to tinker sometimes.

[snip]
This is a bad name for this as it has nothing to do with adding event
listeners. Something like "registerCallback" would make more sense.
I know YUI calls these "custom events", and bundles this functionality
with their event module, but this seems like an odd choice to me.
Ok, so I must have read something on this from someone who uses it
heavily, then. Your suggestion makes a lot more sense, thanks!

[snip]
Why store the "on?"
I debated that for a bit, decided to put it in because the events all
have that, and I am after all trying to pass these off as events, right?
:) Seemed like the way to go, but in retrospect it would be silly to do
that for real, considering that one day more events may come down the
pike with the standard naming and I would be stomping all over them, or
they me, or any other unintended and unexpected result. Thanks again.
You should have a context parameter to the registration function that
allows you to set the "this" identifier for the callback. Then you
could do something like this:

listener.observer.call(listener.context || this);
Where's my net? Whooosh, right over the head. I'll look up context
variavles.
YUI calls this the "scope" of the event, which makes no sense at all.
What the hell for? I at least understand they are not real events, but
just callbacks in a flimsy costume, and I wouldn't be fooling anyone but me.
Why not use the return value of the observer (callback) function?
Most likely because I did not think of it. Thanks again.

[snip]
It doesn't seem protected at all to me. The only thing hidden is the
listeners array.
Yeah, that sentence didn't make much sense.
In this example, you would have to move the creation of the show
method inside the constructor.
And I didn't want a behemoth constructor, but that does get my head the
rest of the way around it. Danke.
There is no point in doing that anyway. Just stipulate the required
types in the documentation.
I've never been good at that. I like to try to fail gracefully if I can,
but Javascript errors are never the end of the world, so I imagine
you're right. Maybe I'll just stay on the light side of it, do some
small basic checks.

[snip]
Personally, I think this sort of syntactic sugar is overkill for all
but the most complex applications. If a page is so complex that you
need to assign multiple teams of developers to it, then perhaps
something like this would make sense. Otherwise, why would the
widgets involved need more than one callback per "event."

Absolutely true, I've never needed more than one handler for any real
life event, never mind a pseudo-bs event from a widget. I'm messing
with the idea, seeing what the language will and will not do when I try,
ya know?

I am fascinated by the idea itself, and so am exploring a bit, but I'm
finding it to be, as you said, largely useless and more than a little
nonsensical. Fun as the dickens, though. :)
You can't.
Darn. That's the answer I came up with too. Oh well, no big deal since
it won't ever see the outside of the sandbox, more likely than not.
Do you plan to create one base widget that all other widgets inherit
from? Will all widgets really need this sort of event system?
Perhaps, but I sincerely doubt it. When I'm feeling playful with a code
idea, I just write it out, code it, and then worry about whether I need
it for anything. Course, this is not on the clock time. :)
Use text/javascript or nothing.
That I do. It was another of those little experiments, except I wes
confusing myself with some of the new 1.8 (incomplete) docs I found, and
just started messing around. Notcied FireFox executed the code for me
and IE didn't, then went back to standard.

Thanks, David! Have a happy New Year.

--
Anthony Levensalor
(e-mail address removed)

Only two things are infinite, the universe and human stupidity,
and I'm not sure about the former. - Albert Einstein
 
S

slebetman

David Mark said:


That I do. It was another of those little experiments, except I wes
confusing myself with some of the new 1.8 (incomplete) docs I found, and
just started messing around. Notcied FireFox executed the code for me
and IE didn't, then went back to standard.

It's better to use nothing. That's because W3C defines it as "text/
javascript" so IE is behaving correctly according to standard. But
unhelpfully IETF decided to define the MIME type of javascripts as
"application/javascript". So, technically, web servers should serve
pages as content-type "application/javascript" but in HTML it should
be declared as "text/javascript" (which is actually a non-existent
MIME type according to IETF). That is why it's better to just say
<script></script>.
 
D

dhtmlkitchen

Hey guys!
I'm writing a widget wherein I want the pages using it to be able to
subscribe to events on it. I did my own research, not asking anyone to
// the dispatch function.
this.dispatch =
function (calEvent) {
for (var i = 0; i < listeners.length; ++i) {
var listener = listeners;
if (listener.event == "on" + calEvent) {


Why store the "on?"
listener.observer();

You should have a context parameter to the registration function that
allows you to set the "this" identifier for the callback. Then you
could do something like this:

listener.observer.call(listener.context || this);

YUI calls this the "scope" of the event, which makes no sense at all.
if (listener.stop) {
break;
}

Why not use the return value of the observer (callback) function?

Yes, you could have something like:
if(callback.call(context, e) == false) {
break; // cancel subsequent callbacks.
}

But this is jumping ahead to the event argument/parameter.

In a pub/sub registry, it's usually handy to push a payload in an
event object. This is called from the object that fires the event.
It's like that object says: "The 'choose' event occured, and here's
what happened: newValue = xxx, oldValue = yyy." You could build this
object like:

/** Fires a "choose" event if a blah was chosen.
MySampleWidget.clickHandler = function( ev ) {
var target = getTarget( ev );
...
if(condition) { // fire custom event.
var prevValue, newValue;
...

var ce = {
domEvent : ev,
type : "choose",
prevValue : prevValue,
newValue : newValue,
currentTarget: this // This will help in problems where you've
used a | context | arg/property.
}
this.choose(ce);
}
};

Now this can seem a bit messy, so you might want to consider building
you're event through a constructor. This is more a matter of style,
but it can make the code that you're reading more succinct.

var ce = new ChooseEvent(ev, "choose", prevValue, newValue, this);
- or -
this.choose ( new ChooseEvent(ev, "choose", prevValue, newValue,
this) );

It doesn't seem protected at all to me. The only thing hidden is the
listeners array.


In this example, you would have to move the creation of the show
method inside the constructor.




There is no point in doing that anyway. Just stipulate the required
types in the documentation.
I think error conditions are important. I noticed something about DOM
events. Try this:

function err() { throw Error('bad'); }
function ok() { document.title = 'happy'; }
document.body.addEventListener('click', err, false);
document.body.addEventListener('click', ok, false);

You can naturally expect that you'll get the callbacks firing.

Other registries will fail in the fireCallbacks function, and
everything will fail. I remember a specific case on a Friday night. I
was scheduled for a massage, but got called back in because the entire
page broke. We had something like:

YAHOO.util.Event.addListener("hubSelect", "change", hubSelected);

function hubSelected() {
document.getElementById( 'newItem' ).style.display = "none";

well this callback failed. My boss had specified that the button must
be hidden and my coworker tried to omit this using conditional logic
in the JSP, and so it did not exist. This was a last minute thing my
boss asked me to do. I do not like last minute checkins on untested
code, and told him so. He told me to do it anyway.

The next callback to hubSelect was the important one. Since the first
one failed, the second one did not fire, and the entire page was
broken from that point one.

I went back in, figured out the condition in about 10 minutes and the
use-case was tried and functioned properly. It was an easy fix for
that one.

Missing my massage was annoying. I broke my appointment with the woman
who was waiting for me. It pissed me off.

So I thought that it would make sense to wrap the callbacks in a try
catch.

for(var i = 0; i < callbacks.length; i++) {
try {
if( callbacks.call( context, ev ) == false ) break; // cancel
subsequent calls.
}
catch (ex) {
setTimeout("throw ex", 1);
}
}

I was concerned about performance, but I tried it out with mousemove
and it did not seem to be a problem. I did not find try/catch to be a
huge performance hit and the safety it got me seemed to make it a good
choice.

I've written this out here:
http://dhtmlkitchen.com/scripts/draglib/DragModule/src/dragUtils.js

I called it EventPublisher.

It's different in one other aspect: The | return false | happens at
the end of the callStack. I left room for beforeEach and afterEach,
which is equivalent to your | stopHere | argument. I did not find a
need for that, and so did not want to create tests for something I
don't need.
How do you justify the stopHere functionality?

Are there other pieces of code that could use event registry
functionality? Would it make sense to move the event pub/sub code into
one place?
Is there something in there I'm doing

0) consider a custom event.
1) the error handling - the callstack should not be breakable by
simply throwing a function in that throws an error. (try/catch
approach)
2) consider the | stopHere | boolean. It would also be possible to
have a function there, if you need it, passing the event param back to
that function.
The only repercussions of that would be if a developer fails to read
the documentation or mistakenly passes invalid parameters, they will
get an error. That is how it should be.




You can't.
You can wrap dispatch in a function.

EventPublisher.fire = function(publisher) {
// This closure sucks. We should have partial/bind in ES.
// If we did, this could more reasonably be a prototype method.

// return function w/identifier doesn't work in Safari 2.
return fireEvent;
function fireEvent(e) {

I left me "this closure sucks" comment in.

I can see why the callStack should be private. If you reassign the
callstack, widget.callStack = false, then there will be errors. That
is bad, but can be avoided.
Do you plan to create one base widget that all other widgets inherit
from? Will all widgets really need this sort of event system?
I would consider using either a registry or using an AOP crosscut to
"borrow" the methods that exist in a generic EventPublisher. I would
prefer either of these (registry or AOP crosscut) over having it in
the superclass. Putting it in the superclass would seem to violate
some OOD concepts like SRP and LSP.

Garrett
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,821
Latest member
AleidaSchi

Latest Threads

Top