Custom Events in JavaScript

Posted by stephen on December 29th, 2009

Usually I use jQuery for custom events, but recently I’ve been working on a library that I want to be independent of other libraries, so I needed to create something to handle events on custom classes/objects.

For this sample, I changed the namespace to “ns”. You can change that to whatever you’d like.

Eventify

To “eventify” an object, you’ll use the ns.events.eventify(obj) method. Once that is done, your object will have “events” and “__listeners” properties, in addition to “dispatchEvent(eventType, eventData)”, “addEventListener(eventType, callback, bubbles)”, and “removeEventListener(eventType, callback||listener)” methods.

You may pass one or many objects to the eventify() method.
ns.events.eventify(obj);
or
ns.events.eventify(obj1, obj2, obj3);

You do not need to interact with the “events” and “__listeners” properties at all, unless you want to predefine a set of events for a particular object, usually for documentation purposes. Here’s an example of a custom class that uses this event class and predefines its events. Just to clarify, I am using the “id” property in this class for the event bubbling example that follows. It is not needed at all for the events to work.

addEventListener() / dispatchEvent()


ns.MyClass = ({
function MyClass(id){
ns.events.eventify(MyClass.prototype);
this.id=id;
}
MyClass.prototype={
id:null,
events:{
ready:new ns.events.Event("ready"),
error:new ns.events.Event("error")
},
property:null,
method:function(){}
}
return MyClass;
})();

If you do not define the events explicitly in the events property, they will be added automatically when a listener is added to the object. For instance:

var my = new ns.MyClass("my");
my.addEventListener("ready", function(){
alert(this.id+" is ready");
});

Would define my.events.ready.

dispatchEvent() and passing data

To dispatch an event, use the dispatchEvent() method. To pass data with a dispatched event, use the eventData parameter. For example:

my.addEventListener("ready", function(evt, data){
alert(data.hello);
});
my.dispatchEvent("ready", {hello:"Hello from dispatcher."});

Event bubbling

Event bubbling is manually assigned in an event’s “bubbleTo” property, because these are custom objects with no inherent heirarchy. The object defined as the “bubbleTo” property must share the event type that is being dispatched on the current object (they do not need to be instances of the same class). Implement bubbling as follows:

var parent = new ns.MyClass("parent");
var child = new ns.MyClass("child");
child.events.ready.bubbleTo = parent;

child.addEventListener(”ready”, function(evt){
alert(”sincerely, “+evt.target.id+”.”);
});
parent.addEventListener(”ready”, function(evt){
alert(”Sincerely, “+evt.target.id+”. With regards, “+evt.currentTarget.id+”.”);
});

child.dispatchEvent(”ready”);

To prevent subsequent callbacks on an object, call evt.stopImmediatePropagation(). To stop the bubbling, call evt.stopPropagation().

You may also prevent the bubbling of an event when creating the listener by using the “bubbles” parameter of the “addEventListener()” method.

var listener = child.addEventListener("ready", function(evt){
alert("sincerely, "+evt.target.id+".");
}, false);

removeEventListener()

Finally, to remove a listener from an object, use the removeEventListener(listener||callback) method. This method will accept the listener or the callback function used. Note that in the above example I have assigned the listener to “var listener”.
child.removeEventListener("ready", listener);
You may also use the remove() method of the listener itself.
listener.remove();

Download

Right click and choose “Save Target As” to download events.js.

Javascript Date difference as milliseconds, minutes, hours, days, weeks, months, or years

Posted by stephen on November 13th, 2009

I needed to figure out the difference between two dates in Javascript, so I wrote up this function. It takes into account the days in each month as well as leap years. It will calculate years, months, weeks, days, hours, minutes, seconds, and milliseconds, or any combination of those. For example, if you want months and hours, this’ll do ya.

Let me know if anyone finds a discrepancy in the calculations.

sample.html
timeSpan.js

View source of the sample page above for usage. Samples include a date comparison and a date countown.

IE6 and form.submit() bug workaround

Posted by stephen on November 3rd, 2009

IE6………one of life’s great mysteries. I think we may need some federally enforced browser standards more than a new health care system, but that’s just me.

In IE6, programatically triggering the submit() method of a form element doesn’t *always* seem to work. There’s no logical reason - it just doesn’t. I’ve run into this problem twice now. The first time, I just ended up building a query string out of the form variables and using window.location to send them to the action page. That was with a static form. I just hit this wall again with a form I’m building dynamically.

