Česky
Kamil Dudka

QuickMenu (Javascript, PHP)

File detail

Name:DownloadQuickMenu.class.php [Download]
Location: QuickMenu > QuickMenu-with-example
Size:10.6 KB
Last modification:2007-09-17 17:47

Source code

<?php
/**
 * @file QuickMenu.class.php
 * Definition of QuickMenu class.
 * @author Kamil Dudka <xdudka00@gmail.com>
 * @ingroup QuickMenu
 */
 
/**
 * @defgroup QuickMenu
 * At http://dudka.cz/QuickMenu may be available abstract, documentation and tutorial for this component.
 * @n Used @b CSS @b IDs: qmWrapper, QuickMenu, qmInputBox, qmInput, qmFacade, qmClearSearch, qmActive, qmActiveLink
 * @n Used @b CSS @b classes: qmDefault, qmMatch, qmHidden
 * @n Used @b images: tree_minus.png, tree_plus.png, tree_item.png, icon_drop.png
 */
 
/**
 * Server part of QuickMenu component.
 * @ingroup QuickMenu
 */
class QuickMenu {
  private $imgExpanded_;
  private $imgCollapsed_;
  private $imgNoChildern_;
  private $imgClearSearch_;
  private $imgExpandAll_;
  public function __construct() {
    $config = Config::instance();
    $imgRoot = $config->webRoot().$config->imgDir();
    $this->imgExpanded_ = $imgRoot.'/tree_minus.png';
    $this->imgCollapsed_ = $imgRoot.'/tree_plus.png';
    $this->imgNoChildern_ = $imgRoot.'/tree_item.png';
    $this->imgClearSearch_ = $imgRoot.'/icon_drop.png';
    $this->imgExpandAll_ = $imgRoot.'/fast_forward.png';
  }
 
  private $visibleDepth_ = 1;
  /**
   * Menu tree visible depth. Default is 1.
   * @return Menu tree visible depth.
   */
  public function visibleDepth() {
    return $this->visibleDepth_;
  }
  /**
   * Set menu tree visible depth. <b>Experimental!</b><br/>Default is 1.
   * @param visibleDepth Menu tree visible depth.
   */
  public function setVisibleDepth($visibleDepth) {
    $this->visibleDepth_ = $visibleDepth;
  }
 
  private $minimalInputLength_ = 1;
  /**
   * Minimal length of input to invoke search action. Default is 1.
   * @return Minimal length of input to invoke search action.
   */
  public function minimalInputLength() {
    return $this->minimalInputLength_;
  }
  /**
   * Minimal length of input to invoke search action. Default is 1.
   * @param minimalInputLength Minimal length of input to invoke search action.
   */
  public function setMinimalInputLength($minimalInputLength) {
    $this->minimalInputLength_ = $minimalInputLength;
  }
 
  private $activeItemHREF_;
  /**
   * Active menu item HREF attribute.
   * @return HREF of active menu item.
   */
  public function activeItemHREF() {
    return $this->activeItemHREF_;
  }
  /**
   * Set active menu item using HREF attribute. This attribute should be equally to HREF given by addItem() method.
   * @param activeItemHREF HREF of menu item to active.
   */
  public function setActiveItemHREF($activeItemHREF) {
    $this->activeItemHREF_ = $activeItemHREF;
  }
 
