组件开发

介绍

组件文件和目录位于插件目录的 /components 子目录中。 每个组件都有一个定义组件类的PHP文件和一个可选的组件partials目录。 组件partials目录名称与以小写字母书写的组件类名称匹配。 组件目录结构的示例:

  1. plugins/
  2. acme/
  3. myplugin/
  4. components/
  5. componentname/ <=== 组件partials目录
  6. default.htm <=== 组件默认标记(可选)
  7. ComponentName.php <=== 组件类文件
  8. Plugin.php

组件必须使用registerComponents方法在Plugin注册类中注册

组件类定义

组件类文件定义组件功能和组件属性。 组件类文件名应与组件类名匹配。 组件类应该继承\Cms\Classes\ComponentBase类。 下一个示例的组件应该在plugins/acme/blog/components/BlogPosts.php文件中定义。

  1. namespace Acme\Blog\Components;
  2. class BlogPosts extends \Cms\Classes\ComponentBase
  3. {
  4. public function componentDetails()
  5. {
  6. return [
  7. 'name' => 'Blog Posts',
  8. 'description' => 'Displays a collection of blog posts.'
  9. ];
  10. }
  11. // This array becomes available on the page as {{ component.posts }}
  12. public function posts()
  13. {
  14. return ['First Post', 'Second Post', 'Third Post'];
  15. }
  16. }

componentDetails方法是必需的。该方法应该返回一个带有两个键的数组:namedescription。 名称和说明显示在CMS后端用户界面中。

当此组件附加到页面或布局时,类属性和方法在页面上通过组件变量可用,该组件变量的名称与组件短名称或别名匹配。 例如,如果上一个示例中的BlogPost组件是在具有短名称的页面上定义的:

  1. url = "/blog"
  2. [blogPosts]
  3. ==

您可以通过blogPosts变量访问其posts方法。 请注意,Twig支持方法的属性表示法,因此您不需要使用括号。

  1. {% for post in blogPosts.posts %}
  2. {{ post }}
  3. {% endfor %}

组件注册

必须通过覆盖Plugin注册类中的registerComponents方法来注册组件。 这告诉CMS有关组件并提供短名称以便使用它。 注册组件的示例:

  1. public function registerComponents()
  2. {
  3. return [
  4. 'October\Demo\Components\Todo' => 'demoTodo'
  5. ];
  6. }

这将使用默认别名demoTodo注册Todo组件类。 有关使用组件的更多信息,请参见CMS组件文章

组件属性

将组件添加到页面或布局时,可以使用属性对其进行配置。 使用组件类的defineProperties方法定义属性。 下一个示例显示了如何定义组件属性:

  1. public function defineProperties()
  2. {
  3. return [
  4. 'maxItems' => [
  5. 'title' => 'Max items',
  6. 'description' => 'The most amount of todo items allowed',
  7. 'default' => 10,
  8. 'type' => 'string',
  9. 'validationPattern' => '^[0-9]+$',
  10. 'validationMessage' => 'The Max Items property can contain only numeric symbols'
  11. ]
  12. ];
  13. }

该方法应该返回一个数组,其中属性键作为索引,属性参数作为值。 属性键用于访问组件类中的组件属性值。 使用具有以下键的数组定义属性参数:

描述
title 标题,必需的属性,它由CMS后端的组件Inspector使用。
description 必需的属性描述,它由CMS后端的组件Inspector使用。
default 选填,将组件添加到CMS后端中的页面或布局时使用的默认属性值。
type 选填,指定属性类型。 该类型定义了在Inspector中显示属性的方式。 目前支持的类型是stringcheckboxdropdown。 默认值:string
validationPattern 用户在检查器中输入属性值时使用的可选正则表达式。 验证只能用于string属性。
validationMessage 验证失败时显示的可选错误消息。
required 可选,该字段必填,不填是提示validationMessage中的内容
placeholder 字符串和下拉属性的可选占位符。
options 下拉属性的可选选项数组。
depends 下拉属性所依赖的属性名称数组。 请参阅下面的下拉属性
group 一个可选的组名。 组在Inspector中创建部分,简化用户体验。 在多个属性中使用相同的组名来组合它们。
showExternalParam 指定Inspector中属性的外部参数编辑器的可见性。 默认值:true