For *whatever* reason, delaying the submit action by 100ms works like a charm. I wish I could tell you more, but as with all IE6 bug fixes, when they work, I try not to ask questions.

Here’s what I ended up doing:

I tried building the form through the browser’s native methods, using document.createElement() and element.appendChild(), which didn’t make a difference, so I reverted back to created the form with jQuery. So, here’s what I ended up doing….

/* ....build form elements using jQuery... */
$j("body").append(form);

setTimeout(function(){
	form.submit();
}, 100);

Hope this saves someone some time! If you’re dealing with a form that is not being built dynamically, I have an inkling that this may not work. Still, delaying the event another way might. Try this, and if it works for you, let me know!

<a href="javascript:;" onclick="setTimeout(function(){ form.submit() }, 100)">Submit</a>

That’s the onclick event of an anchor tag, but the same would apply for the onclick of any element.

Javascript CSS Formatter

Posted by stephen on February 17th, 2009

I’ll probably post more on this later, but I just want to get it up for now.

Sometimes my CSS gets auto-formatted, which adds a whole bunch of “clutter” space that I don’t want. I find CSS easier to read when there is a visual hierarchy of the selectors and the HTML elements they represent, and if there is consistency in the order of the selector attributes.

The following CSS Formatter will place all of your selectors and their contents on a single line, and sort their attributes alphabetically.

For instance…

div#whatever, div.whatnot {
display:block;
background:transparent;
float:left;
}

becomes…

div#whatever,
div.whatnot {background:transparent; display:block; float:left;}

The formatter will IGNORE any space outside of the selector and its contents.

Try it out here:

http://blog.stephenrushing.com/wp-content/uploads/2009/02/cssFormat/cssformat.html

Getting an element’s html, not just the innerHTML

Posted by stephen on January 13th, 2009

The original content of this post involved a little function I wrote called “getNodeHtml”, which basically wrapped a given HTML element with a parent node, got the innerHTML of that temporary parent node, reset the DOM to its previous state, then returned the HTML string. I also used jQuery in the function, which seemed unnecessary to me.

Today I saw that IE has an outerHTML property, which does exactly what I need. I generally avoid giving IE credit, but they get a chocolaty kudos bar for this one.

The Array.indexOf() function I have gave me the idea to extend the prototype of the HTMLElement object so I could have it at my fingertips, and use the same property call in FF and IE (amoung others). After spending some time trying to add a “get outerHTML” property to the HTMLElement object, I found some useful info on Javascript Getters and Setters that pointed me in the right direction. The result…
if(document.__defineGetter__ && !HTMLElement.outerHTML){
HTMLElement.prototype.__defineGetter__(”outerHTML”, function(){
var parent = this.parentNode;
var span = document.createElement(”span”);
span.appendChild(this);
var HTML = span.innerHTML;
parent.appendChild(this);
delete span;
return HTML;
});
}

The __define[SG]etter__ methods do not exist in IE, so I’m still curious how to add a getter/setter to an existing prototype there. We could just create functions like value(’test’, ’set’) / value(null, ‘get’) or setValue(’test’) / getValue(), but bleh…those are methods, not properties.

********************UPDATE************************

I have found that manipulating the DOM momentarily to get the outerHTML of an element can cause some problems when you’re doing alot of DOM manipulation simultaneously. Instead, the following method builds the HTML of the current element by looping through its attributes - much more reliable!

if (document.__defineGetter__ && !HTMLElement.outerHTML) {
    HTMLElement.prototype.__defineGetter__("outerHTML", function(){
        var emptyTags = {
		   "img":   true,
		   "br":    true,
		   "input": true,
		   "meta":  true,
		   "link":  true,
		   "param": true,
		   "hr":    true
		};

		var attrs = this.attributes,
		tagName = this.tagName.toLowerCase(),
		str = "<" + tagName;
		for (var a = 0; a < attrs.length; a++)
		  str += " " + attrs[a].name + "=\"" + attrs[a].value + "\"";

		if (emptyTags[tagName])
		  return str + "/>";

		return str + ">" + this.innerHTML + "";

    });
}

Copyright © 2007 Stephen Rushing. All rights reserved.