Bottom of Two dijit.InlineEditBox with dijit.form.Textarea Editor Does Not Open on Single Click

dijit.InlineEditBox is great, but if you have two stacked on each other in containers with undefined heights, things can get a little buggy for a user. Example:

The problem is that if you open up id=”one” first, the page height expands two rows to make a textarea, so when you click on id=”two”, the page re renders and doesn’t open “two”! (This will work going from “two” to “one” since the top part of the page won’t change.)

If you watch the focus and blur events you’d see the following from “two” to “one”:

Focus: two
Blur: two
… click one
… two closes
Focus: one
… one opens
Blur: one

If you watch the focus and blur events you’d see the following from “one” to “two”:

Focus: one
Blur: one
… click two
… one closes
Focus: two
…. NOTHING …..

Basically for some reason it doesn’t open. So to fix this, just implicitly call edit() during onFocus.


...
onFocus: function()
{
this.inherited(arguments);
this.edit(); // Force (technically it may get called twice now, oh well.)
},
....

dijit.InlineEditBox editWidget is undefined

So I have a save all button to save all dijit.form elements at once, but first I wanted to make sure that they were all valid. Piece of cake, all of them have isValid() methods so all I have to do is loop through them all and check to see if they are all valid… wrong.

Apparently you can not access the editor, editWidget, editNode etc of an dijit.InlineEditBox until AFTER it has already been displayed. This is retarded since it is HIGHLY likely that a user won’t open every edit box on the page *sigh*. This took some digging in the source code to find out, but if you look at the edit() function for dijit.InlineEditBox around line 190:


