“中国要复兴、富强,必须在开源软件领域起到主导作用,为了国家安全和人类发展,责无旁贷,我们须为此而奋斗”——By:云客
109. 菜单本地任务MenuLocalTasks
菜单本地任务(Menu local tasks)这个名字读者可能会感觉有点生涩,其实通常她就是页面主要内容上方的选项卡栏,可以联想下windows操作系统的任务栏,其中就是一些选项卡,但通常叫任务栏,drupal采用该称呼也体现了其重视计算机术语的统一性;不管是管理主题还是前端主题都用到了她,比如默认的seven管理主题“内容”菜单页面上方的“内容、评论、文件”,再比如默认的Bartik前端主题在登录状态下显示一篇文章时,页面主视觉区域标题下方的“查看、编辑、删除”,这些“选项卡tabs”构成的选项卡栏通常用作导航作用,在drupal世界里称为“菜单本地任务”,称为“本地任务”是因为drupal在功能层面需要将其和普通选项卡栏区别开来,采用这个专有名词来特指这种类型的选项卡。
概述:
一个页面的本地任务栏是由“本地任务块”渲染提供的(见下),本地任务栏可以由多组本地任务共同构成,每组本地任务包含多个任务链接,每一个任务链接就是一个选项卡,选项卡又被分为“主选项卡Primary tabs”和“次选项卡Secondary tabs”,系统仅支持两级选项卡,选项卡和任务链接是一对一关系,每个任务链接对应一个任务插件,定义在模块根目录的yml文件中(见下),具备相同任务组id的任务链接属于同一任务组,在继续讲解前需要先明确以下这些词的含义:
选项卡:一个选项卡中仅一个链接,反之一个链接对应一个选项卡,多个选项卡构成一个选项卡栏
任务链接:选项卡中的链接就是任务链接,一个任务链接对应一个插件
任务插件:在模块根目录的yml文件中定义,插件描述了任务链接,她们是一对一关系
任务组:多个任务链接构成一个任务组
本地任务:多个任务组构成完整的本地任务,也称为菜单本地任务栏
本地任务块:
在系统中“菜单本地任务”通常由本地任务块提供,块插件定义如下:
块管理标签admin_label:Tabs
块插件id:local_tasks_block
块插件类:Drupal\Core\Menu\Plugin\Block\LocalTasksBlock
需要显示菜单本地任务栏的页面放置该块即可,块知识详见本系列块主题,该块默认同时显示主、次选项卡,但可以配置只显示其一,可参考默认管理主题seven区块布局下的“主标签Primary tabs”和“次标签Secondary tabs”(官方下载的翻译文件中将选项卡“Tabs”翻译成了标签)
在该块实现中当任务栏只有一个选项卡时将不显示,任务栏的默认渲染数组如下:
$tabs = [
'#theme' => 'menu_local_tasks',
'#primary' => $primary_links['tabs'], //主选项卡(见下)
'#secondary' => $secondary_links['tabs'], //次选项卡(见下)
];
该主题钩子对应的默认模板文件:
core/themes/classy/templates/navigation/menu-local-tasks.html.twig
关于任务块有以下这些地方需要注意:
1、由于块可以配置,因此主选项卡和次选项卡可以分别用两个块来显示,在排版上更加灵活
2、任务栏中链接指向的所有页面都将自动产生任务栏,除非该页面不提供任务块
3、在定义任务插件时需注意任务栏中的链接应该是相关的,因为她们指向的页面有相同的任务栏
本地插件定义:
本地菜单任务栏中的链接由一个插件描述,一对一关系,插件在模块根目录下的“模块名. links.task.yml”文件中定义,以该文件内的根键做插件id(本地任务链接id),对应数组做插件定义,这称为本地任务的静态定义,插件修改钩子及派生机制提供动态定义(见插件篇),定义数组有如下键:
route_name:选项卡中链接所指页面的路由名,换句话说即点击后跳转到该路由,字符串值,必填项
route_parameters:对应以上路由名的路由参数,数组值,默认为空数组 [],可选
title:选项卡文本,字符串值,被系统理解为可翻译的,在系统内部被转化为翻译对象TranslatableMarkup,用于html链接标签的文本,并非title属性值
title_context:可选,标题翻译上下文,见翻译系统
parent_id:次选项卡时为父主选项卡的插件id,主选项卡时,该值为NULL
base_route:任务组id,同一任务组中所有插件的该值相同,可以是任意字符串(可以为非路由名),但通常采用默认选项卡的路由名,以此标识默认选项卡,即路由和该项值相同的选项卡称为默认选项卡,被标记为默认选项卡时如果没有设置权重,将自动赋值“-10”,以将其排在第一位,因此该项也叫做基本路由,当存在parent_id项时该项强制自动继承,此时通常省略也推荐省略
weight:排序权重,默认为null
options:链接选项,见本系列URL篇,可在其中指定链接属性,如是否在新窗口打开、属性值等
id:插件id,系统以yml文件中的定义根键自动赋值,最佳实践推荐根键采用路由名,如果多个定义有相同路由名,那么可以采用后缀加以区分
provider:提供插件定义的模块,由系统赋值
deriver:插件派生器的全限定类名,见插件系统
cache_tags:数组值,缓存标签,默认为空数组(见默认插件类)
cache_contexts:数组值,缓存上下文,默认为空数组(见默认插件类)
cache_max_age:缓存最大时间,整数秒,默认为永久(-1,见默认插件类)
class:实例化插件对象的默认实现类,默认为“Drupal\Core\Menu\LocalTaskDefault”,默认安装中仅插件id:comment.admin_approval没有使用该默认类,当有特殊需要时可以自定义
在定义yml文件时,以上键名中路由名是必须的,base_route和parent_id二选一必须,根据需要定义中也可以携带其他数据,如实体类型id等,但不能和以上键名冲突
菜单本地任务(Menu local tasks)插件管理器:
菜单本地任务插件管理器收集管理本地任务插件,并负责构建任务栏,定义如下:
服务id:plugin.manager.menu.local_task
类:Drupal\Core\Menu\LocalTaskManager
修改钩子:local_tasks
获取方法:
$localTaskManager =\Drupal::service('plugin.manager.menu.local_task');
使用入口:
$links =$localTaskManager->getLocalTasks($route_name, 0);
该方法解释见下文,和菜单链接的插件管理器一样,该插件管理器使用Yaml发现机制(详见本系列插件下集),查找模块根目录下的“模块名. links.task.yml”文件,其中定义了插件,静态定义和派生定义的所有插件可以通过以下修改钩子动态修改:
hook_local_tasks_alter(&$local_tasks)
该插件管理器方法解释如下:
public function getLocalTasksForRoute($route_name)
构造本地任务栏的核心主要方法,为一个路由页面解析并实例化任务插件对象,返回一个数组,最外层键名是代表选项卡层级的数字,0代表主选项卡,1代表次选项卡,下一级键名为插件id,键值为插件对象,次级选项卡仅包含当前可见的次级选项卡,该方法中有三个关键变量:
$base_routes:是一个数组,键名键值相同,保存路由页所有的任务组id(基路由)。
$parents:是一个数组,键值均为true,用键名来保存所有指向本路由页的任务链接插件id,如果他们存在父id,那么父id也被保存,而不管父id指向哪里。
$children:是一个数组,保存本路由页所有任务组中所有的父选项卡任务链接,以及所有可见的子选项卡栏中的任务链接,何为可见呢?子选项卡栏及其父选项卡中有一个任务链接是指向本路由页的即为可见,在任务栏中处于当前状态;该数组键值是一个数组(键名为插件id,键值为插件定义),键名以是否为主选项卡分两种情况:如果是主选项卡则键名为“>任务组id”,如果是次选项卡则键名为父选项卡的插件id
该方法需要注意一下几点:
1、在同一个任务组中如果有多个子选项卡栏都可见,以最后一个为可见选项卡栏,“最后一个”是以收集插件的顺序为依据;
2、多个任务组的父子选项卡分别进行合并,形成最终的任务栏
public function getTasksBuild($current_route_name, RefinableCacheableDependencyInterface &$cacheability)
进一步处理任务栏结构数组,收集缓存元数据、设置权限控制、构造显示渲染数组等,该方法为了减少数据库路由查询次数,采用预加载方法一次性查询以提高性能,主题钩子menu_local_task的预处理函数:
template_preprocess_menu_local_task(&$variables)
将处理渲染数组,如转变链接属性'#link'为链接渲染数组等
public function getLocalTasks($route_name, $level = 0)
使用入口,参数一为当前页面的路由名,参数二如果为0表示需要获取主选项卡栏,为1表示次选项卡栏,返回值是一个数组,包含三个键名:
cacheability:是其缓存元数据对象
route_name:字符串值路由名
tabs:数组,键名为插件id,键值为选项卡的渲染数组
该方法派发以下修改钩子:
menu_local_tasks
函数签名如下:
hook_menu_local_tasks_alter(&$data, $route_name, &$cacheability)
参数$data是一个数组,$data['tabs']包含选项卡内容,其下一级键名为0或1,代表主次选项卡,再下一级键名为插件id,键值为'#theme' => 'menu_local_task'的渲染数组;$data['cacheability']和第三参数相同,为元数据对象。
程序方式构建任务栏:
示例代码如下:
$route_name = 'system.admin_content'; //路由名
$primary = true;//是否显示主选项卡
$secondary = true;//是否显示次选项卡
$localTaskManager = \Drupal::service('plugin.manager.menu.local_task');
$cacheability = new \Drupal\Core\Cache\CacheableMetadata();
$cacheability->addCacheContexts(['route']);
$tabs = [
'#theme' => 'menu_local_tasks',
];
if ($primary) {
$links = $localTaskManager->getLocalTasks($route_name, 0);
$cacheability = $cacheability->merge($links['cacheability']);
$tabs += [
'#primary' => count(\Drupal\Core\Render\Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : [],
];
}
if ($secondary) {
$links = $localTaskManager->getLocalTasks($route_name, 1);
$cacheability = $cacheability->merge($links['cacheability']);
$tabs += [
'#secondary' => count(\Drupal\Core\Render\Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : [],
];
}
$build = [];//任务栏渲染数组
$cacheability->applyTo($build);
return $build + $tabs;
插件定义示例:
这里假设模块名为“yunke”,在模块根目录下建立以下文件:
yunke.links.task.yml
内容如下:
yunke.test1:
route_name: entity.node_type.collection
title: yunke1
base_route: block.admin_display
options: {attributes: {target: '_blank'}}
yunke.test2:
route_name: entity.node_type.collection
title: yunke2
base_route: system.admin_content
清除缓存后这将引起如下变化:
在“/admin/structure/block”页面添加了一个叫做“yunke1”的选项卡
在“/admin/content”页面添加了一个叫做“yunke2”的选项卡
在“/admin/structure/types”页面默认本没有选项卡,现在将前述两个页面的选项卡组合添加了,这是因为任务栏组合了两个任务组
为演示选项参数的作用,上列中打开“yunke1”选项卡时,将在新窗口打开
补充:
1、菜单本地任务的官网文档:
https://www.drupal.org/docs/8/api/menu-api/providing-module-defined-local-tasks
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Menu%21menu.api.php/group/menu/
2、系统中许多选项卡需要动态添加,因此采用了插件派生,这是一种动态定义插件的机制,详见本系列插件篇
反馈互动