使用 Zend 框架和 bootstrap 在二级 UL 上设置 CSS class

Set CSS class on second level UL with Zend framework and bootstrap

以下代码将在第一级 UL

上设置 class 导航
$mainNav = public_nav_main();
$mainNav->setUlClass('nav')->setUlId('main-menu-left');

但是我正在使用 bootstrap,所以希望第二层 ul 具有 class 'dropdown-menu'

我似乎找不到对此进行排序的参考。

Zend 被用作我使用的软件 Omeka 的基础结构。不幸的是,Omeka 没有办法在本地执行此操作,因此我不得不深入研究底层的 Zend FW,尽管我不想对其进行太多修改,因为它可能会发生变化。

您可能只想基于 Zend_View_Helper_Navigation_HelperAbstract.

编写一个全新的 View Helper

在 GitHub 上寻找基于该摘要的 bootstrap 兼容助手时,我确实遇到了这个:https://github.com/michaelmoussa/zf1-navigation-view-helper-bootstrap/blob/master/library/ZFBootstrap/View/Helper/Navigation/Menu.php 采用了一种有趣的方法,post 处理生成的标记来自开箱即用的助手。

我最近采取了一种稍微不同的方法,只是破解了 Zend_View_Helper_Navigation_Menu。这是一个总结这些变化的统一差异:http://pastebin.com/mrJG8QCt 最好扩展 class...

我没有处理子菜单,但是我 运行 遇到的问题是...

  1. <li> 个元素添加 aria-role 的方法。
  2. 避免在两次渲染菜单时发生碰撞 - 两种表示 - 折叠 bootstrap 样式和用于较大视口的传统样式。 -也许 ZF 已经提供了解决这个问题的方法?有的话也没冲着我跳出来。

此代码显示了您需要调整的方法:

class MyMenu extends Zend_View_Helper_Navigation_Menu
{

/**
 * Want a way to set aria role on menu li elements because its 2015 yo
 *
 * @var string
 */
protected $_liRole = '';

/**
 * Workaround so I can render the damn thing twice on the same page and not collide IDs on the <a>'s
 * Issue arose when adopting bootstrap and rendering both full page nav and collapsed nav bar
 *
 * @var string
 */
protected $_idAlias = '';

public function setLiRole($liRole)
{
    if (is_string($liRole)) {
        $this->_liRole = $liRole;
    }
    return $this;
}
public function getLiRole()
{
    return $this->_liRole;
}

public function setIdAlias($alias)
{
    $this->_idAlias = $alias;
    return $this;
}
public function getIdAlias()
{
    return $this->_idAlias;
}

public function renderMenu(Zend_Navigation_Container $container = null, array $options = array())   
{
    $this->setLiRole($options['liRole']);
    $this->setIdAlias($options['idAlias']);

    return parent::renderMenu($container, $options);
}

/**
 * Returns an HTML string containing an 'a' element for the given page if
 * the page's href is not empty, and a 'span' element if it is empty
 *
 * Overrides {@link Zend_View_Helper_Navigation_Abstract::htmlify()}.
 *
 * @param  Zend_Navigation_Page $page  page to generate HTML for
 * @return string                      HTML string for the given page
 */
public function htmlify(Zend_Navigation_Page $page)
{
    // get label and title for translating
    $label = $page->getLabel();
    $title = $page->getTitle();

    // translate label and title?
    if ($this->getUseTranslator() && $t = $this->getTranslator()) {
        if (is_string($label) && !empty($label)) {
            $label = $t->translate($label);
        }
        if (is_string($title) && !empty($title)) {
            $title = $t->translate($title);
        }
    }

    // get attribs for element
    $attribs = array(
        'id'     => $this->getIdAlias() . $page->getId(),
        'title'  => $title,
        'class'  => $page->getClass()
    );

    // does page have a href?
    if ($href = $page->getHref()) {
        $element = 'a';
        $attribs['href'] = $href;
        $attribs['target'] = $page->getTarget();
    } else {
        $element = 'span';
    }

    return '<' . $element . $this->_htmlAttribs($attribs) . '><span class="span-nav-icon"></span><span>'
         . str_replace(chr(32), '&nbsp;', $this->view->escape($label))
         . '</span></' . $element . '>';
}

/**
 * Normalizes given render options
 *
 * @param  array $options  [optional] options to normalize
 * @return array           normalized options
 */
protected function _normalizeOptions(array $options = array())
{
    if (isset($options['indent'])) {
        $options['indent'] = $this->_getWhitespace($options['indent']);
    } else {
        $options['indent'] = $this->getIndent();
    }

    if (isset($options['ulClass']) && $options['ulClass'] !== null) {
        $options['ulClass'] = (string) $options['ulClass'];
    } else {
        $options['ulClass'] = $this->getUlClass();
    }

    if (isset($options['liRole']) && $options['liRole'] !== null) {
        $options['liRole'] = (string) $options['liRole'];
    } else {
        $options['liRole'] = $this->getLiRole();
    }

    if (isset($options['idAlias']) && $options['idAlias'] !== null) {
        $options['idAlias'] = (string) $options['idAlias'];
    } else {
        $options['idAlias'] = '';
    }

    if (array_key_exists('minDepth', $options)) {
        if (null !== $options['minDepth']) {
            $options['minDepth'] = (int) $options['minDepth'];
        }
    } else {
        $options['minDepth'] = $this->getMinDepth();
    }

    if ($options['minDepth'] < 0 || $options['minDepth'] === null) {
        $options['minDepth'] = 0;
    }

    if (array_key_exists('maxDepth', $options)) {
        if (null !== $options['maxDepth']) {
            $options['maxDepth'] = (int) $options['maxDepth'];
        }
    } else {
        $options['maxDepth'] = $this->getMaxDepth();
    }

    if (!isset($options['onlyActiveBranch'])) {
        $options['onlyActiveBranch'] = $this->getOnlyActiveBranch();
    }

    if (!isset($options['renderParents'])) {
        $options['renderParents'] = $this->getRenderParents();
    }

    return $options;
}

 /**
 * Renders the deepest active menu within [$minDepth, $maxDeth], (called
 * from {@link renderMenu()})
 *
 * @param  Zend_Navigation_Container $container  container to render
 * @param  array                     $active     active page and depth
 * @param  string                    $ulClass    CSS class for first UL
 * @param  string                    $indent     initial indentation
 * @param  int|null                  $minDepth   minimum depth
 * @param  int|null                  $maxDepth   maximum depth
 * @return string                                rendered menu
 */
protected function _renderDeepestMenu(Zend_Navigation_Container $container,
                                      $ulClass,
                                      $indent,
                                      $minDepth,
                                      $maxDepth)
{
    if (!$active = $this->findActive($container, $minDepth - 1, $maxDepth)) {
        return '';
    }

    // special case if active page is one below minDepth
    if ($active['depth'] < $minDepth) {
        if (!$active['page']->hasPages()) {
            return '';
        }
    } else if (!$active['page']->hasPages()) {
        // found pages has no children; render siblings
        $active['page'] = $active['page']->getParent();
    } else if (is_int($maxDepth) && $active['depth'] +1 > $maxDepth) {
        // children are below max depth; render siblings
        $active['page'] = $active['page']->getParent();
    }

    $ulClass = $ulClass ? ' class="' . $ulClass . '"' : '';
    $html = $indent . '<ul' . $ulClass . '>' . self::EOL;

    $liRole = (! empty($this->getLiRole())) ? "role=\"{$this->getLiRole()}\"" : "";

    foreach ($active['page'] as $subPage) {
        if (!$this->accept($subPage)) {
            continue;
        }
        $liClass = $subPage->isActive(true) ? ' class="active"' : '';
        $html .= $indent . '    <li' . $liClass . ' ' . $liRole . '>' . self::EOL;
        $html .= $indent . '        ' . $this->htmlify($subPage) . self::EOL;
        $html .= $indent . '    </li>' . self::EOL;
    }

    $html .= $indent . '</ul>';

    return $html;
}

/**
 * Renders a normal menu (called from {@link renderMenu()})
 *
 * @param  Zend_Navigation_Container $container   container to render
 * @param  string                    $ulClass     CSS class for first UL
 * @param  string                    $indent      initial indentation
 * @param  int|null                  $minDepth    minimum depth
 * @param  int|null                  $maxDepth    maximum depth
 * @param  bool                      $onlyActive  render only active branch?
 * @return string
 */
protected function _renderMenu(Zend_Navigation_Container $container,
                               $ulClass,
                               $indent,
                               $minDepth,
                               $maxDepth,
                               $onlyActive)
{
    $html = '';

    // find deepest active
    if ($found = $this->findActive($container, $minDepth, $maxDepth)) {
        $foundPage = $found['page'];
        $foundDepth = $found['depth'];
    } else {
        $foundPage = null;
    }

    // create iterator
    $iterator = new RecursiveIteratorIterator($container,
                        RecursiveIteratorIterator::SELF_FIRST);
    if (is_int($maxDepth)) {
        $iterator->setMaxDepth($maxDepth);
    }

    // iterate container
    $prevDepth = -1;
    foreach ($iterator as $page) {
        $depth = $iterator->getDepth();
        $isActive = $page->isActive(true);
        if ($depth < $minDepth || !$this->accept($page)) {
            // page is below minDepth or not accepted by acl/visibilty
            continue;
        } else if ($onlyActive && !$isActive) {
            // page is not active itself, but might be in the active branch
            $accept = false;
            if ($foundPage) {
                if ($foundPage->hasPage($page)) {
                    // accept if page is a direct child of the active page
                    $accept = true;
                } else if ($foundPage->getParent()->hasPage($page)) {
                    // page is a sibling of the active page...
                    if (!$foundPage->hasPages() ||
                        is_int($maxDepth) && $foundDepth + 1 > $maxDepth) {
                        // accept if active page has no children, or the
                        // children are too deep to be rendered
                        $accept = true;
                    }
                }
            }

            if (!$accept) {
                continue;
            }
        }

        $liRole = (! empty($this->getLiRole())) ? "role=\"{$this->getLiRole()}\"" : "";


        // make sure indentation is correct
        $depth -= $minDepth;
        $myIndent = $indent . str_repeat('        ', $depth);

        if ($depth > $prevDepth) {
            // start new ul tag
            if ($ulClass && $depth ==  0) {
                $ulClass = ' class="' . $ulClass . '"';
            } else {
                $ulClass = '';
            }
            $html .= $myIndent . '<ul' . $ulClass . '>' . self::EOL;
        } else if ($prevDepth > $depth) {
            // close li/ul tags until we're at current depth
            for ($i = $prevDepth; $i > $depth; $i--) {
                $ind = $indent . str_repeat('        ', $i);
                $html .= $ind . '    </li>' . self::EOL;
                $html .= $ind . '</ul>' . self::EOL;
            }
            // close previous li tag
            $html .= $myIndent . '    </li>' . self::EOL;
        } else {
            // close previous li tag
            $html .= $myIndent . '    </li>' . self::EOL;
        }

        // render li tag and page
        $liClass = $isActive ? ' class="active"' : '';
        $html .= $myIndent . '    <li' . $liClass . ' ' . $liRole . '>' . self::EOL
               . $myIndent . '        ' . $this->htmlify($page) . self::EOL;

        // store as previous depth for next iteration
        $prevDepth = $depth;
    }

    if ($html) {
        // done iterating container; close open ul/li tags
        for ($i = $prevDepth+1; $i > 0; $i--) {
            $myIndent = $indent . str_repeat('        ', $i-1);
            $html .= $myIndent . '    </li>' . self::EOL
                   . $myIndent . '</ul>' . self::EOL;
        }
        $html = rtrim($html, self::EOL);
    }

    return $html;
}   

}