if(this.wrapperWidget){
this.wrapperWidget.editWidget.attr("displayedValue" in this.editorParams ? "displayedValue" : "value", this.value);
}else{
// Placeholder for edit widget
// Put place holder (and eventually editWidget) before the display node so that it's positioned correctly
// when Calendar dropdown appears, which happens automatically on focus.
var placeholder = dojo.create("span", null, this.domNode, "before");

// Create the editor wrapper (the thing that holds the editor widget and the save/cancel buttons)
var ewc = dojo.getObject(this.editorWrapper);
this.wrapperWidget = new ewc({
value: this.value,
buttonSave: this.buttonSave,

You see that the editor isn’t created until the actual edit function is called. So basically to force all editors to be created:


try{
inlineeditbox.edit();
// THIS IS UNDEFINED until edit();
inlineeditbox.wrapperWidget.editWidget.isValid();
}
catch(e){
console.log('Editor doesn't have validation');
}

Unregistering Child Widgets from Custom Widget Template

I’ve been having this problem of child widgets hanging around after I destroy a custom widget. For some reason I just can’t get them to destroy when I call destroy / destroyRescursive on my custom widget. This is a problem because every time I try to replace the current instance of my widget with a new one I’ll get the “already registered” error.


var my_tabs = new my.dijit.layout.TabContainer.MyTabs(); // Errors 🙁

To combat this I’ve come up with a little technique to remove them during my custom widget creation life cycle. Just hook into the postMixInProperties function and remove the child widgets if they exists:


postMixInProperties: function()
{
// Destroy the child tabs if they exists. This is just a checker
// incase they don't get destroyed somehow.
this._destroyChildTab('search_form_pane');
this._destroyChildTab('a_tab');
this._destroyChildTab('another_tab');
this.inherited(arguments);
},

/**
* Destroy the tab if it exists.
*/
_destroyChildTab: function(tab)
{
try { dijit.byId(tab).destroy(true); } catch(e){}
}

Since my custom widgets use templates, I know the ID of any of its child widgets, so I’m able to do this. Below is an example of one of my TabContainer templates:

TabContainer / BorderContainer Not Resizing with Custom Widget

I have the following custom widget that defines a tab container in it’s template:


dojo.require("dijit.layout.TabContainer");
dojo.provide('custom.dijit.layout.TabContainer.Courses');

dojo.declare('custom.dijit.layout.TabContainer.Courses', [dijit._Widget, dijit._Templated],
{
widgetsInTemplate:true,
templateString: dojo.cache('custom.dijit.layout.TabContainer', 'Courses.html'),

...
}

The template looks like:

When the widget loads, the template doesn’t render correctly until after the browser is resized. Apparently if you create widgets that contain dijit.layout widgets you have to tell them to resize after they render. All you have to do is add a resize function, that will call all of the ContentPane’s resize functions, to your widget and everything will look great again 🙂


resize: function()
{
// Tell the content panes to resize.
dojo.forEach(this.getChildren(), function (child) { child.resize(); });
}

A Dojo Enhanced Grid Example with Context Menu

For some reason dojox.grid.EnhancedGrid doesn’t have an out of the box way to access the selected rows data and send it to the context menu. Google doesn’t help either so hope this helps someone. Here is how to access the selected row from the context menu:

Ugly Demo


// First create a menu object to hold the various menus
var menusObject = {
// headerMenu: new dijit.Menu(),
rowMenu: new dijit.Menu()//,
// cellMenu: new dijit.Menu(),
// selectedRegionMenu: new dijit.Menu()
};

//Add a menu item

menusObject.rowMenu.addChild(new dijit.MenuItem({
label: "Show me data",
onClick: function(e){
console.log(this.selectedRow)
}
}));

menusObject.rowMenu.startup();

//Create the grid

var grid = new dojox.grid.EnhancedGrid({
store : store,
structure : layout,
rowsPerPage: 10,
escapeHTMLInData: false,
plugins: {
menus: menusObject
}
}, 'some are to place');

// Activate message sending from data grid row to menu items

dojo.connect(grid, 'onRowContextMenu', function(e)
{
// Set the "selectedItem" property of all of the menu items of a menu. This lets you reference the row data!!

var menuChildren = menusObject.rowMenu.getChildren();
for(var i = 0; i

Ugly Demo

How To Display A Loading Message While dijit.form.ComboBox / dijit.form.FilteringSelect Fetch Option List

When you create a dijit.form.ComboBox or dijit.form.FilteringSelect you are more than likely using dojo.data.ItemFileReadStore objects to populate the lists. If you look at Dojo’s documentation on dijit.form.ComboBox (http://dojotoolkit.org/reference-guide/dijit/form/ComboBox.html#dijit-form-combobox) you’ll see they have all sorts of examples on how to do this… The only problem is that ALL of the examples use url’s that point to static json lists to read from…

So what if you want to use a dynamic list? Say populated from a database? Well you’re in luck, you just have to point the url to a server script that will first select the data from the database, convert the data into Dojo’s data api format. (If you use PHP and the Zend Framework, look at Zend_Dojo_Data). This works great… except when you have a huge list.

The longer the list, the longer the download time from the server. When a user clicks on the combo box, Dojo doesn’t tell the user that the list is being downloaded, it just sit’s there, and the finally, eventually, once the content has been downloaded (could be 5 secs!) the list is displayed.

So how do we fix this? Simple. Just override the dijit.form.ComboBox’s loadDropDown function so that some kind of loading message (in this case an image) is display while fetching the content from the server and then removing the loading message once the content has been fetched. (loadDropDown is called once the user clicks on the down arrow of the combo box.)


dojo.provide('stricks.customComboBox');
dojo.require('dijit.form.ComboBox');

dojo.declare('stricks.customComboBox', [dijit.form.ComboBox],
{
constructor: function(params, srcNodeRef)
{
this.store = new dojo.data.ItemFileReadStore({
url: "/blog-demos/get-custom-list"
});
this.searchAttr = "name";

},

loadDropDown: function()
{
// Create and display loading image.
var c = dojo.create('img', {src:"/blog-demos/images/ajax-loader.gif"}, this.domNode, 'after');
// Keep scope.
var parent = dojo.hitch(this, "inherited", arguments);
// Fetch data from URL.
var request = this.store.fetch({
onComplete: function(){
parent(); // Call dijit.form.ComboBox loadDropDown
dojo.destroy(c); // Destory load image.
}
});
}
});

I’ll work on adding a demo for this a little later.

Aborting In Progress dojo.xhr

If you only want to allow one dojo.xhr to run at any given time and cancel the currently running ones, you’ll need to track all of the requests. From there you can test to see if any are currently running and abort them if so.

Click here for an example of how this works

1) This code creates a tracker object that you can pass all of your requests to.

var tracker = function(){
return {
_xhr:null,
track:function(xhr)
{
var obj = this;
// If a request was made and still working, kill it.
if(this._xhr){
this._xhr.cancel();
}
// Create new request and return handle on it.
return this._xhr = xhr;
}
}}();

Once you have your tracker, you’ll just need to pass all future dojo.xhr’s to the tracker by first, getting a handle on the request

var xhr = dojo.xhrGet({
url:"/blog-demos/slow-load.php",
load:function(response){
// Do something
}
});

and then sending the handle to the tracker.

tracker.track(xhr);
tracker.track(xhr1); // Other requests
tracker.track(xhr2);
tracker.track(xhr3);

What the above code would do is cancel xhr, xhr1 and xhr2 (if they didn’t finish) before running xhr3.

Click here for an example of how this works

How To Get Parent Item Of Selected Item in Dijit.Tree

This will give you the immediate parent item of the selected item in a tree list:


dijit.byId('tree_node_id').selectedNode.getParent().item.label[0];

For example: You want to get the singer of the select album.
– Music (root node)
— CD
—- Meat Loaf
—– Bat out of hell 1
—– Bat out of hell 2
— Tape
— Meat Loaf
—- Bat out of hell 1


var tree = dijit.byId('tree_id');
if(tree.selectedItem.type == 'album'){
var artist = tree.selectedNode.getParent().item.label[0]; // Meat Loaf
}

Let’s say you just wanted to know the type of media a selected item is on.


var tree = dijit.byId('tree_id');
var node = tree.selectedNode;
while(node.type != 'media'){
node = tree.selectedNode.getParent();
}
return node.type; // CD

Here’s the JSON code for the Tree list.


{"identifier":"id","items":[{"id":0,"label":"CD","children":[{"label":"Meat Loaf","id":0,"children":[{"label":"Bat out of hell 1","id":0,"type":"album"},{"label":"Bat out of hell 2", "id":1,"type":"album"}],"type":"artist"}],"type":"media"},{"id":1,"label":"Tape","children":[{"label":"Meat Loaf","id":0,"children":[{"label":"Bat out of hell 1", "id":0,"type":"album"}],"type":"artist"}],"type":"media"}]}

Extending dijit.Dialog with a Templated Widget Containing Widgets

Click here for a demo.

http://heather.koyuk.net/refractions/?p=246 was a good post on how to extend a dijit.Dialog with a templated widget, but it won’t work if you have widgets within your template. The reason being is that when the template file is parsed by dojo, the widgets within the template will be registered with the dijit.registry. That’s fine and all, but once you add the template to your dijit.dialog


this.popup.set("content", this.domNode); // I used set instead of attr since it is deprecated now

you’ll get errors about the widgets already being registered. That’s because dijit.dialog extends dijit.layout.ContentPane which is automatically parses any node that you send it using set(“content”);

To fix that, all you have to do is add parseOnLoad:false to the dijit.Dialog constructor:


this.popup = new dijit.Dialog({parseOnLoad:false});

That takes care of the biggest issue, but there are two more things that you will need to add/change to make this work:

1) Extend the dijit.Dialog.hide() method to first call your widgets hide function. This is needed so that when a user clicks on the dialog’s X close, your widgets hide function will run.


var hide = this.popup.hide;
var obj = this;
this.popup.hide = function(){
obj.hide();
hide();
}

2) In your widgets hide function, destroyRecursive instead of just destroy.


hide: function()
{
this.popup.destroyRecursive();
}

Click here for a demo

Here is the complete js code for the custom widget:


dojo.provide('stricks.customPopup');
dojo.require('dijit.Dialog');
dojo.declare('stricks.customPopup', [dijit._Widget, dijit._Templated],
{
popup : null,
widgetsInTemplate: true,

constructor: function(params, srcNodeRef)
{
this.templateString = dojo.cache('stricks', 'customPopup.html');
},

postMixInProperties: function()
{
this.popup = new dijit.Dialog({title:'Strick Popup', parseOnLoad:false});
var hide = this.popup.hide;
var obj = this;
this.popup.hide = function(){
obj.hide();
hide();
}
this.inherited(arguments);
},

postCreate: function()
{
this.cancel_button.onClick=dojo.hitch(this.popup, 'hide');
this.inherited(arguments);
},

show: function()
{
this.popup.set("content", this.domNode);
this.popup.show();
},

hide: function()
{
this.popup.destroyRecursive();
}
});

HTML Template:

Drag and Drop Hierarchies: dnd.Source within dnd.Source

It seems like that best way to do drag and drops that have a hierarchy with Dojo is to use dijit.Tree. I didn’t want to do this for two reasons:

1) Way to late in the game to change the code (though, I may add it for a separate module.)
2) I need to be able to have my hierarchies displayed with any type of HTML that I want. (div’s within div’s within ul’s etc.) dijit.Tree didn’t seem to allow for that.

Anyhow, adding a bunch of sources and source children was simple enough:

Group 1

  • Child 1
  • Child 2

Group 2

  • Child 1
  • Child 2

Group 3

  • Child 1
  • Child 2

The child elements could be dragged and dropped between each other with no problem, but when I went to move a group (say Group 3 above Group 2) everything would break once I hovered over either of the custom.dnd.Source.Group elements.

To fix this, I basically just hid any Source elements that the element that I was moving was not a child of. This was accomplished by:

1) Overrode the checkAcceptance function in dojo.dnd.Source.js. Just added the following for the if(!flag) return false;:


if(!flag){
/**
* Main Source
* - Group 1
* -- Child 1
* -- Child 2
* - Group 2
*/
var node = dojo.byId(this.node);
// If this is a source that can be moved inside the parent, allow. (If moving Group 1 and found Group 2 as a source).
if(dojo.query('#'+dojo.attr(node, 'id') + ' #'+dojo.attr(source.node, 'id')).length)
return true;
// A sub source, hide. This "removes" the overSource events. (If moving Group 1 and found Child 1 or Child 2
dojo.addClass(node, 'hiddenSource'); // visibility:hidden;
return false;
}

2) Extended onDndDrop to remove the added class to re display the hidden elements.


onDndDrop: function(source, nodes, copy, target)
{
this.inherited(arguments);
dojo.forEach(dojo.query('.hiddenSource'),
function(el){dojo.removeClass(el, 'hiddenSource');}
);
}

3) Extend onDndCancel to do the above


onDndCanel: function()
{
this.inherited(arguments);
dojo.forEach(dojo.query('.hiddenSource'),
function(el){dojo.removeClass(el, 'hiddenSource');}
);
}

4) Update the css file

