bootstrap动态菜单_Bootstrap 3的动态菜单构建器:菜单管理器

萧琛
2023-12-01

bootstrap动态菜单

Creating menus and navigation bars has never been easier than with Twitter Bootstrap. We can easily create stylish navigation bars without too much effort. While it’s enough for some projects, you might encounter situations when you need to have more control over the links and items inside your menu. Imagine you need to load the items from a database table. What if you need to limit the items to a group of users having the required permissions? This is where static solutions can’t help much, so, we should think of a more dynamic approach.

创建菜单和导航栏从未比使用Twitter Bootstrap容易。 我们可以轻松创建时尚的导航栏,而无需付出太多努力。 尽管对于某些项目来说已经足够了,但是您可能会遇到需要对菜单中的链接和项目进行更多控制的情况。 假设您需要从数据库表中加载项目。 如果您需要将项目限制为具有所需权限的一组用户,该怎么办? 这是静态解决方案无济于事的地方,因此,我们应该考虑一种更动态的方法。

In this tutorial I’m going to show you how to create your own dynamic menu builder in PHP. This is a two part series, with the first part focusing on demo code and the Menu class, and the second part taking care of the other classes and usage examples.

在本教程中,我将向您展示如何在PHP中创建自己的动态菜单构建器。 这是一个分为两部分的系列,其中第一部分着重于演示代码和Menu类,第二部分着重于其他类和用法示例。

定义目标 (Defining The Goal)

Before we start, let’s briefly outline what we need to accomplish. Personally I like to get more with writing less and I’m sure you do as well.

在开始之前,让我们简要概述一下我们需要完成的工作。 就个人而言,我喜欢写更少的东西来获得更多收益,而且我相信你也能做到。

We should be able to create menus the easy way, but the code should stay clean and professional, written in modern object oriented style, like so:

我们应该能够以简单的方式创建菜单,但是代码应该保持整洁和专业,以现代的面向对象风格编写,如下所示:

//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');

We should be able to add sub items in a semantic way rather than explicitly defining a pid for each sub item:

我们应该能够以语义方式添加子项,而不是为每个子项显式定义一个pid

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

We should provide a way for adding HTML attributes to the items:

我们应该提供一种向项目添加HTML属性的方法:

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

Wouldn’t it be fantastic if we could easily append or prepend some content to the anchors like a caret symbol or a graphical icon?

如果我们可以轻松地将一些内容(例如插入符号或图形图标)添加或添加到锚点上,那不是很棒吗?

//...
$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>');
//...

We should also provide a way to filter the items:

我们还应该提供一种过滤项目的方法:

$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;    
});

Menus should be rendered as HTML entities like lists, divs or any boilerplate code we need:

菜单应呈现为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();
    
    //...

Think we can pull it off?

认为我们可以实现?

创建菜单生成器 (Creating The Menu Builder)

I’m going to split our menu builder into three independent components:

我将菜单构建器分为三个独立的组件:

  • Menu manager manages the menu items. It creates, modifies and renders the items.

    菜单管理器管理菜单项。 它创建,修改和渲染项目。

  • Item represents menu items as objects. It stores title, link, attributes and extra data with each item.

    项目将菜单项表示为对象。 它与每个项目一起存储标题,链接,属性和额外数据。

  • Link represents links as objects.

    链接表示链接为对象。

We’re going to implement these components in three separate class definitions: Menu, Item and Link.

我们将在三个单独的类定义中实现这些组件: MenuItemLink

These are the methods we’re going to create with a short explanation of what they do and what they return:

这些是我们要创建的方法,并简要说明了它们的作用和返回的作用:

Menu

菜单

  • Item add(string $title, mixed $options) Adds an item.

    Item add(string $title, mixed $options)添加一个项目。

  • array roots() Returns items at root level (items with no parent).

    array roots()返回根级别的项目(没有父项的项目)。

  • array whereParent(int $parent) Returns items with the given parent id.

    array whereParent(int $parent)返回具有给定父ID的项目。

  • Menu filter(callable $closure) Filters items by a callable passed by the user.

    Menu filter(callable $closure)按用户传递的可调用对象过滤项目。

  • string render(string $type) Renders menu items in HTML format.

    string render(string $type)以HTML格式呈现菜单项。

  • string getUrl(array $options) Extracts the URL from user $options.

    string getUrl(array $options)从用户$options提取URL。

  • array extractAttr(array $options) Extracts valid HTML attributes from user $options.

    array extractAttr(array $options)从用户$options提取有效HTML属性。

  • string parseAttr(array $attributes) Generates a string of key=”value” pairs(separated by space).

    string parseAttr(array $attributes)生成一个键=“值”对的字符串(以空格分隔)。

  • int length() Counts all items in the menu.

    int length()计算菜单中的所有项目。

  • string asUl(array $attributes) Renders the menu as an unordered list.

    string asUl(array $attributes)将菜单呈现为无序列表。

  • string asOl(array $attributes) Renders the menu as an ordered list.

    string asOl(array $attributes)将菜单呈现为有序列表。

  • string asDiv(array $attributes) Renders the menu as HTML

    string asDiv(array $attributes)将菜单呈现为HTML

    .

