// ------------------------------------------------------------------------------------
// AJAX TREE PACKAGE
//
// TODO: Try to use JcmsJsonRequest
// ------------------------------------------------------------------------------------
Ajax.Tree = {
enableDragNDrop: true,
dragdropArray: $A(new Array()),
// ------------------------------------------------------------------------------------
// AJAX Function
// ------------------------------------------------------------------------------------
_downloadChildrenHooks: $H(),
/**
* Allow other javascript code to register a custom callback
* when a new tree branch is retrieved in ajax.
*/
registerDownloadChildrenHook: function(ajaxSuffix, customCallback) {
var callbacks = Ajax.Tree._downloadChildrenHooks.get(ajaxSuffix);
if (!callbacks) {
callbacks = $A();
Ajax.Tree._downloadChildrenHooks.set(ajaxSuffix,callbacks);
}
callbacks.push(customCallback);
},
invokeDownloadChildrenHook: function(ajaxSuffix, ul) {
var callbacks = Ajax.Tree._downloadChildrenHooks.get(ajaxSuffix);
if (!callbacks) { return; }
callbacks.each(function(item) {
item(ul);
});
},
/**
* Get HTML of children
* - for the ID in the img.classname (ID_j_42)
* - for the full tree with given checkedArray
*
* Update UL innerHTML with this new content.
* Remove Image click function to toggleVisibility instead of calling a new Ajax call.
*
* @param img the clicked image
* @param ajaxSuffix String that represents the main UL id
* @param ul the root branch to fill
* @param checkedArray a Array of checked category ids
*/
downloadChildren: function(img, ajaxSuffix, ul, checkedArray, openedArray, customCallback) {
JcmsLogger.debug('TreeCat','downloadChildren():', ajaxSuffix, img, checkedArray, openedArray);
// Init Json Request
var jsonRequest = new JcmsJsonRequest(img);
// Init RPC with jsonRequest
var funcRPC = function(){
if (img){
Ajax.Tree._getRpcTree(ajaxSuffix).getChildren(jsonRequest.asyncJsonCallBack.bind(jsonRequest), $(img).getJcmsId());
} else {
Ajax.Tree._getRpcTree(ajaxSuffix).getChildren(jsonRequest.asyncJsonCallBack.bind(jsonRequest),checkedArray,openedArray);
}
}
// Init Callback with jsonRequest
var funcCallback = function(returnValue){
if (!returnValue){
ul.parentNode.removeChild(ul);
return;
}
// Clean
Ajax.Tree._disposeUL(ul);
Util.cleanDOMElements(ul,true);
ul.innerHTML = returnValue;
// Update DragDrop
if($(ajaxSuffix).hasClassName('dragdrop') && Ajax.Tree.enableDragNDrop){
setTimeout(function(){ Ajax.Tree._initDragDrop(ul); },10);
}
// Call custom callback
if (customCallback){
customCallback();
}
Ajax.Tree.invokeDownloadChildrenHook(ajaxSuffix, ul);
}
// Init custom exception handle
var funcException = function(){
ul.parentNode.removeChild(ul);
Ajax.Tree.toggleOpenClose(img);
}
// Run JSON Request
jsonRequest.rpc = funcRPC;
jsonRequest.callback = funcCallback;
jsonRequest.exception = funcException;
jsonRequest.asyncJsonCall();
},
/**
* Rename the given category using ajax call
* Refresh the full category tree in ajax
*
* @param ajaxSuffix String that represents the main UL id
* @param catId The JCMS Category id
* @param value The new category name in current language
*/
rename: function(ajaxSuffix, catId, value){
JcmsLogger.debug('TreeCat','rename():', ajaxSuffix, catId, value);
// Init Json Request
var jsonRequest = new JcmsJsonRequest();
// Init RPC with jsonRequest callback
var funcRPC = function(){
Ajax.Tree._getRpcTree(ajaxSuffix).rename(function(msg){ Ajax.Tree._handleRPCResponse(jsonRequest, msg, ajaxSuffix); }, catId, value);
};
// Run JSON Request
jsonRequest.rpc = funcRPC;
jsonRequest.callback = Ajax.Tree._callbackRefresh;
jsonRequest.asyncJsonCall();
},
/**
* Add a new category using ajax call
* Refresh the full category tree in ajax
*
* @param ajaxSuffix String that represents the main UL id
* @param catId The JCMS Category id
* @param value The category name to add in current language
*/
addSubCat: function(ajaxSuffix, catId, value){
JcmsLogger.debug('TreeCat','addSubCat():', ajaxSuffix, catId, value);
// Init Json Request
var jsonRequest = new JcmsJsonRequest();
// Init RPC with jsonRequest callback
var funcRPC = function(){
Ajax.Tree._getRpcTree(ajaxSuffix).addSubCat(function(msg){ Ajax.Tree._handleRPCResponse(jsonRequest, msg, ajaxSuffix); },catId, value);
};
// Run JSON Request
jsonRequest.rpc = funcRPC;
jsonRequest.callback = function(returnValue, returnEffect){ Ajax.Tree._callbackRefresh(returnValue, returnEffect, catId); };
jsonRequest.asyncJsonCall();
},
/**
* Add a sibling category using ajax call
* Refresh the full category tree in ajax
*
* @param ajaxSuffix String that represents the main UL id
* @param catId The JCMS Category id
* @param value The category name to add in current language
*/
addSiblingCat: function(ajaxSuffix, catId, value){
JcmsLogger.debug('TreeCat','addSiblingCat():', ajaxSuffix, catId, value);
// Init Json Request
var jsonRequest = new JcmsJsonRequest();
// Init RPC with jsonRequest callback
var funcRPC = function(){
Ajax.Tree._getRpcTree(ajaxSuffix).addSiblingCat(function(msg){ Ajax.Tree._handleRPCResponse(jsonRequest, msg, ajaxSuffix); },catId, value);
};
// Run JSON Request
jsonRequest.rpc = funcRPC;
jsonRequest.callback = function(returnValue, returnEffect){ Ajax.Tree._callbackRefresh(returnValue, returnEffect); };
jsonRequest.asyncJsonCall();
},
/**
* Remove a category using ajax call
* Refresh the full category tree in ajax
*
* @param ajaxSuffix String that represents the main UL id
* @param catId The JCMS Category id
*/
remove: function(ajaxSuffix, catId){
JcmsLogger.debug('TreeCat','remove():', ajaxSuffix, catId);
// Init Json Request
var jsonRequest = new JcmsJsonRequest();
// Init RPC with jsonRequest callback
var funcRPC = function(){
Ajax.Tree._getRpcTree(ajaxSuffix).remove(function(msg){ Ajax.Tree._handleRPCResponse(jsonRequest, msg, ajaxSuffix); },catId);
};
// Run JSON Request
jsonRequest.rpc = funcRPC;
jsonRequest.callback = Ajax.Tree._callbackRefresh;
jsonRequest.asyncJsonCall();
},
/**
* Update the parent of a givent category
* Refresh the full category tree in ajax
*
* @param ajaxSuffix String that represents the main UL id
* @param catId The JCMS Category id
*/
setParent: function(ajaxSuffix, catId, parentId){
JcmsLogger.debug('TreeCat','setParent():', ajaxSuffix, catId, parentId);
// Init Json Request
var jsonRequest = new JcmsJsonRequest();
// Init RPC with jsonRequest callback
var funcRPC = function(){
Ajax.Tree._getRpcTree(ajaxSuffix).setParent(function(msg){ Ajax.Tree._handleRPCResponse(jsonRequest, msg, ajaxSuffix); },catId, parentId);
};
// Run JSON Request
jsonRequest.rpc = funcRPC;
jsonRequest.callback = function(returnValue, returnEffect){ Ajax.Tree._callbackRefresh(returnValue, returnEffect, parentId); };
jsonRequest.asyncJsonCall();
},
/**
* Refresh an Ajax TreeCat
* 1. Parse tree to record checked nodes.
* 2. Remove all children of main branch.
* 3. Retrieve a new Tree using Ajax.Tree.downloadChildren
* 4. Update main branch
*
* If UL.TreeCat have className 'follow' then the tree will open (not select)
* the given branch ids then follow the Ahref link.
*
* @param ajaxSuffix String that represents the main UL id
* @param ids an array of ids to add to checked/open elements
* or a single id (that will follow url after refresh)
*/
refresh: function(ajaxSuffix, ids, checked){
JcmsLogger.debug('TreeCat','refresh():',ajaxSuffix,ids);
Ajax.setWaitState(true);
var ul = $(ajaxSuffix);
// Search checked categories
var openedArray = new Array();
var checkedArray = new Array();
$A(ul.getElementsByTagName('INPUT')).each(function(elm, idx){
if (elm.checked){
checkedArray.push(elm.value);
}
});
// Search opened nodes
$A(ul.getElementsBySelector('LI.open')).each(function(elm, idx){
var node = elm.down();
if (node){
var nodeCatId = $(node).getJcmsId();
openedArray.push(nodeCatId);
JcmsLogger.debug('TreeCat', 'Opened:', nodeCatId);
}
});
// Add given additional checked ids
if (ids){
openedArray = openedArray.concat(ids);
if (checked && !ul.hasClassName('follow')){
checkedArray = checkedArray.concat(ids);
}
}
// Removing all children
Ajax.Tree._disposeUL(ul);
Util.cleanDOMElements(ul,true);
// Append message
ul.innerHTML = "
Loading...";
// Prepare custom callback follow
var customCallback = null;
if (ul.hasClassName('follow') && ids && !(ids instanceof Array)){
customCallback = function(){ Ajax.Tree._followRefreshCallback(ajaxSuffix, ids); }
}
// Dowload children
Ajax.Tree.downloadChildren(null, ajaxSuffix, ul, checkedArray, openedArray, customCallback);
},
/**
* Called by refresh() for Treecat with class 'follow'
* @param ajaxSuffix String that represents the main UL id
* @param ids the id to work with to find URL to follow
*/
_followRefreshCallback: function(ajaxSuffix, ids){
var elms = $(ajaxSuffix).select('IMG.ID_'+ids);
if (!elms || !elms[0]){
JcmsLogger.warn('TreeCat','Id not found',ids);
return;
}
var img = elms[0];
var ahref = img.next('A');
if (ahref){
document.location = ahref.href;
}
},
// ------------------------------------------------------------------------------------
// Utility Function
// ------------------------------------------------------------------------------------
/**
* Makes AJAX call to import all the children
* for the given img LI and given AJAX suffix.
*
* @param img the clicked image
*/
_importChildren: function(img) {
var li = $(img.parentNode);
if (li.hasClassName('imported')) {
return;
}
Ajax.setWaitState(true,img);
// Set wait icon
var ul = document.createElement('UL');
ul.innerHTML = "
Loading...";
li.appendChild(ul);
// Mark this branch as imported
li.addClassName('imported');
// Asynchronous call
var ajaxSuffix = $(img).fastUp('UL', 'TreeCat').id;
Ajax.Tree.downloadChildren(img, ajaxSuffix, ul);
},
/**
* Toggles className 'open' and 'close' on parent LI of image.
*
* @param img the clicked image
*/
toggleOpenClose: function(img) {
var li = $(img.parentNode);
li.toggleClassName('close');
li.toggleClassName('open');
Ajax.Tree._importChildren(img);
},
/**
* Returns the ajaxSuffix of the parent UL of
* the given element.
*
* @param the children element
*/
getAjaxSuffix: function(elm){
if (!elm){
return;
}
var elm = $(elm);
// Hook for an element handle click as a proxy
if (elm.id && elm.id.indexOf('proxy') >= 0){
return elm.id.substring(6);
}
var ul = elm.fastUp('UL', 'TreeCat');
if (!ul){
return;
}
JcmsLogger.debug('TreeCat','getAjaxSuffix():', ul.id);
return ul.id;
},
/**
* Returns the category id for the given element
*
* @param elm the element to work with
*/
getCategoryId: function(elm){
if (!elm){
return;
}
if (elm.tagName == 'LI'){
return Ajax.Tree.getCategoryId(elm.down(0));
}
else if (elm.tagName == 'IMG'){
return $(elm).getJcmsId();
}
else{
return Ajax.Tree.getCategoryId(elm.up('UL.TreeCat LI'));
}
},
// ------------------------------------------------------------------------------------
// Internal Function
// ------------------------------------------------------------------------------------
/**
* Returns the correct JSON-RPC AjaxTree from given ajaxSuffix
*
* @param ajaxSuffix the ajaxSuffix or null
*/
_getRpcTree: function(ajaxSuffix){
if (!ajaxSuffix){
return JcmsJsContext.getJsonRPC().AjaxTree;
}
else {
return JcmsJsContext.getJsonRPC()['AjaxTree'+ajaxSuffix];
}
},
/**
* Convenient callback function called to refresh
* treecat after json-rpc call
*
* @param ajaxSuffix the ajaxsuffix used to refresh tree
* @param returnEffect the Effect (not used)
*/
_callbackRefresh: function(ajaxSuffix, returnEffect, openId){
JcmsLogger.debug('TreeCat','Callback Refresh',ajaxSuffix,returnEffect,openId);
if (ajaxSuffix){
if (openId){
var ids = new Array();
ids.push(openId);
Ajax.Tree.refresh(ajaxSuffix, ids);
}
else {
Ajax.Tree.refresh(ajaxSuffix);
}
}
},
/**
* Convenient function used to handle RPC reponse.
* If RPC returns a message then call alert and finish JsonRequest job
* else finish JsonRequest Job with the given return value.
*
* @param msg the error message
* @param returnValue the value to return if there is no errors
*/
_handleRPCResponse: function(jsonRequest, msg, returnValue){
if (msg){
alert(msg);
jsonRequest.asyncJsonCallBack();
return;
}
jsonRequest.asyncJsonCallBack(returnValue);
},
/**
* Init Sortable on TreeCat
*/
_initTreeCat: function(){
JcmsLogger.info('TreeCat','Init TreeCat');
if (!Ajax.Tree.enableDragNDrop){
return;
}
$$('UL.TreeCat').each(function(elm, idx){
if (elm.hasClassName('dragdrop')){
Ajax.Tree._initDragDrop(elm);
}
});
},
/**
* Clean the cached dragdropArray
*/
dispose: function(){
// Because underlaying function only dispose DrangNDrop
if (!Ajax.Tree.enableDragNDrop){
return;
}
$$('UL.TreeCat').each(function(elm, idx){
Ajax.Tree._disposeUL(elm);
});
Ajax.Tree.dragdropArray.clear();
},
/**
* Clean LI items
*
* @param li the li to clean
*/
_disposeLI: function(li){
// Remove previous draggable
if (li.draggable){
li.draggable.destroy();
li.draggable = null;
}
// Remove previous droppable
Droppables.remove(li);
},
/**
* Remove all drag/drop object bind to LIs under given UL
*
* @param ul the ul element to work with
*/
_disposeUL: function(ul){
var ul = $(ul);
// Remove LI Drag/Drop
$A(ul.getElementsByTagName('LI')).each(function(li,idx){
Ajax.Tree._disposeLI(li);
Ajax.Tree.dragdropArray.splice(idx,1);
});
// Remove onclick events
$A(ul.getElementsByTagName('A')).each(function(ahref,idx){
ahref.onclick = null;
});
},
/**
* Inits all drag/drop object bind to LIs under given UL
*
* @param ul the ul element to work with
*/
_initDragDrop: function(ul){
var ul = $(ul);
$A(ul.getElementsByTagName('LI')).each(function(li,idx){
var li = $(li);
var anchor = li.down('IMG.visual');
Event.observe(anchor,'mousedown',Ajax.Tree._lazyDrag);
// Init Droppable
Droppables.remove(li);
Droppables.add(li,{greedy:false, onHover:Ajax.Tree._onHover, onDrop:Ajax.Tree._onDrop});
// Cache LI's for clean purpose
Ajax.Tree.dragdropArray.push(li);
});
},
/**
* Used by _initDragDrop() to lazy initialise draggable onmousedown
* @param event the mousedown event
*/
_lazyDrag: function(event){
var anchor = Event.element(event);
JcmsLogger.debug('TreeCat','_lazyDrag',anchor);
// Destroy previous Draggable
var li = anchor.fastUp('LI');
if (li.draggable){
li.draggable.destroy();
}
// Init new Draggable
li.draggable = new Draggable(li,{revert: true, handle:'visual'});
// Remove observer
Event.stopObserving(anchor,'mousedown',Ajax.Tree._lazyDrag);
// Kick start dnd
li.draggable.initDrag(event);
Draggables.updateDrag(event);
},
/**
* Convenient internal function to stop Drag/Drop events
*
* @param event the event
*/
_stopEvent: function(event){
Event.stop(event);
},
/**
* Internal function called by _initDragDrop()
*
* @param dragElm the object that encapsulate the real element
*/
_onChange: function(dragElm){
// Skip quickly
if (!dragElm.element.dragObserver){
JcmsLogger.debug('TreeCat','Start dragObserver');
Event.observe(dragElm.element, 'click', Ajax.Tree._stopEvent);
dragElm.element.dragObserver = true;
}
},
/**
* Internal function called by _initDragDrop()
*
* @param dragElm Dragged element
* @param dropElm Dropped element
* @param overlap Percetage of overlaping
*/
_onHover: function(dragElm, dropElm, overlap){
// Skip quickly
if (dropElm.className.indexOf('droppable') >= 0){
return;
}
// Remove previous droppable if item changes
if (dragElm.oldDropElm && dragElm.oldDropElm != dropElm){
dragElm.oldDropElm.removeClassName('droppable');
}
// Do not highlight it's own parent
// if (dragElm.up('LI') == dropElm){
// return;
// }
// Add class name
dropElm.addClassName('droppable');
dragElm.oldDropElm = dropElm;
},
/**
* Internal function called by _initDragDrop()
*
* @param dragElm Dragged element
* @param dropElm Dropped element
* @param event the Drag/Drop event
*/
_onDrop: function(dragElm, dropElm, event){
// Stop event handler
// JcmsLogger.debug('TreeCat','Stop dragObserver');
// Event.stopObserving(dragElm, 'click', Ajax.Tree._stopEvent);
// Remove previous droppable if item changes
if (dragElm.oldDropElm){
dragElm.oldDropElm.removeClassName('droppable');
}
// Do not work on parent element
if (dragElm.fastUp('LI') === dropElm){
return;
}
// Ask question
var dnd = top.confirm(I18N.glp('msg.confirm.dragdrop'));
// Make AJAX Call
if (dnd){
dragElm.hide(); // Hide element
Ajax.Tree.setParent(Ajax.Tree.getAjaxSuffix(dragElm),
Ajax.Tree.getCategoryId(dragElm),
Ajax.Tree.getCategoryId(dropElm));
}
}
}
// ------------------------------------------------------------------------------------
// EVENT OBSERVER
// ------------------------------------------------------------------------------------
Event.observe(window, 'load' , function() { Ajax.Tree._initTreeCat();} );
if (navigator.appVersion.match(/\bMSIE\b/)){
Event.observe(window, 'unload', function() { Ajax.Tree.dispose(); }, false);
}