QuickMenu (Javascript, PHP)
File detail
Source code
/**
* @fileoverview Client-part of QuickMenu component
* @author Kamil Dudka
* @version SVN Snapshot
*/
/**
* @constructor
* @param idMenu XML ID of QuickMenu root - UL element. See server-part documentation of QuickMenu.
* @param idInput XML ID of QuicMenu input - INPUT element. See server-part documentation of QuickMenu.
* @class Client-part of QuickMenu component
*/
function QuickMenu(idMenu, idInput) {
/** @private */ this.menuRoot = document.getElementById(idMenu);
/** @private */ this.input = document.getElementById(idInput);
/**
* <b>Experimental!</b> QuickMenu visible depth. Default is 1.
*/
this.visibleDepth = 1;
/**
* Minimal length of input to invoke search action. Default is 1.
*/
this.minimalInputLength = 1;
/**
* XML ID of active menu item. See server-part documentation of QuickMenu.
*/
this.activeItemId = 'qmActive';
/**
* CSS classes mapping. See server-part documentation of QuickMenu.
*/
this.classTable = new Object;
this.classTable.qmDefault = 'qmDefault';
this.classTable.qmMatch = 'qmMatch';
this.classTable.qmHidden = 'qmHidden';
/**
* Image source of expanded list item. See server-part documentation of QuickMenu.
*/
this.imgExpanded = '';
/**
* Image source of collapsed list item. See server-part documentation of QuickMenu.
*/
this.imgCollapsed = '';
/**
* Image source of list item without childern. See server-part documentation of QuickMenu.
*/
this.imgNoChildern = '';
/**
* @private
*/
QuickMenu.prototype.MODE_INIT = 0;
/**
* @private
*/
QuickMenu.prototype.MODE_SEARCH = 1;
/**
* @private
*/
QuickMenu.prototype.MODE_EXPAND = 2;
/**
* @private
*/
QuickMenu.prototype.MODE_SHOWACTIVE = 3;
/**
* @private
*/
QuickMenu.prototype.setCollapseImg = function(node, hasChildern, bCollapsed) {
var img = new ChildElementsEnumerator(node, 'img').next();
img.qmCollapsed = bCollapsed;
if (!hasChildern)
img.src = this.imgNoChildern;
else if (bCollapsed)
img.src = this.imgCollapsed;
else
img.src = this.imgExpanded;
}
/**
* @private
*/
QuickMenu.prototype.setItemClass = function(item, className){
var className = this.classTable[className];
item.className = className;
var a = (new ChildElementsEnumerator(item, 'a')).next();
a.className = className;
}
/**
* @private
*/
QuickMenu.prototype.crawler = function(currentNode, mode, depth) {
var textNode = (new ChildElementsEnumerator(currentNode, 'a')).next().firstChild;
var bCurrentVisible = false;
var bImpliedVisible = false;
switch (mode) {
case this.MODE_SEARCH:
// Search in current menu item
bCurrentVisible = textNode.nodeType==3 && this.searchEngine.isMatch(textNode.data);
}
var nextList = (new ChildElementsEnumerator(currentNode, 'ul')).next();
var hasChildern = nextList!=null;
if (hasChildern) {
var current;
var enumerator = new ChildElementsEnumerator(nextList, 'li');
while (null!= (current = enumerator.next())) {
// Invoke recursion
var bNested = this.crawler(current, mode, depth-1);
bImpliedVisible |= bNested;
}
}
var bActive = (currentNode.id == this.activeItemId);
switch (mode) {
case this.MODE_INIT:
//QuickMenu initialization for current node
currentNode.qmStateClass = this.classTable['qmDefault'];
var img = new ChildElementsEnumerator(currentNode, 'img').next();
img.onclick = function() {
with (arguments.callee.qm) {
var bCollapsed = !this.qmCollapsed;
var nextList = (new ChildElementsEnumerator(this.parentNode, 'ul')).next();
if (nextList==null)
return;
var current;
var enumerator = new ChildElementsEnumerator(nextList, 'li');
while (null!= (current = enumerator.next())) {
if (bCollapsed) {
current.qmStateClass = current.className;
setItemClass(current, 'qmHidden');
} else {
setItemClass(current, current.qmStateClass);
}
}
setCollapseImg (this.parentNode, true, !this.qmCollapsed);
}
}
img.onclick.qm = this;
break;
case this.MODE_SEARCH:
case this.MODE_SHOWACTIVE:
if (bCurrentVisible)
this.setItemClass(currentNode, 'qmMatch');
else if (bImpliedVisible || depth>0)
this.setItemClass(currentNode, 'qmDefault');
else
this.setItemClass(currentNode, 'qmHidden');
var bVisible = bImpliedVisible ||
(mode == this.MODE_SEARCH && bCurrentVisible) ||
(mode == this.MODE_SHOWACTIVE && bActive);
if (bVisible)
this.crawler(
currentNode, this.MODE_EXPAND,
(bCurrentVisible)?this.visibleDepth:1);
this.setCollapseImg(currentNode, hasChildern, !bVisible && depth<=1);
return bVisible;
case this.MODE_EXPAND:
if (depth>=0 && currentNode.className == this.classTable['qmHidden']) {
this.setItemClass(currentNode, 'qmDefault');
this.setCollapseImg(currentNode, hasChildern, depth==0);
}
}
}
/**
* @private
*/
QuickMenu.prototype.enumMenu = function(mode, depth) {
var rootEnum = new ChildElementsEnumerator(this.menuRoot, 'li');
var currentItem;
while (currentItem = rootEnum.next())
this.crawler(currentItem, mode, depth);
}
/**
* Start QuickMenu functionality. QuickMenu properties should not be modified after call of this method.
*/
QuickMenu.prototype.init = function() {
var cbInput = function(searchStr) {
var bEnoughInput =
searchStr!=null &&
searchStr.length >= this.minimalInputLength;
var mode = this.MODE_SEARCH;
if (searchStr=='*')
this.enumMenu(this.MODE_SHOWACTIVE, Infinity);
else if (bEnoughInput) {
this.searchEngine = new SearchEngineChain(searchStr);
this.enumMenu(this.MODE_SEARCH, 1);
} else {
this.enumMenu(this.MODE_SHOWACTIVE, this.visibleDepth);
}
}
new InputWatch(this.input, cbInput, this);
this.enumMenu(this.MODE_INIT, 0);
cbInput.call(this);
}
}