Item

项目

  • Item add(string $title, mixed $options) Adds a sub item.

    Item add(string $title, mixed $options)添加一个子项目。

  • int id() Generates a unique Id for the item.

    int id()为该项目生成一个唯一的ID。

  • int get_id() Returns item’s Id.

    int get_id()返回项目的ID。

  • int get_pid() Returns item’s pid (parent Id).

    int get_pid()返回项目的pid (父ID)。

  • boolean hasChilderen() Check whether the item has any children or not.

    boolean hasChilderen()检查项目是否有任何子级。

  • array childeren() Returns children of the item.

    array childeren()返回该项目的子级。

  • mixed attributes(string $key [, string $value]) Sets or gets attributes of the item.

    mixed attributes(string $key [, string $value])设置或获取项目的属性。

  • mixed meta(string $key [, string $value]) Sets or gets item’s meta data.

    mixed meta(string $key [, string $value])设置或获取项目的元数据。

Link

链接

  • string get_url() Returns link URL.

    string get_url()返回链接URL。

  • string get_text() Returns link text.

    string get_text()返回链接文本。

  • Link prepend(string $content) Inserts content at the beginning of link text.

    Link prepend(string $content)在链接文本的开头插入内容。

  • Link append(string $content) Append content to the link text.

    Link append(string $content)将内容追加到链接文本。

  • mixed attributes(string $key [, string $value]) Sets or gets link’s attributes.

    mixed attributes(string $key [, string $value])设置或获取链接的属性。

Okay now that we have the prototypes on paper, We can start building them. You can also use the above list as a quick documentation cheat sheet for when you inevitably use our end product in a project.

好的,现在我们有了纸上的原型,我们可以开始构建它们。 当您不可避免地在项目中使用我们的最终产品时,也可以将以上列表用作快速文档备忘单。

Run your favorite text editor, create a new file and name it menu.php.

运行您喜欢的文本编辑器,创建一个新文件,并将其命名为menu.php

menu.php

menu.php

class Menu {

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

Menu has two protected attributes:

Menu具有两个受保护的属性:

  • $menu Contains objects of type Item.

    $menu包含Item类型的对象。

  • $reserved Contains reserved options.

    $reserved包含保留选项。

$menu stores an array of registered items.

$menu存储一系列已注册的项目。

Okay, what about $reserved? What is it exactly?

好吧, $reserved呢? 究竟是什么?

When the user registers an item, he/she passes an array of options through the add method. Some options are HTML attributes and some just contain the information we need inside the class.

当用户注册项目时,他/她通过add方法传递一组选项。 有些选项是HTML属性,有些只是包含我们在类内部所需的信息。

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

In this example, class is an HTML attribute but url is a reserved key. We do this to distinguish HTML attributes from other data.

在此示例中, class是HTML属性,而url是保留键。 我们这样做是为了将HTML属性与其他数据区分开。

添加(标题,选项) (add(title, options))

This method creates an item:

此方法创建一个项目:

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() accepts two parameters, the first one is title and the second one is options.

add()接受两个参数,第一个是title ,第二个是options

Options can be a simple string or an associative array of options. If options is just a string, add() assumes that the user wants to define the URL without any other options.

选项可以是简单的字符串或选项的关联数组。 如果options只是一个字符串,则add()假定用户要定义URL而没有任何其他选项。

To create an Item we need to provide the following data:

要创建Item我们需要提供以下数据:

  • $title item’s title

    $title项目的标题

  • $url item’s link URL

    $url项目的链接URL

  • $pid item’s pid (if it is a sub item)

    $pid项的pid (如果它是子项)

  • $attr item’s HTML attributes

    $attr项目HTML属性

  • $manager a reference to the Menu object (Menu manager)

