构建组件

October CMS Documentation Docs

构建组件

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

plugins/
  acme/
    myplugin/
      components/
        componentname/       <=== 部件目录(小写)
          default.htm        <=== 默认标记(可选)
        ComponentName.php    <=== Class文件
      Plugin.php

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

# 组件类定义

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

namespace Acme\Blog\Components;

class BlogPosts extends \Cms\Classes\ComponentBase
{
    public function componentDetails()
    {
        return [
            'name' => '博客文章',
            'description' => '显示博客文章的集合。'
        ];
    }

    /**
     * 帖子在页面上变为可用 {{ component.posts }}
     */
    public function posts()
    {
        return ['First Post', 'Second Post', 'Third Post'];
    }
}

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

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

url = "/blog"

[blogPosts]
==

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

{% for post in blogPosts.posts %}
    {{ post }}
{% endfor %}

# 组件注册

组件必须通过覆盖插件注册类内的registerComponents方法来注册。 这会告诉 CMS 有关该组件的信息,并提供一个 短名称 以供使用。 注册组件的示例:

public function registerComponents()
{
    return [
        \October\Demo\Components\Todo::class => 'demoTodo'
    ];
}

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

# 组件属性

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

public function defineProperties()
{
    return [
        'maxItems' => [
            'title' => '最大项目',
            'description' => '允许的最多待办事项',
            'default' => 10,
            'type' => 'string',
            'validationPattern' => '^[0-9]+$',
            'validationMessage' => 'Max Items 属性只能包含数字符号'
        ]
    ];
}

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

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

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

$this->property('maxItems');

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

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

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

$properties = $this->getProperties();

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

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

# 下拉和设置属性

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

public function defineProperties()
{
    return [
        'units' => [
            'title' => '单位',
            'type' => 'dropdown',
            'default' => 'imperial',
            'placeholder' => '选择单位',
            'options' => ['metric' => '公制', 'imperial' => '英制']
        ]
    ];
}

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

public function defineProperties()
{
    return [
        'country' => [
            'title' => '国家',
            'type' => 'dropdown',
            'default' => 'us'
        ]
    ];
}

public function getCountryOptions()
{
    return ['us' => '美国', 'ca' => '加拿大', 'zh-cn' => '中华人民共和国'];
}

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

public function defineProperties()
{
    return [
        'country' => [
            'title' => '国家',
            'type' => 'dropdown',
            'default' => 'us'
        ],
        'state' => [
            'title' => 'State',
            'type' => 'dropdown',
            'default' => 'dc',
            'depends' => ['country'],
            'placeholder' => '选择一个州'
        ]
    ];
}

为了加载州列表,您应该知道当前在检查器中选择了哪个国家。 检查器将所有属性值 POST 到 getPropertyOptions 处理程序,因此您可以执行以下操作:

public function getStateOptions()
{
    // 从 POST 加载国家属性值
    $countryCode = post('country');

    $states = [
        'ca' => ['ab' => '艾伯塔省', 'bc' => '不列颠哥伦比亚省'],
        'us' => ['al' => '阿拉巴马州', 'ak' => '阿拉斯加州'],
        'zh-cn' => ['hb' => '河北省', 'hk' => '香港特别行政区', 'tw' => '台湾省']
    ];

    return $states[$countryCode];
}

# 页面列表属性

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

public function defineProperties()
{
        return [
            'postPage' => [
                'title' => '帖子页面',
                'type' => 'dropdown',
                'default' => 'blog/post'
            ]
        ];
}

public function getPostPageOptions()
{
    return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
}

# 路由参数

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

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

在某些情况下,组件属性 可能充当硬编码值或从 URL 引用该值。

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

url = "/blog/hard-coded-page"

[blogPost]
id = "2"

或者,可以使用 外部属性值 从页面 URL 动态引用该值:

url = "/blog/:my_custom_parameter"

[blogPost]
id = "{{ :my_custom_parameter }}"

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

$this->property('id');

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

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

