I am currently developing a cascading tree menu. When a tree item is expanded it creates new <div>
's below it that may also be expanded. I can get the first set of <div>
's to bind and thus expand, but I can't seem to get the newly created div's to bind automatically using the "this._on" method. I'm looking for functionality similar to live(), delegate() and on().
I have posted my code in jsFiddle at http://jsfiddle.net/vEAhq/ .
I set the event binding under the _setupEvents method on line 60.
In review, the elements that are created in the refresh method, called during the _create method bind okay. It's the elements created in the _expand method that do not get bound like I would expect.
Thanks in advance for your time.
$.widget( "custom.categoryMenu", {
options: {
links: null
},
_create: function() {
this.refresh();
this._on('.categoryNavigationExpandIcon', {
click: function(event) {
var $container = $(event.target).parent();
if( ($container).data('expanded') === false ) {
this._expand($container);
} else {
this._contract($container);
}
}
});
},
refresh: function() {
var $elm = this.element;
var $newElem;
var $itemTree = this.options.links;
var $itemTreeRoot = sortObj( $itemTree['1'], 'function', true );
this._setupEvents();
$.each( $itemTreeRoot, function( key, value ) {
var ID = $itemTreeRoot[key];
var label = key;
$newElem = $( '<div style="padding:8px 0;"></div>' )
.data( 'expanded', false )
.data( 'id', ID )
.data( 'level', 1 )
.addClass( 'categoryNavigationLevel1' );
$( '<div class="categoryNavigationExpandIcon"></div>' ).appendTo($newElem);
$( '<a href="index.cfm?action=products&cat=' + ID + '" rel="?cat=' + ID + '" class="categoryMenuLabel">' + label + '</a>' ).appendTo($newElem);
// Add triangle icons if there are children
if ($itemTree[ID]) {
$($newElem)
.find('.categoryNavigationExpandIcon')
.addClass('ui-icon')
.addClass('ui-icon-triangle-1-e');
}
$elm.append($newElem);
});
},
_setupEvents: function() {
var events = {
click: function( event ) {
event.preventDefault();
}
};
this._on('.categoryNavigationExpandIcon', {
click: function(event) {
var $container = $(event.target).parent();
if( ($container).data('expanded') === false ) {
this._expand($container);
} else {
this._contract($container);
}
}
});
},
_expand: function(container) {
var $elm = this.element;
var $itemTree = this.options.links;
var containerLevel = $(container).data('level');
var containerID = $(container).data('id');
var newContainerLevel = containerLevel + 1;
var $itemTreeBranch = sortObj( $itemTree[containerID], 'function', true );
$(container)
.data('expanded', true)
.find('.ui-icon-triangle-1-e')
.removeClass('ui-icon-triangle-1-e')
.addClass('ui-icon-triangle-1-se');
$.each( $itemTreeBranch, function( key, value ) {
var ID = $itemTreeBranch[key];
label = key;
$newElem = $( '<div style="padding:8px 0;"></div>' )
.data('expanded', false)
.data( 'id', ID )
.data( 'level', newContainerLevel )
.addClass( 'categoryNavigationLevel' + newContainerLevel );
$( '<div class="categoryNavigationExpandIcon"></div>' ).appendTo($newElem);
$( '<a href="index.cfm?action=products&cat=' + ID + '" rel="?cat=' + ID + '" class="categoryMenuLabel">' + label + '</a>' ).appendTo($newElem);
// Add triangle icons if there are children
if ($itemTree[ID]) {
$($newElem)
.find('.categoryNavigationExpandIcon')
.addClass('ui-icon')
.addClass('ui-icon-triangle-1-e');
}
$(container).after($newElem);
});
},
_contract: function(container) {
var containerLevel = $(container).data('level');
$(container)
.data( 'expanded', false )
.find('.ui-icon-triangle-1-se')
.removeClass('ui-icon-triangle-1-se')
.addClass('ui-icon-triangle-1-e');
$(container)
.nextUntil( '.categoryNavigationLevel' + containerLevel )
.remove();
},
destroy: function() {
$.widget.prototype.destroy.call(this);
}
});
/**
* Name: Sort Javascript Object
* Credit: Arne Martin Aurlien
* URL: http://am.aurlien.net/post/1221493460/sorting-javascript-objects
*/
sortObj = function(obj, type, caseSensitive) {
var temp_array = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (!caseSensitive) {
key = (key['toLowerCase'] ? key.toLowerCase() : key);
}
temp_array.push(key);
}
}
if (typeof type === 'function') {
temp_array.sort(type);
} else if (type === 'value') {
temp_array.sort(function(a,b) {
var x = obj[a];
var y = obj[b];
if (!caseSensitive) {
x = (x['toLowerCase'] ? x.toLowerCase() : x);
y = (y['toLowerCase'] ? y.toLowerCase() : y);
}
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
} else {
temp_array.sort();
}
var temp_obj = {};
for (var i=0; i<temp_array.length; i++) {
temp_obj[temp_array[i]] = obj[temp_array[i]];
}
return temp_obj;
};
It's better, when you have to append some new created content, to bind the events on the document, for a given selector, or for custom events.
For instance, if you are trying to bind a click on a given class '.your-class' :
$('.your-class').on('click', function (e) {
// ...
})
You would prefer to replace it by that :
$(document).on('click', '.your-class', function (e) {
// ...
})
It is exactly the same for jQuery UI:
You just would prefer to replace:
// Here you just bind a click on the existing elements of your class
this._on('.categoryNavigationExpandIcon', {
click: function(event) {
var $container = $(event.target).parent();
if( ($container).data('expanded') === false ) {
this._expand($container);
} else {
this._contract($container);
}
}
});
By:
// Bind a custom event on the document in order to delegate this event event to the new created classes
this._on(this.document, {
'click.categoryNavigationExpandIcon': function(event) {
var $container = $(event.target).parent();
if( ($container).data('expanded') === false ) {
this._expand($container);
} else {
this._contract($container);
}
}
});
You can find the updated the JSFiddle here: http://jsfiddle.net/vEAhq/7/
By the way, you can skip the closing tag when you create a element with jQuery !
$('<div>'); // instead of $('<div></div>');