.hiddenSource{ visibility:hidden !important;}

This isn’t the best solution since it hides the elements that can’t be used with the current element that you are positioning, but it worked for me.

All together

dojo.declare('custom.dnd.Source', [dojo.dnd.Source],
checkAcceptance: function(source, nodes){
// summary:
// checks if the target can accept nodes from this source
// source: Object
// the source which provides items
// nodes: Array
// the list of transferred items
if(this == source){
return !this.copyOnly || this.selfAccept;
}
for(var i = 0; i < nodes.length; ++i){ var type = source.getItem(nodes[i].id).type; // type instanceof Array var flag = false; for(var j = 0; j < type.length; ++j){ if(type[j] in this.accept){ flag = true; break; } } if(!flag){ var node = dojo.byId(this.node); // If this is a source that can be moved inside the parent, allow. (If moving Group 1 and found Group 2 as a source). if(dojo.query('#'+dojo.attr(node, 'id') + ' #'+dojo.attr(source.node, 'id')).length) return true; // A sub source, hide. This "removes" the overSource events. (If moving Group 1 and found Child 1 or Child 2 dojo.addClass(node, 'hiddenSource'); return false; // Boolean } } return true; // Boolean }, onDndCanel: function() { this.inherited(arguments); dojo.forEach(dojo.query('.hiddenSource'), function(el){dojo.removeClass(el, 'hiddenSource');} ); }, onDndDrop: function(source, nodes, copy, target) { this.inherited(arguments); dojo.forEach(dojo.query('.hiddenSource'), function(el){dojo.removeClass(el, 'hiddenSource');} ); } });