  /**
   * Generate head section of component and send direct to output.
   * @remarks
   * - Generate link to QuickMenu.class.js
   * - Generate initialization inline script
   * - Use JSOnloadList singleton to run scripts on page load (at client's side).
   */
  public function genHead() {
    $config = Config::instance();
    $qmScript = $config->webRoot().$config->scriptDir().'/QuickMenu.class.js';
    echo '  <script type="text/javascript" src="'.$qmScript.'"></script>'."\n";
    ?>  <script type="text/javascript">
qmFacade = null;
qmInput = null;
qmClear = null;
qmExpandAll = null;
function qmOnload() {
  (new Image).src='<?php echo $this->imgExpanded_; ?>';
  (new Image).src='<?php echo $this->imgCollapsed_; ?>';
  (new Image).src='<?php echo $this->imgNoChildern_; ?>';
  (new Image).src='<?php echo $this->imgClearSearch_; ?>';
  qm = new QuickMenu('QuickMenu', 'qmInput');
  qm.visibleDepth = <?php echo $this->visibleDepth(); ?>;
  qm.minimalInputLength = <?php echo $this->minimalInputLength(); ?>;
  qm.imgExpanded = '<?php echo $this->imgExpanded_; ?>';
  qm.imgCollapsed = '<?php echo $this->imgCollapsed_; ?>';
  qm.imgNoChildern = '<?php echo $this->imgNoChildern_; ?>';
  qm.init();
  qmFacade = document.getElementById('qmFacade');
  qmInput = document.getElementById('qmInput');
  qmClear = document.getElementById('qmClearInput');
  qmExpandAll = document.getElementById('qmExpandAll');
  qmFacade.onfocus = function() {
    qmInput.style.display = '';
    qmClear.style.display = '';
    this.style.display = 'none';
    qmExpandAll.style.display = 'none';
    if (-1==navigator.appName.search(/konqueror/i))
      qmInput.focus();
    else
      // Special version for Konqueror
      setTimeout("qmInput.focus()", 100);
  }
  qmInput.onblur = function() {
    if (this.value.length >= qm.minimalInputLength)
      return;
    this.style.display = 'none';
    qmClear.style.display = 'none';
    this.value = '';
    qmFacade.style.display = '';
    qmExpandAll.style.display = '';
  }
  qmClear.onclick = function() {
    qmInput.value = '';
    qmInput.blur();
    qmInput.onblur();
  }
  qmExpandAll.onclick = function() {
    qmInput.value = '*';
    qmFacade.onfocus();
    if (-1==navigator.appName.search(/konqueror/i))
      qmInput.select();
    else
      // Special version for Konqueror
      setTimeout("qmInput.select()", 200);
  }
  qmInput.onblur.call(qmInput);
}
  </script>
<?php
    JSOnloadList::instance()->addFunction('qmOnload');
  }
 
  private $currentId = 1;                       ///< Menu item enumeration id (id=0 ... menu root)
  private $mapIdToText = Array();               ///< Menu item text associative array
  private $mapIdToHREF = Array();               ///< Menu item HREF associative array
  private $mapIdToMenuList = Array(0=>Array()); ///< Menu tree linkage
 
  /**
   * Add menu item somewhere to menu
   * @param szText Menu item text.
   * @param szHREF Menu item HREF. <b>Item HREF is relative to webRoot!!!</b>
   * @param idParent Parent menu item ID. ID must be an number previously returned by addItem() method or zero for base level menu item.
   * @return Return ID of just added menu item.
   */
  public function addItem($szText, $szHREF, $idParent=0) {
    $id = $this->currentId++;
    $this->mapIdToText[$id] = $szText;
    $this->mapIdToHREF[$id] = $szHREF;
    $this->mapIdToMenuList[$id] = Array();
    $this->mapIdToMenuList[$idParent] []= $id;
    return $id;
  }
 
  /**
   * Extract submenu direct to output
   * @param id menu item ID of parent menu item
   * @param xmlID (voluntary) xml ID of UL tag to set
   */
  private function genSubMenu($id, $xmlId=null) {
    $menuList = $this->mapIdToMenuList[$id];
    if (0==count($menuList))
      return;
 
    // Initiate list
    $indentor = Indentor::singleton();
    if (isset($xmlId))
      $indentor->puts("<ul id=\"$xmlId\">");
    else
      $indentor->puts('<ul>');
    $indentor->enter();
 
    // Generate items
    foreach ($menuList as $current)
      $this->genMenuItem($current);
 
    // Terminate list
    $indentor->leave();
    $indentor->puts('</ul>');
  }
 