在组件内部,您可以使用property方法读取属性值:

  1. $this->property('maxItems');

如果未定义属性值,则可以提供默认值作为property方法的第二个参数:

  1. $this->property('maxItems', 6);

您还可以将所有属性加载为数组:

  1. $properties = $this->getProperties();

要从组件的Twig partials访问该属性,请使用引用Component对象的__SELF__变量:

{{ __SELF__.property('maxItems') }}

下拉属性

下拉属性的选项列表可以是静态的或动态的。 静态选项使用属性定义的options元素定义。示例:

  1. public function defineProperties()
  2. {
  3. return [
  4. 'units' => [
  5. 'title' => 'Units',
  6. 'type' => 'dropdown',
  7. 'default' => 'imperial',
  8. 'placeholder' => 'Select units',
  9. 'options' => ['metric'=>'Metric', 'imperial'=>'Imperial']
  10. ]
  11. ];
  12. }

显示Inspector时,可以从服务器动态获取选项列表。 如果在下拉属性定义中省略options参数,则选项列表被视为动态。 组件类必须定义返回选项列表的方法。 该方法应具有以下格式的名称:get*Property*Options,其中Property是属性名称,例如:getCountryOptions。 该方法返回一个选项数组,其中选项值为键,选项标签为值。 动态下拉列表定义的示例:

  1. public function defineProperties()
  2. {
  3. return [
  4. 'country' => [
  5. 'title' => 'Country',
  6. 'type' => 'dropdown',
  7. 'default' => 'us'
  8. ]
  9. ];
  10. }
  11. public function getCountryOptions()
  12. {
  13. return ['us'=>'United states', 'ca'=>'Canada'];
  14. }

动态下拉列表可以依赖于其他属性。 例如,州名单可能取决于所选国家。 依赖项在属性定义中使用depends参数声明。 下一个示例定义了两个动态下拉属性,状态列表取决于国家/地区:

  1. public function defineProperties()
  2. {
  3. return [
  4. 'country' => [
  5. 'title' => 'Country',
  6. 'type' => 'dropdown',
  7. 'default' => 'us'
  8. ],
  9. 'state' => [
  10. 'title' => 'State',
  11. 'type' => 'dropdown',
  12. 'default' => 'dc',
  13. 'depends' => ['country'],
  14. 'placeholder' => 'Select a state'
  15. ]
  16. ];
  17. }

要加载状态列表,您应该知道Inspector中当前选择的国家/地区。 Inspector将所有属性值POST到getPropertyOptions处理程序,因此您可以执行以下操作:

  1. public function getStateOptions()
  2. {
  3. $countryCode = Request::input('country'); // Load the country property value from POST
  4. $states = [
  5. 'ca' => ['ab'=>'Alberta', 'bc'=>'British columbia'],
  6. 'us' => ['al'=>'Alabama', 'ak'=>'Alaska']
  7. ];
  8. return $states[$countryCode];
  9. }

页面列表属性

有时组件需要创建指向网站页面的链接。 例如,博客帖子列表包含指向博客帖子详细信息页面的链接。 在这种情况下,组件应该知道帖子详细信息页面文件名(然后它可以使用页面Twig过滤器)。 October包含一个用于创建动态下拉页面列表的帮助程序。 下一个示例定义了postPage属性,该属性显示了一个页面列表:

  1. public function defineProperties()
  2. {
  3. return [
  4. 'postPage' => [
  5. 'title' => 'Post page',
  6. 'type' => 'dropdown',
  7. 'default' => 'blog/post'
  8. ]
  9. ];
  10. }
  11. public function getPostPageOptions()
  12. {
  13. return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
  14. }