# 处理页面执行周期

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

public function onRun()
{
    // 当加载页面或布局并将组件附加到它时,将执行此代码。

    $this->page['var'] = 'value'; // 向页面注入一些变量
}

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

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

1.布局onInit()函数。 1.页面onInit()函数。 1.布局onStart()函数。 1.布局组件onRun()方法。 1.布局onBeforePageStart()函数。 1.页面onStart()函数。 1.页面组件onRun()方法。 1.页面onEnd()函数。 1.布局onEnd()函数。

# 组件初始化

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

public function init()
{
    $this->addComponent(\Acme\Blog\Components\BlogPosts::class, 'blogPosts');
}

# 停止响应

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

public function onRun()
{
    if (true) {
        return Response::make('拒绝访问!', 403);
    }
}

您还可以从 onRun 方法返回 404 响应:

public function onRun()
{
    if (true) {
        $this->setStatusCode(404);
        return $this->controller->run('404');
    }
}

# AJAX 处理程序

组件可以承载 AJAX 事件处理程序。 它们在组件类中的定义与在 页面或布局代码 中定义的完全一样。 在组件类中定义的示例 AJAX 处理程序方法:

public function onAddItem()
{
    $value1 = post('value1');
    $value2 = post('value2');
    $this->page['result'] = $value1 + $value2;
}

如果这个组件的别名是 demoTodo 这个处理程序可以通过 demoTodo::onAddItem 访问。 有关在组件中使用 AJAX 的详细信息,请参阅 调用组件中定义的 AJAX 处理程序 文章。

# 默认标记

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

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

url = "/todo"

[demoTodo]
==
{% component 'demoTodo' %}

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

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

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

public function onRender()
{
    // 此代码将在默认组件之前执行
    // 标记呈现在页面或布局上。

    $this->page['var'] = '允许的最大项目数: ' . $this->property('maxItems');
}

# 组件部件

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

{% partial 'demoTodo::pagination' %}

可以使用上下文相关的宽松方法。如果在组件部件内部调用,它将直接引用自身。如果在主题部件内部调用,它将扫描页面/布局上使用的所有组件以查找匹配的部件名称并使用它。

{% partial '@pagination' %}

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

{% partial '@shared' %}

# 引用"自己"

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

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

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

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

如果在组件部件内部,您需要渲染另一个组件部件,将 __SELF__ 变量与部件名称连接起来:

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

# 唯一标识符

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

{{__SELF__.id}}

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

<!-- ID: demoTodo527c532e9161b -->
{% component 'demoTodo' %}

<!-- ID: demoTodo527c532ec4c33 -->
{% component 'demoTodo' %}

# 从代码渲染部件

您可以使用 renderPartial 方法以编程方式在 PHP 代码中渲染组件部件。 这将检查名为component-partial.htm的部件的组件,并将结果作为字符串返回。 第二个参数用于传递视图变量。 当你在 PHP 中渲染组件部件时,同样的 路径解析逻辑 也适用于 Twig; 使用 @ 前缀来引用组件本身内的部件。

$content = $this->renderPartial('@component-partial.htm');

$content = $this->renderPartial('@component-partial.htm', [
    'name' => '约翰·史密斯'
]);

例如,要将部件渲染为对 AJAX 处理程序 的响应:

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

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

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

# 使用组件注入页面资产

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

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

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

addCssaddJs 方法提供了第二个参数,用于将注入资产的属性定义为数组。 一个特殊的属性 - build - 可用,它将使用指定插件的当前版本为您注入的资产添加后缀。 这可用于在插件升级时刷新缓存的资产。

public function onRun()
{
    $this->addJs('/plugins/acme/blog/assets/javascript/blog-controls.js', [
        'build' => 'Acme.Test',
        'defer' => true
    ]);
}

您也可以使用字符串作为第二个参数,然后默认使用字符串值作为 build

public function onRun()
{
    $this->addJs('/plugins/acme/blog/assets/javascript/blog-controls.js', 'Acme.Test');
}