  /**
   * Generate menu item and its subitems recursively direct to output.
   * @param id menu ID of item to generate
   */
  private function genMenuItem($id) {
    $indentor = Indentor::singleton();
    $indentor->indentDirect();
    $szText = $this->mapIdToText[$id];
    $szHREF = $this->mapIdToHREF[$id];
    $menuList = $this->mapIdToMenuList[$id];
 
    // Is current item active menu item?
    $isActive = $szHREF == $this->activeItemHREF_;
    $szItemId = ($isActive)?' id="qmActive"':'';
 
    // Initiate list item
    echo "<li$szItemId class=\"qmDefault\">";
 
    // Has current item childern
    $hasChilds = 0!= count($menuList);
    $szImgSrc = ($hasChilds)?
      $this->imgExpanded_:
      $this->imgNoChildern_;
    echo "<img src=\"$szImgSrc\" title=\"$szText\" alt=\"-\" />";
 
    // Generate item link
    $szFullHREF = Config::instance()->webRoot().'/'.$szHREF;
    $szLinkId = ($isActive)?' id="qmActiveLink"':'';
    echo "<a$szLinkId class=\"qmDefault\" href=\"$szFullHREF\">$szText</a>";
 
    if ($hasChilds) {
      // Generate subitems recursively
      echo "\n";
      $indentor->enter();
      $this->genSubMenu($id);
      $indentor->leave();
      $indentor->indentDirect();
    }
 
    // Terminate list item
    echo "</li>\n";
  }
 
  /**
   * Generate body section of component and send direct to output.
   * @remarks
   * - DIV \#qmWrapper
   *    - DIV \#qmInputBox
   *        - INPUT \#qmInput
   *        - INPUT \#qmFacade
   *        - IMG \#qmClearSearch
   *        - IMG \#qmExpandAll
   *    - UL \#QuickMenu (with dynamic content inside)
   *        - LI (optional item)
   *            - IMG
   *            - A
   *            - UL (optional subitem list)
   *                - LI ...
   *        - ...
   */
  public function genMenu() {
    $indentor = Indentor::singleton();
    $indentor->puts('<div id="qmWrapper">');
    $indentor->enter();
      $indentor->puts('<div id="qmInputBox">');
      $indentor->enter();
        $indentor->puts('<input type="text" id="qmInput" style="display:none;"/>');
        $indentor->puts('<input type="text" id="qmFacade" value="QuickFind" style="display:none;"/>');
        $indentor->puts('<img src="'.$this->imgClearSearch_.'" alt="Clear search" id="qmClearInput" style="display:none;"/>');
        $indentor->puts('<img src="'.$this->imgExpandAll_.'" alt="Expand all" id="qmExpandAll" style="display:none;"/>');
      $indentor->leave();
      $indentor->puts('</div>');
      $this->genSubMenu(0, 'QuickMenu');
    $indentor->leave();
    $indentor->puts('</div>');
  }
 
  /**
   * Add item and its subitems recursively from SimpleXML document tree.
   * @param item SimpleXML node to add as item
   * @param idParent Parent item ID or zero for base level item.
   */
  private function addXmlItem($item, $idParent=0) {
    // Localized item text
    $lang = LangSelect::singleton()->getLang();
    $textArray = $item->xpath('child::qmText[@lang=\''.$lang.'\']');
    if (0==count($textArray))
      // Implicit item text (not localized)
      $textArray = $item->xpath('child::qmText[not(@lang)]');
    if (0==count($textArray))
      // Any item text (emergency case)
      $textArray = $item->xpath('child::qmText');
 
    // Add current item
    $hrefArray = $item->xpath('@href');
    $id = $this->addItem($textArray[0], $hrefArray[0], $idParent);
 
    // Scan for childern
    $childArray = $item->xpath('child::qmItem');
    foreach ($childArray as $subItem)
      // Direct recursion
      $this->addXmlItem($subItem, $id);
  }
 
  /**
   * Add menu items from XML template.
   * DTD available at http://dudka.cz/dtd/QuickMenu.dtd
   * @param qmTemplate Path to XML document containing menu template.
   * @param idParent Parent item ID or zero for base level item.
   */
  public function loadXmlTemplate($qmTemplate, $idParent=0) {
    $xml = simplexml_load_file($qmTemplate);
    $catArray = $xml->xpath('/QuickMenu/child::qmItem');
    foreach ($catArray as $current)
      $this->addXmlItem($current, $idParent);
  }
}
?>