/** * @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); /** * Experimental! 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); } }