诚然代码很多。将您现在拥有的 class 与此 http://pastebin.com/qiD2ULsz 进行比较可能会很好 - 然后查看接触点是什么,创建一个新的扩展 class。实际上只是新属性和一些 "tweaks" 到它用来呈现标记的字符串连接。

我没有在 "second level ul's" 上专门针对 class,但传入一个额外的 属性 将是微不足道的,并且遵循我所做的相同更改。

希望这对一些人有所帮助。 ZF 1.x 有点老了,这些视图助手从来没有那么好。底层 Nav 代码还不错,因此,也许只是从头开始并编写您自己的代码来呈现 Zend Nav Container。祝你好运。

这确实是一个丑陋的 hack,但您可以通过使用正则表达式处理 public_nav_main() 的输出来做到这一点。因此,在 header.php 文件中,您将替换:

echo public_nav_main();

echo preg_replace( "/(?<!\/)ul(?!.*?nav)/", 'ul class="dropdown-menu"', public_nav_main() );

这仅在您的菜单中只有 2 级时才有效,因为上面的正则表达式还会将 class="dropdown-menu" 添加到顶层 ul 以下的所有 ul 元素.

好处

  • 简单
  • 实现你想做的事
  • 不需要编写助手或修改底层框架
  • 更新底层框架时不应中断,除非更新呈现的不是 public_nav_main()
  • 中的字符串

缺点

  • 如果您的菜单超过两层,将无法使用
  • 将导致在您的第 2 级和更低级别 ul 元素中具有两个 class 属性,如果它们已经具有 class 属性