路由参数

组件可以直接访问页面URL中定义的路由参数值。

  1. // 返回URL段值,例如: /page/:post_id
  2. $postId = $this->param('post_id');

在某些情况下,组件属性可以充当硬编码值或引用URL中的值。

这个硬编码示例显示了使用标识符“2”的博客文章:

  1. url = "/blog/hard-coded-page"
  2. [blogPost]
  3. id = "2"

或者,可以使用external property value从页面URL动态引用该值:

  1. url = "/blog/:my_custom_parameter"
  2. [blogPost]
  3. id = "{{ :my_custom_parameter }}"

在这两种情况下,都可以使用property方法检索值:

  1. $this->property('id');

如果需要访问路由参数名称:

  1. $this->paramName('id'); // 返回 "my_custom_parameter"

处理页面执行周期

通过覆盖组件类中的onRun方法,组件可以参与页面执行循环事件。 每次加载页面或布局时,CMS控制器都会执行此方法。 在方法内部,您可以通过page属性将变量注入到Twig环境中:

  1. public function onRun()
  2. {
  3. // 加载页面或布局并将组件附加到该代码时,将执行此代码。
  4. $this->page['var'] = 'value'; // 将一些变量注入页面
  5. }

页面执行生命周期处理程序

当页面加载时,October执行可以在布局和页面PHP部分和组件类中定义的处理函数。 执行处理程序的顺序如下:

  1. 布局Layout onInit() function.
  2. 页面Page onInit() function.
  3. 布局Layout onStart() function.
  4. 布局中的组件Layout components onRun() method.
  5. 布局Layout onBeforePageStart() function.
  6. 页面Page onStart() function.
  7. 页面组件Page components onRun() method.
  8. 页面Page onEnd() function.
  9. 组件Layout onEnd() function.

组建初始化

有时您可能希望在首次实例化组件类时执行代码。 您可以覆盖组件类中的init方法来处理任何初始化逻辑,这将在AJAX处理程序之前和页面执行生命周期之前执行。 例如,此方法可用于动态地将另一个组件附加到页面。

  1. public function init()
  2. {
  3. $this->addComponent('Acme\Blog\Components\BlogPosts', 'blogPosts');
  4. }

页面周期响应

页面执行生命周期中的所有方法一样,如果组件中的onRun方法返回一个值,这将在此时停止循环并返回 响应浏览器。 这里我们使用Responsefacade返回一个拒绝访问的消息:

  1. public function onRun()
  2. {
  3. if (true) {
  4. return Response::make('Access denied!', 403);
  5. }
  6. }

AJAX处理

组件可以托管AJAX事件处理程序。 它们在组件类中定义,就像它们可以在页面或布局代码中定义一样。 在组件类中定义的示例AJAX处理程序方法:

  1. public function onAddItem()
  2. {
  3. $value1 = post('value1');
  4. $value2 = post('value2');
  5. $this->page['result'] = $value1 + $value2;
  6. }

