Статьи

Конструктор динамических меню для Bootstrap 3: Менеджер меню

Создание меню и панелей навигации никогда не было проще, чем с помощью Twitter Bootstrap. Мы можем легко создавать стильные панели навигации без особых усилий. Хотя этого достаточно для некоторых проектов, вы можете столкнуться с ситуациями, когда вам нужно больше контролировать ссылки и элементы в вашем меню.
Представьте, что вам нужно загрузить элементы из таблицы базы данных. Что если вам нужно ограничить элементы группой пользователей, имеющих необходимые разрешения?
Здесь статические решения не могут помочь, поэтому нам следует подумать о более динамичном подходе.

В этом уроке я собираюсь показать вам, как создать свой собственный динамический конструктор меню на PHP. Это серия из двух частей, первая из которых посвящена демонстрационному коду и классу Menu, а вторая часть — другим классам и примерам использования.

Определение цели

Прежде чем мы начнем, давайте кратко опишем, что нам нужно сделать. Лично мне больше нравится писать меньше, и я уверен, что вы тоже.

Мы должны иметь возможность создавать меню простым способом, но код должен оставаться чистым и профессиональным, написанным в современном объектно-ориентированном стиле, например:

//create the menu $menu = new Menu; //Add some items $menu->add('Home', ''); $menu->add('About', 'about'); $menu->add('Services', 'services'); $menu->add('Portfolio', 'portfolio'); $menu->add('contact', 'contact'); 

Мы должны иметь возможность добавлять подпункты семантическим способом, а не явно определять pid для каждого подпункта:

 //... $about = $menu->about('about', 'About'); $about->add('Who we are?', 'who-we-are'); $about->add('What we do?', 'what-we-do'); //... 

Мы должны предоставить способ добавления атрибутов HTML к элементам:

 //... $menu->add('About', array('url' => 'about', 'class' => 'about-li active', 'id' => 'about-li')); //... 

Разве не было бы замечательно, если бы мы могли легко добавлять или добавлять некоторый контент к якорям, например символ каретки или графический значок?

 //... $about = $menu->add('About', array('url' => 'about', 'class' => 'about-li active', 'id' => 'about-li')); $about->link->append('<b class="caret"></b>') ->prepend('<span class="glyphicon glyphicon-user"></span>'); //... 

Мы также должны предоставить способ фильтрации элементов:

 $menu = new Menu; $menu->add('Home', ''); $menu->add('About', 'about'); $menu->add('Services', 'services'); $menu->add('Portfolio', 'portfolio'); $menu->add('contact', 'contact'); $menu->filter( function($item){ if( statement ){ return true; } return false; }); 

Меню должны отображаться в виде HTML-сущностей, таких как списки, div или любой стандартный код, который нам нужен:

 //... // render the menu as an unordered list echo $menu->asUl(); // render menu as an ordered list echo $menu->asOl(); // render menu as an ordered list echo $menu->asDiv(); //... 

Думаешь, мы справимся?

Создание построителя меню

Я собираюсь разделить наш конструктор меню на три независимых компонента:

  • Менеджер меню управляет пунктами меню. Он создает, изменяет и отображает элементы.
  • Пункт представляет пункты меню как объекты. Он хранит заголовок, ссылку, атрибуты и дополнительные данные с каждым элементом.
  • Ссылка представляет ссылки в виде объектов.

Мы собираемся реализовать эти компоненты в трех отдельных определениях классов: Menu , Item и Link .

Это методы, которые мы собираемся создать с кратким объяснением того, что они делают и что они возвращают:

Меню

  • Item add(string $title, mixed $options) Добавляет элемент.
  • array roots() Возвращает элементы на корневом уровне (элементы без родительского элемента).
  • array whereParent(int $parent) Возвращает элементы с указанным родительским идентификатором.
  • Menu filter(callable $closure) Фильтрует элементы по запросу, передаваемому пользователем.
  • string render(string $type) Отображает элементы меню в формате HTML.
  • string getUrl(array $options) URL из пользовательских $options .
  • array extractAttr(array $options) Извлекает допустимые атрибуты HTML из пользовательских $options .
  • string parseAttr(array $attributes) Генерирует строку пар ключ = «значение» (разделенных пробелом).
  • int length() Подсчитывает все элементы в меню.
  • string asUl(array $attributes) Отображает меню как неупорядоченный список.
  • string asOl(array $attributes) Отображает меню как упорядоченный список.
  • string asDiv(array $attributes) Отображает меню как HTML
    ,

Вещь

  • Item add(string $title, mixed $options) Добавляет подпункт.
  • int id() Создает уникальный идентификатор для элемента.
  • int get_id() Возвращает идентификатор элемента.
  • int get_pid() Возвращает pid элемента (родительский идентификатор).
  • boolean hasChilderen() Проверьте, есть ли у элемента дочерние элементы или нет.
  • array childeren() Возвращает потомков элемента.
  • mixed attributes(string $key [, string $value]) Устанавливает или получает атрибуты элемента.
  • mixed meta(string $key [, string $value]) Устанавливает или получает метаданные элемента.

Ссылка на сайт

  • string get_url() Возвращает URL ссылки.
  • string get_text() Возвращает текст ссылки.
  • Link prepend(string $content) Вставляет контент в начале текста ссылки.
  • Link append(string $content) Добавление содержимого к тексту ссылки.
  • mixed attributes(string $key [, string $value]) Устанавливает или получает атрибуты ссылки.

Хорошо, теперь, когда у нас есть прототипы на бумаге, мы можем начать их создавать. Вы также можете использовать приведенный выше список в качестве быстрой таблицы документации, когда вы неизбежно используете наш конечный продукт в проекте.

Запустите ваш любимый текстовый редактор, создайте новый файл и назовите его menu.php .

menu.php

 class Menu { protected $menu = array(); protected $reserved = array('pid', 'url'); //... } 

Menu имеет два защищенных атрибута:

  • $menu Содержит объекты типа Item .
  • $reserved Содержит зарезервированные параметры.

$menu хранит массив зарегистрированных элементов.

Хорошо, а как насчет $reserved ? Что именно?

Когда пользователь регистрирует элемент, он / она передает массив опций через метод add . Некоторые параметры являются атрибутами HTML, а некоторые просто содержат информацию, которая нам нужна внутри класса.

 //... $menu->add('About', array('url' = 'about', 'class' => 'item')); //... 

В этом примере class является атрибутом HTML, а url является зарезервированным ключом.
Мы делаем это, чтобы отличить атрибуты HTML от других данных.

добавить (название, параметры)

Этот метод создает элемент:

 public function add($title, $options) { $url = $this->getUrl($options); $pid = ( isset($options['pid']) ) ? $options['pid'] : null; $attr = ( is_array($options) ) ? $this->extractAttr($options) : array(); $item = new Item($this, $title, $url, $attr, $pid); array_push($this->menu, $item); return $item; } 

add() принимает два параметра: первый — заголовок, а второй — параметры .

Опции могут быть простой строкой или ассоциативным массивом опций.
Если options — это просто строка, add() предполагает, что пользователь хочет определить URL без каких-либо других параметров.

Для создания Item нам необходимо предоставить следующие данные:

  • $title предмета
  • $url ссылки на элемент $url
  • $pid элемента $pid (если это субэлемент)
  • HTML-атрибуты $attr item
  • $manager ссылка на объект меню (менеджер меню)

$url получается из getUrl() который мы собираемся создать позже; Так что давайте просто получим URL независимо от того, как он работает.

Далее мы проверим $options содержит ли $options ключ pid , если он есть, мы сохраним его в $pid .

Мы также храним ссылку на объект Menu с каждым элементом. Эта ссылка позволяет нам использовать методы менеджера меню в контексте Item (мы поговорим об этом позже).

Наконец, мы создаем элемент, помещаем его в массив $menu и возвращаем Item .

Вот и мы! мы создали наш первый метод.

корнеплоды()

Выбирает элементы на корневом уровне, вызывая метод whereParent(parent) который мы вскоре создадим.

 public function roots() { return $this->whereParent(); } 

whereParent (родитель)

Возвращает предметы на заданном уровне.

 public function whereParent($parent = null) { return array_filter($this->menu, function($item) use ($parent){ if( $item->get_pid() == $parent ) { return true; } return false; }); } 

Внутри whereParent() мы используем встроенную функцию array_filter в массиве $menu .

Если вы вызываете метод без аргумента, он вернет элементы без родительского значения элементов на корневом уровне. Вот почему root () вызывает whereParent () без аргументов!

фильтр (обратный вызов)

Фильтрует элементы на основе вызываемого пользователя, предоставленного пользователем.

 public function filter($closure) { if( is_callable($closure) ) { $this->menu = array_filter($this->menu, $closure); } return $this; } 

Разве это не просто? Он просто принимает вызываемый объект в качестве параметра и запускает PHP array_filter для массива $menu .
closure зависит от того, как вы хотите ограничить свои пункты меню. Я собираюсь показать вам, как использовать это позже.

рендер (тип, pid)

Этот метод отображает элементы в формате HTML.

 public function render($type = 'ul', $pid = null) { $items = ''; $element = ( in_array($type, ['ul', 'ol']) ) ? 'li' : $type; foreach ($this->whereParent($pid) as $item) { $items .= "<{$element}{$this->parseAttr($item->attributes())}>"; $items .= $item->link(); if( $item->hasChildren() ) { $items .= "<{$type}>"; $items .= $this->render($type, $item->get_id()); $items .= "</{$type}>"; } $items .= "</{$element}>"; } return $items; } 

$element — это элемент HTML, который собирается обернуть каждый элемент.
Вы можете спросить, что это за $type мы пропускаем через render() ?

Прежде чем ответить на этот вопрос, давайте посмотрим, как элементы отображаются в виде HTML-кода:

Если мы вызовем render() с ul как $type , результат будет:

 <ul> <li><a href="about">About</a></li> <li><a href="services">Services</a></li> <li><a href="portfolio">Portfolio</a></li> <li><a href="contact">Contact</a></li> </ul> 

Или, если мы называем это с div как $type :

 <div> <div><a href="about">About</a></div> <div><a href="services">Services</a></div> <div><a href="portfolio">Portfolio</a></div> <div><a href="contact">Contact</a></div> </div> 

Некоторые элементы HTML представляют собой группу родительских / дочерних тегов, таких как списки.
Поэтому, когда я вызываю render('ul') , я ожидаю, что он обернет каждый элемент в <li> .

 $element = ( in_array($type, ['ul', 'ol']) ) ? 'li' : $type; 

Эта строка проверяет, находится ли $type в array('ul', 'ol') ; Если это так, он вернет li иначе вернет $type .

Далее мы перебираем элементы $menu с указанным $parent . Если $parent равен нулю, он начнется с элементов на корневом уровне.

На каждой итерации мы проверяем, есть ли у элемента дочерние элементы. Если это произойдет, метод вызовет сам себя для визуализации детей и детей детей.

Внутри нашего конструктора меню у нас есть несколько методов, которые действуют как помощники:

GetURL (варианты)

Извлекает URL из $options .

 public function getUrl($options) { if( ! is_array($options) ) { return $options; } elseif ( isset($options['url']) ) { return $options['url']; } return null; } 

Вы можете спросить, зачем нам такая функция? Разве пользователь не предоставляет url при регистрации элемента?

Ты прав! Но, как показано ранее, у нас есть два способа определения URL:

 ... // This is the quick way $menu->add('About', 'about'); // OR $menu->add('About', array('url' => 'about')); ... 

getUrl() возвращает саму getUrl() $options , если это простая строка и если это массив, он ищет ключ ‘url` и возвращает значение.

extractAttr (варианты)

extractAttr получает массив $options и извлекает из него атрибуты HTML.

 public function extractAttr($options) { return array_diff_key($options, array_flip($this->reserved)); } 

Как упоминалось ранее, некоторые ключи в $options являются атрибутами HTML, а некоторые используются методами класса. Например, class и id — это атрибуты HTML, но url — это опция, которую мы используем внутри класса.

Как отмечалось ранее, у нашего менеджера меню есть атрибут с именем $reserved . Мы сохраняем параметры класса, такие как url в массиве $reserved чтобы отличать их от атрибутов HTML

Внутри extractAttr() мы использовали array_diff_key() для исключения зарезервированных опций.

array_diff_key() сравнивает ключи из $options с ключами из $reserved и возвращает разницу.

Результатом будет массив, содержащий все записи из $options которые недоступны в $reserved . Таким образом мы исключаем зарезервированные ключи из допустимых атрибутов HTML.

О, подождите минуту, array_diff_keys() использует ключи для сравнения, но $reserved имеет цифровые ключи:

 protected $reserved = array('url', 'pid'); /* $reserved = array(0 => 'url', 1 => 'pid'); */ 

Вы правильный читатель! Это когда array_flip() приходит на помощь!

array_flip() Обменивает все ключи с соответствующими значениями в массиве. Так что $reserved будет изменен на это:

 [ 'url' => 0, 'pid' => 1 ] 

parseAttr (атрибуты)

Атрибуты элемента должны быть в формате свойство = значение для использования в тегах HTML.
parseAttr получает ассоциативный массив атрибутов и помещает его в строку пар свойство = значение.

 public function parseAttr($attributes) { $html = array(); foreach ( $attributes as $key => $value) { if (is_numeric($key)) { $key = $value; } $element = (!is_null($value)) ? $key . '="' . $value . '"' : null; if (!is_null($element)) $html[] = $element; } return count($html) > 0 ? ' ' . implode(' ', $html) : ''; } 

parseAttr перебирает атрибуты для генерации строки. На каждой итерации мы проверяем, есть ли у текущего элемента числовой ключ; если это произойдет, мы будем считать, что ключ и значение совпадают.

Например:

 ['class' => 'navbar item active', 'id' => 'navbar', 'data-show'] 

Будет преобразован в:

 class="navbar item active" id="navbar" data-show="data-show" 

Метод помещает каждую пару в массив, затем мы помещаем их в пробел и возвращаем результат вызывающей стороне.

длина ()

Подсчитывает все зарегистрированные элементы:

 public function length() { return count($this->menu); } 

Asul (атрибут)

asUl вызывает render() и помещает результат в <ul> .
Он также получает массив атрибутов на случай, если вам нужно добавить некоторые атрибуты HTML в саму <ul> .

 public function asUl($attributes = array()) { return "<ul{$this->parseAttr($attributes)}>{$this->render('ul')}</ul>"; } 

Два других метода работают так же, как asUl() :

Ассоль (атрибуты)

asOl вызывает render() и помещает результат в <ol> .
Он также получает массив атрибутов на тот случай, если вам нужно добавить некоторые атрибуты HTML к самому <ol> .

 public function asOl($attributes = array()) { return "<ol{$this->parseAttr($attributes)}>{$this->render('ol')}</ol>"; } 

asDiv (атрибуты)

asDiv вызывает render() и помещает результат в <div> .
Он также получает массив атрибутов на случай, если вам нужно добавить атрибуты som HTML в сам <div> .

 public function asDiv($attributes = array()) { return "<div{$this->parseAttr($attributes)}>{$this->render('div')}</div>"; } 

Завершение

В этой части мы построили наш менеджер меню — основной класс для построения меню, который содержит все экземпляры классов, которые мы будем строить дальше. Вы можете увидеть полный исходный код для всех классов здесь или дождаться второй части, которая выйдет завтра.