В новом (относительно) Bootstrap 3 отсутствует возможность по умолчанию для выпадающего меню в навигациии задавать уровень вложенности более чем 2. Т.е. невозможно построить навигацию с бесконечным выпадающим меню используя возможности Bootstrap 3 по умолчанию. Для добавления такой нужной возможности существует следующее решение.
На stackoverflow предложено отличное рабочее решение. Оно будет браться за основу, почти без изменений. Это добавление некоторого css и js кода, который расширяет или переопределяет стандартное поведение навигации bootstrap.
Я обычно добавляю этот код в файлы bootstrap-fix.css и bootstrap-fix.js и подключаю сразу после бутстраповских файлов.
Вот необходимый код, который надо добавить в css.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
/* Created on : 04.02.2014, 18:54:37 Author : seyfer */ .dropdown-menu { margin: 0 0 0; } .navbar-brand { color: #68b604 !important; } .dropdown-submenu{ position:relative; } .dropdown-submenu>.dropdown-menu{ top:0;left:100%; margin-top:-6px;margin-left:-1px; -webkit-border-radius:0 6px 6px 6px; -moz-border-radius:0 6px 6px 6px; border-radius:0 6px 6px 6px; } .dropdown-submenu:hover>.dropdown-menu{ display:block; } .dropdown-submenu>a:after{ display:block; content:" "; float:right; width:0; height:0; border-color:transparent; border-style:solid; border-width:5px 0 5px 5px; border-left-color:#cccccc; margin-top:5px; margin-right:-10px; } .dropdown-submenu:hover>a:after{ border-left-color:#ffffff; } .dropdown-submenu.pull-left{ float:none; } .dropdown-submenu.pull-left>.dropdown-menu{ left:-100%; margin-left:10px; -webkit-border-radius:6px 0 6px 6px; -moz-border-radius:6px 0 6px 6px; border-radius:6px 0 6px 6px; } |
А вот код для js файла.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<script> $(document).ready(function () { $('.nav li.dropdown').hover(function () { $(this).addClass('open'); }, function () { $(this).removeClass('open'); }); }); $('ul.dropdown-menu [data-toggle=dropdown]').on('mouseover', function (event) { // Avoid following the href location when clicking event.preventDefault(); // Avoid having the menu to close when clicking event.stopPropagation(); // If a menu is already open we close it //$('ul.dropdown-menu [data-toggle=dropdown]').parent().removeClass('open'); // opening the one you clicked on $(this).parent().addClass('open'); var menu = $(this).parent().find("ul"); var menupos = menu.offset(); var newpos; if ((menupos.left + menu.width()) + 30 > $(window).width()) { newpos = -menu.width(); } else { newpos = $(this).parent().width(); } menu.css({left: newpos}); }); $('a.dropdown-toggle').on('click', function (event) { window.location = $(this).prop("href"); }); </script> |
Верстка для данного фикса предполагается следующая.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<div class="collapse navbar-collapse navbar-ex1-collapse"> <ul class="nav navbar-nav"> <li class="menu-item dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown">Drop Down<b class="caret"></b></a> <ul class="dropdown-menu"> <li class="menu-item dropdown dropdown-submenu"> <a href="#" class="dropdown-toggle" data-toggle="dropdown">Level 1</a> <ul class="dropdown-menu"> <li class="menu-item "> <a href="#">Link 1</a> </li> <li class="menu-item dropdown dropdown-submenu"> <a href="#" class="dropdown-toggle" data-toggle="dropdown">Level 2</a> <ul class="dropdown-menu"> <li> <a href="#">Link 3</a> </li> </ul> </li> </ul> </li> </ul> </li> </ul> </div> |
Как должно быть понятно из кода – на каждый дополнительный уровень вложенности создается тег
<li> с классом
dropdown-submenu. Для автоматической генерации такой верстки из какого-то набора (массива) ссылок меню придется воспользоваться рекурсией. Т.к. на каждой итерации при построении верстки надо будет проверть – есть ли у текущего родительского элемента дети? От этого зависит будет ли открыт новый тег для подменю.
Далее я приведу пример реализации на основе Zend Framework 2 хелпера partial(). И второй пример будет с использованием шаблонизатора Smarty и его встроенной возможности создавать ф-ии.
Пример реализации на Zend Framework 2
В Zend обычно существует базовый шаблон layout. В нем для рендеринга навигации вызывается хелпер navigation() c указанием partial шаблона меню. Кстати через navigation() можно построить breadcrumbs() – хлебные крошки (код шаблона стандартный из мануала).
Предварительно подключены css и js файлы в head. Сам же bootstrap-fix.js подключен в шаблоне меню.
Пример верстки layout.phtml.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<body> <div class="container"> <div class="row"> <?php $partial = array('partial/top_menu.phtml', 'default') ?> <?php echo $this->navigation("index_navigation") ->menu() ->setMinDepth(0) ->setMaxDepth(0) ->setUlClass('nav navbar-nav') ->setPartial($partial); ?> </div> <div class="row"> <?php echo $this->navigation('index_navigation') ->breadcrumbs()->setMinDepth(0) ->setPartial(array('partial/breadcrumb.phtml', 'Application')) ->setRenderInvisible(true); ?> </div> <div class="row content"> <?php echo $this->content; ?> <hr> </div> <div class="row"> <footer> <p>© <?php echo date('Y') ?> <?php echo $this->translate('All rights reserved.') ?></p> </footer> </div> <?php echo $this->inlineScript() ?> </div> |
Как можно понять из кода – контроллеры при выполнении заполняют переменную content, а нас интересует сейчас файл top_menu.phtml.
Вот его содержимое:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <div class="container"> <div class="navbar-inner navbar-collapse"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Gate V2</a> </div> <ul class="nav navbar-nav" id="menu"> <?php echo $this->partial('partial/top_menu_part.phtml', [ 'level' => 0, 'container' => $this->container ]); ?> </ul> </div> </div> </div> <!-- фикс выпадающего меню --> <?php $this->inlineScript() ->appendFile($this->basePath() . '/js/bootstrap/bootstrap-fix.js'); ?> |
Далее построение содержимого листа вынесено в еще один партиал, и в данном случае он играет роль ф-ии для организации рекурсии и вызывает сам себя у себя внутри. Так же мы считаем уровень вложенности меню. Вот внутренний код партиал-функции:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
<?php $pages = $this->container; foreach ($pages as $page): ?> <?php // when using partials we need to manually check for ACL conditions ?> <?php if (!$page->isVisible() || !$this->navigation()->accept($page)) : continue; endif; $hasChildren = $page->hasPages(TRUE) ?> <?php //только если имеет видимых детей if ($hasChildren): ?> <?php if ($level == 0) : ?> <li class="dropdown <?php if ($page->isActive()) : ?>active<?php endif; ?>"> <a class="dropdown-toggle" data-toggle="dropdown" href="<?php echo $page->getHref() ?>"> <?php echo $this->translate($page->getLabel()) ?> <b class="caret"></b> </a> <ul class="dropdown-menu" id="level<?php echo $level; ?>"> <?php echo $this->partial('partial/top_menu_part.phtml', [ 'level' => $level + 1, 'container' => $page->getPages() ]); ?> </ul> </li> <?php else : ?> <li class="dropdown dropdown-submenu <?php if ($page->isActive()) : ?>active<?php endif; ?>" > <a class="dropdown-toggle" data-toggle="dropdown" href="<?php echo $page->getHref() ?>"> <?php echo $this->translate($page->getLabel()) ?> </a> <ul class="dropdown-menu" id="level<?php echo $level; ?>"> <?php echo $this->partial('partial/top_menu_part.phtml', [ 'level' => $level + 1, 'container' => $page->getPages() ]); ?> </ul> </li> <?php endif; ?> <?php else: ?> <li <?php if ($page->isActive()) : ?> class="active" <?php endif; ?>> <a href="<?php echo $page->getHref() ?>"> <?php echo $this->translate($page->getLabel()) ?> </a> </li> <?php endif; ?> <?php endforeach; ?> |
Это самая главная реализация. Тут мы обходим массив страниц, смотрим есть ли у них дети и смотрим уровень вложенности и на основе этих данных решаем как рендерить текущую страницу. Если дети есть, то партиал вызывавает сам себя с новым уровнем вложенности и списком детей текущего элемента в качестве параметров.
Пример реализации на Smarty
Для того, чтобы сделать такое же меню используя Smarty надо воспользоваться встроенной возможностью создания функций прямо внутри шаблона. Выглядит это несколько проще, по сравнению с Zend. Код файла bootstrap.tpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
{function menu_vert level =0} {foreach $data as $entry} {if isset($entry.children) && !empty($entry.children)} {if $level == 0} <li class="menu-item dropdown"> <a href="{$entry.link}" class="dropdown-toggle" data-toggle="dropdown"> {$entry.title} <b class="caret"></b> </a> <ul class="dropdown-menu" id="level{$level}"> {menu_vert data=$entry.children level=level+1} </ul> </li> {else} <li class="menu-item dropdown dropdown-submenu"> <a class="dropdown-toggle" data-toggle="dropdown" href="{$entry.link}">{$entry.title}</a> <ul class="dropdown-menu" id="level{$level}"> {menu_vert data=$entry.children level=level+1} </ul> </li> {/if} {else} <li class="menu-item" {*class="active"*}> <a href="{$entry.link}">{$entry.title}</a> </li> {/if} {/foreach} {/function} <!-- Fixed navbar --> <div class="navbar navbar-default {*navbar-fixed-top*}" role="navigation"> <div class="container"> {if $header} <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">{$header}</a> </div> {/if} <div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> {menu_vert data=$structure} </ul> </div><!--/.nav-collapse --> </div> </div> <script src="{$boostrapFixJsPath}"></script> |
Тут отличие в том, что структура передается как чистый массив, где элементы тоже массивы, а не объекты. Так же видно, что путь в js файлу в переменной. Этот шаблон может быть просто отрендерен средствами Smarty и выведен в нужном месте.
Я использую этот шаблон в моем модуле для Kohana – seyfer/kohana-dynamic-structure