    $managerMenu对象的引用(菜单管理器)

$url is obtained from getUrl() method which we’re going to create later; So let’s just get the URL regardless of how it works.

$url是从getUrl()方法获得的,我们将在以后创建; 因此,无论URL如何工作,我们都只需获取它即可。

Next, we’ll check if $options contains key pid, if it does, we store it in $pid.

接下来,我们将检查$options包含密钥pid ,如果包含,则将其存储在$pid

We also store a reference to the Menu object with each item. This reference allows us to use methods of the menu manager within Item context (we’ll talk about this later).

我们还将每个项目存储对Menu对象的引用。 此参考使我们可以在Item上下文中使用菜单管理器的方法(我们将在后面讨论)。

Lastly, we create the item, push it to $menu array and return the Item.

最后,我们创建项目,将其推入$menu数组并返回Item

There we go! we created our first method.

好了! 我们创建了第一个方法。

roots() (roots())

Fetches items at root level by calling method whereParent(parent) which we’ll create shortly.

通过调用稍后将创建的whereParent(parent)方法在根级别获取项目。

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

父母(父母) (whereParent(parent))

Returns items at the given level.

返回给定级别的项目。

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

Inside whereParent() we make use of PHP’s built-in function array_filter on $menu array.

whereParent()内部,我们使用$menu数组上PHP内置函数array_filter

If you call the method with no argument, It will return items with no parent meaning items at root level. That’s why roots() calls whereParent() with no argument!

如果调用不带参数的方法,它将返回在根级别没有父项的项目。 这就是为什么roots()不带参数调用whereParent()的原因!

过滤器(回调) (filter(callback))

Filters items based on a callable provided by the user.

根据用户提供的可调用项过滤项目。

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

	}

	return $this;
}

Isn’t it simple? It just accepts a callable as parameter and runs PHP’s array_filter on $menu array. closure depends on how you like to limit your menu items. I’m going to show you how to use this later.

是不是很简单? 它只接受一个callable作为参数,并在$menu数组上运行PHP的array_filter。 closure取决于您希望如何限制菜单项。 稍后我将向您展示如何使用它。

渲染(类型,pid) (render(type, pid))

This method renders the items in HTML format.

此方法以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 is the HTML element which is going to wrap each item. You might ask so what’s that $type we pass through render() then?

$element是将包装每个项目HTML元素。 您可能会问,那么我们通过render()传递的$type是什么?

Before answering this question, let’s see how items are rendered as HTML code:

在回答这个问题之前,让我们看一下如何将项目呈现为HTML代码:

If we call render() with ul as $type, The output would be:

如果我们调用ul作为$type render() ,输出将是:

<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>

Or if we call it with div as $type:

或者,如果我们用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>

Some HTML elements are a group of parent/child tags like lists. So when I call render('ul'), I’m expecting it to wrap each item in a <li> tag.

一些HTML元素是一组父/子标签,例如列表。 因此,当我调用render('ul') ,我期望它将每个项目包装在<li>标记中。

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

This line checks if $type is in the array('ul', 'ol'); If it is, it will return li otherwise it’ll return $type.

这行检查$type是否在array('ul', 'ol') ; 如果是,它将返回li否则将返回$type

Next, we iterate over $menu elements with the given $parent. If $parent is null, it’ll start with the items at root level.

接下来,我们使用给定的$parent遍历$menu元素。 如果$parent为null,则将从根级别的项目开始。

On each iteration, we check if the item has any children. If it does, the method would call itself to render the children and children of children.

在每次迭代中,我们检查项目是否有任何子代。 如果是这样,则该方法将调用自身以呈现子代和子代子代。

Inside our menu builder, we have several methods which act as helpers:

在我们的菜单构建器中,我们有几种充当助手的方法:

getUrl(选项) (getUrl(options))

Extracts URL from $options.

$options提取URL。

public function getUrl($options)
{
	if( ! is_array($options) ) {
			return $options;
		} 

		elseif ( isset($options['url']) ) {
			return $options['url'];
		} 

		return null;
}

You might ask why do we need such a function in the first place? Doesn’t the user provide the url when registering the item?

您可能会问为什么我们首先需要这样的功能? 用户注册项目时不提供url吗?

You’re right! But as shown earlier, we have two ways to define the URL:

你是对的! 但是,如前所述,我们有两种方法来定义URL:

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

