使用 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...
我没有处理子菜单,但是我 运行 遇到的问题是...
- 向
<li>
个元素添加 aria-role
的方法。
- 避免在两次渲染菜单时发生碰撞 - 两种表示 - 折叠 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), ' ', $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
属性
以下代码将在第一级 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
.
在 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...
我没有处理子菜单,但是我 运行 遇到的问题是...
- 向
<li>
个元素添加aria-role
的方法。 - 避免在两次渲染菜单时发生碰撞 - 两种表示 - 折叠 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), ' ', $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
属性