Česky
Kamil Dudka

QuickMenu (Javascript, PHP)

File detail

Name:DownloadQuickMenu.class.js [Download]
Location: QuickMenu > QuickMenu-with-example
Size:6.2 KB
Last modification:2022-09-09 13:06

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);
  }
}