getUrl() returns the $options itself if it’s just a simple string and if it’s an array, it looks for key ‘url` and returns the value.

如果它只是一个简单的字符串,如果是数组,则getUrl()返回$options本身,它将查找键'url`并返回值。

extractAttr(选项) (extractAttr(options))

extractAttr gets the $options array and extracts HTML attributes from it.

extractAttr获取$options数组并从中提取HTML属性。

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

As mentioned earlier, some keys in $options are HTML attributes and some are used by class methods. For example class and id are HTML attributes but url is an option which we use inside the class.

如前所述, $options中的某些键是HTML属性,而某些则由类方法使用。 例如, classid是HTML属性,但是url是我们在类内部使用的选项。

As noted earlier our menu manager has an attribute named $reserved. We store class options like url in $reserved array to distinguish them from HTML attributes.

如前所述,我们的菜单管理器具有名为$reserved的属性。 我们将诸如url类的类选项存储在$reserved数组中,以将其与HTML属性区分开。

Inside extractAttr() we used array_diff_key() to exclude reserved options.

extractAttr()内部,我们使用array_diff_key()排除了保留选项。

array_diff_key() compares the keys from $options against the keys from $reserved and returns the difference.

array_diff_key()$options中的键与$reserved中的键进行比较,并返回差值。

The result will be an array containing all entries from $options which are not available in $reserved. This is how we exclude reserved keys from valid HTML attributes.

结果将是一个包含$options中所有条目的数组,这些条目在$reserved中不可用。 这就是我们从有效HTML属性中排除保留键的方式。

Oh wait a minute, array_diff_keys() uses keys for comparison but $reserved has numeric keys:

等等, array_diff_keys()使用键进行比较,但是$reserved具有数字键:

protected $reserved = array('url', 'pid');

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

You are right keen reader! This is when array_flip() comes to the rescue!

您是正确的读者! 这是当array_flip()抢救出来的时候!

array_flip() Exchanges all keys with their associated values in an array. So $reserved will be changed to this:

array_flip()交换数组中所有键及其关联的值。 因此, $reserved将更改为:

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

parseAttr(属性) (parseAttr(attributes))

Item attributes should be in a property=value format to be used in HTML tags. parseAttr gets an associative array of attributes and coverts it into a string of property=value pairs.

项目属性应采用HTML标记中使用的property = value格式。 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 iterates over attributes to generate the string. On each iteration we check if the current element has a numeric key; if it does, we will assume that the key and the value are the same.

parseAttr遍历属性以生成字符串。 在每次迭代中,我们检查当前元素是否具有数字键; 如果是这样,我们将假定键和值相同。

For example:

例如:

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

Would be converted to:

将转换为:

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

The method pushes each pair to an array, then we implode them with a space and return the result to the caller.

该方法将每对插入一个数组,然后将它们插入一个空格,然后将结果返回给调用方。

长度() (length())

Counts all items registered:

计算所有已注册的项目:

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

asUl(属性) (asUl(attribute))

asUl calls render() and put the result in a <ul> tag. It also receives an array of attributes in case you need to add some HTML attributes to <ul> itself.

asUl调用render()并将结果放入<ul>标记中。 如果您需要向<ul>本身添加一些HTML属性,它也会收到一个属性数组。

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

The other two methods work just like asUl():

其他两种方法就像asUl()一样工作:

asOl(属性) (asOl(attributes))

asOl calls render() and put the result in a <ol> tag. It also receives an array of attributes in case you need to add some HTML attributes to <ol> itself.

asOl调用render()并将结果放入<ol>标记中。 如果您需要向<ol>本身添加一些HTML属性,它也会收到一个属性数组。

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

asDiv(属性) (asDiv(attributes))

asDiv calls render() and put the result in a <div> tag. It also gets an array of attributes in case you need to add som HTML attributes to <div> itself.

asDiv调用render()并将结果放入<div>标记中。 如果您需要将Som HTML属性添加到<div>本身,它还会获取一个属性数组。

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

结语 (Wrapping up)

In this part, we built our menu manager – the main class for building the menu, which contains all instances of the classes we’ll be building next. You can see the complete source code for all classes here or wait for part 2 which is coming out tomorrow.

在这一部分中,我们构建了菜单管理器–构建菜单的主要类,其中包含我们将要构建的类的所有实例。 您可以在此处查看所有类的完整源代码,或者等待第二部分将于明天发布。

翻译自: https://www.sitepoint.com/dynamic-menu-builder-bootstrap-3-menu-manager/

bootstrap动态菜单

 类似资料: