// create closure
if(typeof jQuery != 'undefined') (function($){
// plugin definition
$.fn.lazyTree = function(_options){
var lazyTree = $.fn.lazyTree;
// store a reference to the root object
var treeRoot = $(this);
// extend jQuery options for this object
var treeOptions = $.extend({}, lazyTree.defaultOptions, _options);
// stores an array of nodes that needs to be opened (if using persistence)
var nodesToOpen = [];
// stores a reference to the currently selected (leaf) node
var selectedNode = treeRoot;
// declare public methods - all these methods may be overridden
var publicMethods = {
/*
init : creates the root elements of the tree and starts all necessary operations
*/
init : function(){
// create the root element for the tree
lazyTree.createRoot();
// load the first branch of the tree
lazyTree.loadBranches( treeRoot.find('li.root') );
// establish event listeners required for operation
lazyTree.setEventListeners();
// if persistence is enabled, try to load previously stored elements
if(lazyTree.isPersistenceEnabled()){
lazyTree.loadTree();
}
},
/*
branchHasChildren : determines if a branch currently has children loaded
*/
branchHasChildren : function(treeNode){
// check to see if a branch has child lists
return (treeNode.children('ul:not(.' + treeOptions.errorClass + ')').length > 0);
},
/*
createRoot : creates the root elements in the DOM which all branches will expand upon
*/
createRoot : function() {
// create root ul with two li items, one for loading, one as the first branch
var root = $('
- ' + treeOptions.loadingMessage + '
' +
'
');
// set the root data on the first branch in the tree
root.find('li.root').data('nodeData', treeOptions.rootData);
// add root element to the DOM
treeRoot.html(root);
},
/*
createTreeNode : creates a list item for all tree nodes and attaches appropriate data
*/
createTreeNode : function(data, parent){
// build list item
var newNode = $('' +
'' + data[treeOptions.titleProperty] + '');
// attach data to element
newNode.data('nodeData', data);
return newNode;
},
/*
debug : for development only, writes to the FireBug console
*/
debug : function(value){
// make sure the enableDebug flag is true and the console is available
if(treeOptions.enableDebug && window.console && window.console.log){
window.console.log(value);
}
},
/*
errorHandler : handles all errors returned from the AJAX request
*/
errorHandler : function(XMLHttpRequest, textStatus, errorThrown, treeNode){
// log the error
lazyTree.debug(textStatus + ' : ' + errorThrown);
// remove the loading class from the branch that made the request
treeNode.removeClass(lazyTree.getTreeOption('loadingClass'));
var nodeChildren = treeNode.children('ul.error');
if(nodeChildren.length == 0){
// create new branch to display warning to user
var newBranch = $('');
// add the branch to the tree
treeNode.append(newBranch);
// open the requesting branch to show all the warning
lazyTree.toggleBranch(newBranch);
}else{
nodeChildren.fadeOut().fadeIn();
}
},
/*
findInArray : finds an element in an array that has a property value that matches the value provided
*/
findInArray : function(array, propertyName, value){
// loop through the array
for(var i=0; i -1){
nodesToOpen.splice(nodeIndex,1);
}
});
});
}
});
// now load all the functions that have been stored in the queue
treeRoot.dequeue('openBranchQueue');
lazyTree.debug('number of items stored: ' + nodesToOpen.length);
// determine if the new branch is the parent of the stored selected node
if(selectedLeaf.parent == parentNodeData[treeOptions.idProperty]){
// get the selected node from the DOM
var selectedNodeFound = lazyTree.findNodeInBranch(newBranch, selectedLeaf.node);
lazyTree.setSelectedNode(selectedNodeFound);
}
}
// check to see if there are any 'open nodes' still left to open
if(nodesToOpen.length < 1){
lazyTree.debug('all nodes loaded: ' + parentNodeData[treeOptions.idProperty]);
treeRoot.unbind('branchLoad');
}
},
/*
preprocessDataFilter : filters data before is returned to the 'success' method
*/
preprocessDataFilter : function(data, type, treeNode){
// simply return the raw data since it's already in JavaScript Object Notation
// this method would typically be overriden to accomodate XML or other data types returned
return data;
},
/*
prepDataForRequest : returns whatever data needs to be sent to the server in the AJAX request
*/
prepDataForRequest : function(data, treeNode){
// simply send the node ID, if it's null then send a blank ID
var nodeID = (typeof data[treeOptions.idProperty] != 'undefined') ? data[treeOptions.idProperty] : '';
return {id: nodeID};
},
/*
saveTree : stores the current 'state' of the tree in a cookie
*/
saveTree : function(){
// make sure persistence is enabled
if(lazyTree.isPersistenceEnabled()){
// open nodes will store the object string for all open nodes
var openNodes = '';
// find all open folders by checking for the 'expanded' class
treeRoot.find('li.expanded').each( function(){
// get the node's parent and grab it's id
var parentNode = $(this).parents('li:first');
if(parentNode.length == 0) return;
var parentID = parentNode.data('nodeData')[treeOptions.idProperty];
// get the node's id
var nodeID = $(this).data('nodeData')[treeOptions.idProperty];
// append this node to the object string
openNodes += '{node: "' + nodeID + '", parent: "' + parentID + '"},';
});
// remove the trailing comma from the object string
openNodes = openNodes.substr(0, openNodes.length-1);
// get the id and the parent id of the currently selected node
var selectedLeaf = "{id: '', parent: ''}";
if(typeof selectedNode.data('nodeData') == 'object'){
var selectedNodeID = selectedNode.data('nodeData')[treeOptions.idProperty];
var selectedNodeParentID = $(selectedNode).parents('li:first').data('nodeData')[treeOptions.idProperty];
selectedLeaf = "{node: '" + selectedNodeID + "', parent: '" + selectedNodeParentID + "'}";
}
// save the cookie to the user's machine
$.cookie('lazyTree-'+treeOptions.treeName,
'{treeName: "' + treeOptions.treeName + '", openNodes: [' + openNodes + '], selectedNode: ' + selectedLeaf + '}',
{expires: 45, path: '/'});
}
},
/*
setEventListeners : creates event listeners to respond to tree events
*/
setEventListeners : function(){
var treeRoot = lazyTree.getTreeRoot();
// listen for the click event on all nodes
$(treeRoot).delegate("a", "click", lazyTree.nodeClickHandler);
// add keystroke handlers, if applicable
if(treeOptions.enableKeystrokes){
// add class on focus
$(treeRoot).delegate("a", "focus", function(){
$(this).addClass(treeOptions.focusedClass);
});
// remove class on focus
$(treeRoot).delegate("a", "blur", function(){
$(this).removeClass(treeOptions.focusedClass);
});
// listen for keystrokes that are used to navigate the tree
$(treeRoot).delegate("a", "keydown", lazyTree.navigateByKeystroke);
}
// if persistence is enabled, detect the 'unload' event so we can store the 'state' of the tree
if(lazyTree.isPersistenceEnabled()){
$(window).bind('unload', lazyTree.saveTree);
}
},
/*
setSelectedNode : set the selected node in the tree and adds the selected style to it
*/
setSelectedNode : function(node){
// remove selection class from current item
if(typeof selectedNode == 'object')
selectedNode.removeClass(treeOptions.selectedClass);
// add the selection class to the new item
if(typeof node == 'object')
node.addClass(treeOptions.selectedClass);
// store a reference to the new selection
selectedNode = node;
},
/*
successHandler : handles the data returned from a successful AJAX request
*/
successHandler : function(data, textStatus, treeNode){
//lazyTree.debug('return data type: ' + typeof data);
// if this was the first branch loaded, remove the 'loading...' message
treeRoot.find('li.treeLoader').remove();
// remove any previous error messages
treeNode.children('ul.' + treeOptions.errorClass).remove();
//create a new list and append all the new children to the requesting branch
var newBranch = $('');
$.each(data, function(index, obj){
// add one li for each data item returned from the server
newBranch.append(lazyTree.createTreeNode(obj, treeNode));
});
treeNode.append(newBranch);
// bind click handler and other events to the new nodes
//lazyTree.bindEvents(treeNode);
// remove the loading class from the branch that made the request
treeNode.removeClass(treeOptions.loadingClass);
// open the requesting branch to show all it's new children
lazyTree.toggleBranch(newBranch);
// dispatch the 'branchLoad' event
treeRoot.trigger('branchLoad', treeNode);
},
/*
toggleBranch : opens or closed a branch
*/
toggleBranch : function(branch){
// add or remove the 'expanded' class
branch.parent('li').toggleClass(treeOptions.expandedClass);
// hide or show the branch's children
branch.slideToggle(600);
}
};// end public methods
// extend jQuery methods to add the Tree's public methods
$.each(publicMethods, function(method){
lazyTree[method] = this;
});
return lazyTree;
};// end lazyTree function
// establish default tree options
$.fn.lazyTree.defaultOptions = {
treeName : 'lazyTree', // the name of the tree, will be stored in the the persistence cookie (if applicable)
persistTree : false, // a boolean flag, whether to store the 'state' of the tree between visists
titleProperty : 'title', // the property in the data that contains the 'title' of each tree item
typeProperty : 'type', // the property in the data that differentiates branches from leaves
idProperty : 'id', // the property in the data that uniquely identifies each element in the tree
loadingMessage : ' ', // the loading text that displays while the tree is initializing
rootData : {id: ''}, // the data to send to the server for the initial AJAX call to the the root elements
ajaxURL : 'proxy.cfm', // the URL to use for the AJAX requests - this can become dynamic by overriding the getAjaxURL method
dataType : 'json', // the return type of the AJAX requests
branchType : 'branch', // the value of the 'typeProperty' for branch elements
leafType : 'leaf', // the value of the 'typeProperty' for leaf elements
enableKeystrokes : true, // a boolean flag specifying whether to allow the user to navigate the tree with the keypad
enableDebug : false, // a boolean flag specifying whether to allow logs in the Firebug console
expandedClass : 'expanded', // the CSS class used to style branch nodes
loadingClass : 'loading', // the CSS class used to style elements during the AJAX load process
selectedClass : 'selected', // the CSS class used to style the currently selected element in the tree
focusedClass : 'focused', // the CSS class used for elements in the tree that currently have focus
errorClass : 'error' // the CSS class used when an error occurrs loading a node
};
// end closure
})(jQuery);