čtvrtek 19. dubna 2012

Recursive DOM walk with Dojo

Ever wanted to recursively walk all children of some DOM node? As far as I was looking for it, DOJO does not support this feature for now. 


With extend function from dojoj/_base/lang I was able to extend Nodelist class, so whenever I use dojo's brilliant query function, I have my custom walk method available.


Just save this as a module wherever you want and include (require) it when you need it.  

define(["dojo/query", "dojo/_base/lang", "dojo/NodeList-traverse",], function(query, lang) {

var NodeList = query.NodeList;

lang.extend(NodeList, {

_walk: function (node, func) {
func(node);
node = query(node).children().first()[0];
while(node) {
this._walk(node, func);
node = query(node).next()[0];
}
},

walk:  function (func) {
this.forEach(function (node) {
this._walk(node, func);
}, this);
}
});

return NodeList;
});

Example of usage:

define([
"dojo/query",
"dojo/NodeList-traverse",
"./NodeList-walk", // !!! your NodeList-walk module
], function(query) {
query('.selector').walk(function(elm) {
console.log(elm)
});
});


Hope you like it! If you find any bug, just let me know in comments.


You can test this on http://jsfiddle.net/joshuaboshi/qpvMn/

středa 18. dubna 2012

Setting "blockNodeForEnter" for EnterKeyHandling plugin of Dijit/Editor

You might run into a problem when you would like to change default behaviour of Dijit Editor. By default, when user presses enter key in Dijit Editor, only <br /> tag is generated. What if you want to encapsulate text into <p> tags or <div> tags?


Writing post filter was not helpful (probably because EnterKeyHandling plugin processing was fired after post filter, but I am not sure about this). So I opened /dijit/_editor/plugins/EnterKeyHandling.js and the solution was right in front of me:
// This plugin has three modes:
//
// * blockNodeForEnter=BR
// * blockNodeForEnter=DIV
// * blockNodeForEnter=P
//
// In blockNodeForEnter=P, the ENTER key starts a new
// paragraph, and shift-ENTER starts a new line in the current paragraph.
Now the only problem: How to change default mode from BR to P? Where can it be done?


Constructor of EnterKeyHandling states:

if("blockNodeForEnter" in args){
args.blockNodeForEnter = args.blockNodeForEnter.toUpperCase();
}
We are close to finish! How to pass custom args when invoking this plugin? The Dijit Editor has array "plugins" in which are stored all plugins for editor. Invocation of these plugins is done in "addPlugin" method.


The magic is done on this line:
var args=lang.isString(plugin)?{name:plugin}:lang.isFunction(plugin)?{ctor:plugin}:plugin;
This line can be translated as: if passed plugin is string, us it as plugin name (for plugins like "bold", "italic" etc.). If passed plugin is function, it is considered to be dojo Class and is used as constructor ({ctor:plugin} part). And anything else is just passed as is.
The args variable is passed to plugin constructor few lines later. 


So! We can add custom object to plugins array of editor that will look somehow like this:

{
ctor: EnterKeyHandlingPlugin,
blockNodeForEnter: 'P'
}
Easy huh? Very unfortunate that this is undocumented or not easy to find :/
Complete example:


require([
"dijit/Editor",
"dijit/_editor/plugins/EnterKeyHandling",
        "dijit/_editor/plugins/LinkDialog",
        "dijit/_editor/plugins/FullScreen"
], function( DijitEditor, DijitEditorEnterKeyHandling) {
var editorConfig = {
plugins: [
"bold",
"italic",
"|",
"cut",
"copy",
"paste",
"|",
"createLink",
"fullscreen",
{
ctor: DijitEditorEnterKeyHandling,
blockNodeForEnter: 'P'
}
]
};
var editor = new DijitEditor(editorConfig);
}); 



čtvrtek 5. dubna 2012

Extending dojo standard modules

New dojo 1.7 is awesome. I really enjoy working with it. The new AMD modules let you do things easy and straightforward in javascript.


When you will developing some webapp, you will come across the situation when you will need to extend some dojo basic functionality.  For example I needed function for removing items from array at one particular index.


My first attempt was pretty straightforward and dumb.  I created new function in the module where I needed it:


define([
"dojo/_base/lang",
"dojo/_base/array",
...
], function(lang, array, ...) {

function removeFromArray(array, index) {
/* implementation */
}

/* rest of the module */

});

Few moment later, I needed same function in another module. Copy/paste is not a good idea, not even saying, that it is not good to have this kind of function in absolutely unrelated module. The solution is pretty elegant and easy. I just created a new module "my/extendedArray" that looked like this:


define([
"dojo/_base/lang",
"dojo/_base/array",
], function(lang, array) {

lang.mixin(array, {
remove: function (arr, indexToRemove) {
/* implementation */
}
});

return array;
});

Now, whenewer I need these extensions, I just switch "dojo/array" for "my/extendedArray" in the define or require. Anything else from "dojo/_base/array" works normally and I have available my own extensions in array module!

Of course, you can extend anything in dojo by this way. You just have to pick right extension mechanism - mixin, extend, or classy inheritance :-)