如果此组件的别名是demoTodo,则可以通过demoTodo::onAddItem访问此处理程序。 有关将AJAX与组件一起使用的详细信息,请参阅[调用组件中定义的AJAX处理程序(ajax-handlers.md#calling-handlers))文章。

默认标签

所有组件都可以带有默认标记,当将其包含在带有{%component%}标记的页面上时使用该标记,尽管这是可选的。 默认标记保存在组件partials部分目录中,该目录与小写的组件类具有相同的名称。

默认组件标记应放在名为default.htm的文件中。 例如,Demo ToDo组件的默认标记在文件 /plugins/october/demo/components/todo/default.htm 中定义。 然后可以使用{%component%}标记将其插入页面的任何位置:

  1. url = "/todo"
  2. [demoTodo]
  3. ==
  4. {% component 'demoTodo' %}

默认标记还可以采用在渲染时覆盖组件属性 的参数。

  1. {% component 'demoTodo' maxItems="7" %}

这些属性在onRun方法中不可用,因为它们是在页面循环完成后建立的。 相反,可以通过覆盖组件类中的onRender方法来处理它们。 CMS控制器在呈现默认标记之前执行此方法。

  1. public function onRender()
  2. {
  3. // 此代码将在页面或布局上呈现默认组件标记之前执行。
  4. $this->page['var'] = 'Maximum items allowed: ' . $this->property('maxItems');
  5. }

组件部分(cms-partials.md)

除了默认标记之外,组件还可以提供可以在前端或默认标记本身内使用的其他部分。 如果Demo ToDo组件具有分页部分,它将位于 /plugins/october/demo/components/todo/pagination.htm 中并使用以下内容显示在页面上:

  1. {% partial 'demoTodo::pagination' %}

可以使用比较容易的方法,即上下文。 如果在组件partial内部调用,它将直接引用自身。 如果在主题partial内部调用,它将扫描页面/布局上使用的所有组件以获得匹配的部分名称并使用它。

  1. {% partial '@pagination' %}

通过将部分文件放在名为 components/partials 的目录中,多个组件可以共享部分。 当找不到通常的组件部分时,此目录中找到的部分用作后备。 例如,位于 /plugins/acme/blog/components/partials/shared.htm 中的共享部分可以由任何组件在页面上显示,使用:

  1. {% partial '@shared' %}

引用自身 “self”

组件可以使用__SELF__变量在其partials中引用它们自己。 默认情况下,它将返回组件的短名称或别名

  1. <form data-request="{{__SELF__}}::onEventHandler">
  2. [...]
  3. </form>

组件也可以引用自己的属性。

  1. {% for item in __SELF__.items() %}
  2. {{ item }}
  3. {% endfor %}

如果在组件partial中你需要渲染另一个组件,则部分地将__SELF__变量与部分名称连接起来:

  1. {% partial __SELF__~"::screenshot-list" %}

唯一标识符

如果在同一页面上调用两次相同的组件,则可以使用id属性来引用每个实例。

  1. {{__SELF__.id}}

每次显示组件时,ID都是唯一的。

  1. <!-- ID: demoTodo527c532e9161b -->
  2. {% component 'demoTodo' %}
  3. <!-- ID: demoTodo527c532ec4c33 -->
  4. {% component 'demoTodo' %}

在代码里引用partials

您可以使用renderPartial方法以编程方式在PHP代码中呈现组件部分。 这将检查组件的名为component-partial.htm的部分,并将结果作为字符串返回。 第二个参数用于传递视图变量。

  1. $content = $this->renderPartial('component-partial.htm');
  2. $content = $this->renderPartial('component-partial.htm', [
  3. 'name' => 'John Smith'
  4. ]);

例如,要将partial作为对AJAX 处理的响应呈现:

  1. function onGetTemplate()
  2. {
  3. return ['#someDiv' => $this->renderPartial('component-partial.htm')];
  4. }

另一个例子可能是通过从onRun 页面循环方法返回一个值来覆盖整个页面视图响应。 此代码将使用“Response”facade专门返回XML响应:

  1. public function onRun()
  2. {
  3. $content = $this->renderPartial('default.htm');
  4. return Response::make($content)->header('Content-Type', 'text/xml');
  5. }

使用组件注入页面资源

组件可以将静态文件(CSS和JavaScript文件)注入它们所附加的页面或布局。 使用控制器的addCssaddJs方法将静态文件添加到CMS控制器。 它可以在组件的onRun方法中完成。 请阅读有关在页面文章中注入静态文件.的更多详细信息。 例:

  1. public function onRun()
  2. {
  3. $this->addJs('/plugins/acme/blog/assets/javascript/blog-controls.js');
  4. }

如果addCssaddJs方法参数中指定的路径以斜杠(/)开头,那么它将相对于网站根目录。 如果资源路径不以斜杠开头,则它相对于组件目录。