Creating and Enhancing Dojo Classes
Like all top-notch JavaScript toolkits, Dojo tries to make its classes as flexible as possible, knowing that users of the toolkit may have different ideas about how a given class or class method should work. Luckily, Dojo provides you a number of methods by which you can subclass or modify existing classes. Let’s examine a few ways you can make Dojo classes exactly the way you like.
Creating Dojo Subclasses
The typical method by which you can create a Dojo class (or subclass) is by using the dojo.declare
method. The dojo.declare
method registers your given class within its designated namespace, while also subclassing any number of classes passed in the method’s second argument.
The following code shows the basic method by which one can create a subclass:
//dojo.declare('your.name.space',superClass,{ customProperties });
//or
//dojo.declare('your.name.space',[superClass1,superClass2,superClass3],{ customProperties });
dojo.provide('davidwalsh.Menu');
dojo.declare('davidwalsh.Menu',dijit.Menu,{
/* your custom properties and methods here */
myCustomProperty: true,
myCustomMethod: function() {
/* do stuff */
}
//all of dijit.Menu's methods are also part of this new class
});
The new class above, davidwalsh.Menu
, is a shiny new custom Dojo class that inherits all methods and properties of the dijit.Menu
class. My new class also features a new custom property and a new custom method which can do anything I want. Now that we know how to create a subclass, let’s create a realistic example of a useful subclass: davidwalsh.Menu
.
dojo.provide('davidwalsh.Menu');
dojo.declare('davidwalsh.Menu',dijit.Menu,{
//new option
allowSubmenuHover: true,
//another new option
popupDelay: 500,
//override the dijit.Menu method
onItemHover: function(item) {
if(this.isActive || this.allowSubmenuHover) {
this.focusChild(item);
//use the new settings to trigger popup
if(this.focusedChild.popup &&
!this.focusedChild.disabled &&
!this.hover_timer){
this.hover_timer = setTimeout(
dojo.hitch(this, '_openPopup'),
this.popupDelay);
}
}
if(this.focusedChild){
this.focusChild(item);
}
this._hoveredChild = item;
}
});
davidwalsh.Menu
is an enhancement of the original dijit.Menu
class; it features two new options and overrides a dijit.Menu
method with an end goal of providing a Menu whose PopupMenuItem opens when hovered over instead of clicked.
You may be wondering how to call methods of a superclass from your new subclass. That’s simple too:
//more methods above
someMethod: function() {
/* do anything you want here */
// call the superclass' "someMethod" method
// to execute superclass' original functionality
var result = this.inherited(arguments);
/* do anything you want here */
}
//more methods below
As you can see, creating subclasses is a breeze! But what can you do if you simply want to modify an existing Dojo class? Monkey patch it!
Prototype Modification or “Monkey Patching”
Sometimes subclassing existing Dojo classes isn’t the best option or an option at all. You may find yourself in the position where you can only patch an existing Dojo install; in that case, monkey patching is the ideal strategy. Monkey patching is the process by which you modify the prototype of an existing object (in our case a Dojo class). Positives of using a monkey patch approach are:
- All existing objects of this type are instantly changed.
- You don’t need access to the core Dojo files.
- Since you aren’t modifying the core Dojo files themselves, upgrading your Dojo builds will be exponentially easier as you wont have to hunt for your past changes.
- Your patches are more portable as they aren’t placed directly in the core Dojo files themselves.
The following code shows the monkey patching format:
(function(){
//save the old prototype method
var oldPrototypeSomeMethod =
dijit.someDijit.prototype.someMethod;
//modify the prototype
dijit.someDijit.prototype.someMethod = function(){
/* some new stuff here */
// call the old method *only if you
// want to keep some of the original functionality*
oldPrototypeSomeMethod.apply(this, arguments);
};
})();
Now let’s take a look at a realistic example. I was recently working with the FilteringSelect Dijit when I noticed that if the first option
element within the srcNode (select
element) has no value (an empty string value attribute), the element’s label will not display. A very odd bug and definitely not a desired result. What I was able to do was patch the Dijit’s postMixInProperties'
method to fix the problem.
(function(){
var dffsp = dijit.form.FilteringSelect.prototype;
//save the old prototype method
var oldPMIP = dffsp.postMixInProperties;
//modify the Dijit's prototype
dffsp.postMixInProperties = function(){
//if this select has no value and the first option is blank:
//set the displayedValue of this dijit to that label initially
if(!this.store && this.srcNodeRef.value == ''){
var srcNodeRef = this.srcNodeRef,
nodes = dojo.query("> option[value='']", srcNodeRef);
if(nodes.length){
this.displayedValue =
dojo.trim(nodes[0].innerHTML);
}
}
// call the original prototype method;
// we still want the original functionality to fire
oldPMIP.call(this, arguments);
};
})();
That’s just one great example of monkey patching. While monkey patching may not look like the most beautiful technique, it’s an essential part of patching your Dojo installs.
Extending Dojo Classes
The dojo.extend
method allows us to add new methods to the class’ prototype, thus providing the new methods to every instance of a given class. If a method name is passed to dojo.extend
that already exists for a given class, the new method overrides the original method.
The following code illustrates extending the dijit.Menu
class, allowing popup menu items to open during its label’s hover event in addition to the click event.
dojo.extend(dijit.Menu,{
allowSubmenuHover: true, //new setting
popupDelay: 500, //new setting
onItemHover: function() { //overriding original method
if(this.isActive || this.allowSubmenuHover) {
this.focusChild(item);
//use the new settings to trigger popup
if(this.focusedChild.popup &&
!this.focusedChild.disabled &&
!this.hover_timer){
this.hover_timer = setTimeout(dojo.hitch(
this, '_openPopup'), this.popupDelay);
}
}
if(this.focusedChild){
this.focusChild(item);
}
this._hoveredChild = item;
}
});
Note that the original onItemHover
method isn’t saved to be executed later — the entire prototype has been rewritten. Essentially we’re rubbishing the original functionality of this method. Now we have a dijit.Menu
class that conforms to our needs and doesn’t interfere with our Dojo build itself.
To Subclass, Extend, or Monkey Patch?
There are no hard and fast rules for when to extend a class or monkey patch a class. I do have a few recommendations though:
- Do *not* change one of the core Dojo files; monkey patch or extend.
- Monkey patch the class if you need access to the original prototype.
- Subclass with your own namespace when looking to share code between projects.
- If portability is an important goal, extend the class.
Extend Away!
Extending Dojo classes is the perfect way to fix bugs, enhance native Dojo classes, and prevent you from needing to repeat code. The limitations of Dojo are only those that you put on it!