# 组件
组件是可重用代码的基础。 他们使你的工作更容易,并允许你从社区工作中获利。 组件是美好的。 这章会学到以下几点
1、如何写组件
2、什么是信号
3、如何发送弹出消息
4、如何使用AJAX
组件是可呈现对象。 它可以是一个形式,菜单,民意调查等。在一个页面内可以有你想要的任何组件。 在https://addons.nette.org/cs,
你可以找到来自Nette Framework社区志愿者的开源组件。
你可以找到一个组件的例子,它在示例Fifteen中的一个页面中使用。
组件是Nette \ Application \ UI \ Control类的后代
~~~
use Nette\Application\UI\Control;
class PollControl extends Control
{
}
~~~
当谈论组件时,我们通常指的是Control类的后代。 组件基本上是框架术语中的控制。
## 模板
组件的模板有工厂。 它创建一个模板,传递几个变量(如下所示)并注册默认过滤器。 渲染是我们的工作,它是在方法render()中完成的。 这是我们选择将从中加载模板的文件的位置。 render()方法也是注册模板中使用的变量的正确位置。 模板可以放在与组件相同的文件夹中,并具有相同的文件名。
~~~
public function render()
{
$template = $this->template;
$template->setFile(dirname(__FILE__) . '/PollControl.latte');
//插入一些参数到模板
$template->param = 'some value';
// 渲染它
$template->render();
}
~~~
可以将参数从模板传递到render()。
~~~
{control poll $poll}
~~~
~~~
// PollControl.php
public function render($poll) { ... }
~~~
~~~
// PollPresenter.php
protected function createComponentPoll() { ... }
~~~
## 链接
方法link()用于链接到信号。 如果我们在组件的模板中,并且目标是控制器,则使用模板中的宏{link}或宏{plink}渲染链接。
在组件中使用:
~~~
$url = $this->link('click!', $x, $y);
~~~
在模板中使用:
~~~
<a n:href="click! $x, $y"> ... </a>
~~~
## 弹出信息
组件具有自己的闪存消息存储,而与呈现者无关。 这些消息通知操作的结果,然后是重定向。
发送由flashMessage方法完成。 第一个参数是消息的文本,第二个(可选)参数是其类型(错误,警告,信息等)。 方法flashMessage()返回一个可以传递信息的弹出消息的实例。
例如:
~~~
public function deleteFormSubmitted(Form $form)
{
... 使用模型删除记录...
// 传递弹出消息
$this->flashMessage('Item was deleted.');
$this->redirect(...); // 跳转
}
~~~
模板在变量$flashes中自动接收此消息。 此变量包含一个对象数组(stdClass),它包含属性消息,类型,并且可以包含上面提到的额外信息。
例如:
~~~
{foreach $flashes as $flash}
<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}
~~~
如果保存了flashMessage()并发生重定向,则模板中将存在相同的参数$ flashes。 然后,邮件在接下来的三秒内保持活动状态 - 以防止在页面未成功加载并且用户刷新请求的情况下丢失。 如果某人连续两次刷新页面(F5),则弹出信息仍然存在,如果他/她单击其他位置,它就会消失。
## 信号(子请求)
信号(子请求)是与服务器在正常视图级别下的通信。信号是没有改变视图的动作。只有控制器可以更改视图,因此组件仅与信号一起工作。 $ component-> link()链接到一个信号,$ presenter-> link()通常链接到一个视图(除非使用感叹号 - $ presenter-> link('signal!'))。但是一个组件可以调用$ component-> presenter-> link('view')。
一个信号导致像原始请求(除了AJAX)的页面重新加载,并调用方法signalReceived($ signal),它的类Nette \ Application \ UI \ Component中的默认实现尝试调用一个由句柄{Signal }。进一步的处理依赖于给定的对象。作为组件(即控制和呈现器)的后代的对象试图用相关参数调用句柄{Signal}。
换句话说:采用方法句柄{Signal}的定义,并且在请求中接收的所有参数都与方法的参数相匹配。这意味着来自URL的参数id与方法的参数$ id匹配,某事到$ something等。如果方法不存在,则signalReceived方法抛出异常。
示例信号处理程序:
~~~
public function handleClick($x, $y)
{
if (!$this->isClickable($x, $y)) {
throw new Nette\Application\UI\BadSignalException('Action not allowed.');
}
// ... 处理信号 ...
}
~~~
信号可以由任何组件接收,如果它连接到组件树,实现接口ISignalReceiver对象的控制器。
信号的主要接收器是呈现器和可视化组件扩展控制。 信号是对象的标志,它必须做某事 - 在来自用户的投票中的轮询计数,具有新闻的框必须展开,形式被发送并且必须处理数据等等。
信号总是在当前控制器和视图上调用,因此不可能将其指向其他地方。
信号的URL使用方法Component :: link()创建。作为参数$ destination我们传递string {signal}!和$ args我们想传递给信号处理程序的参数数组。信号参数附加到当前控制器/视图的URL。 URL中的参数?do确定调用的信号。
其格式为{signal}或{signalReceiver} - {signal}。 {signalReceiver}是控制器中组件的名称。这就是为什么连字符(不准确的破折号)不能出现在组件名称中 - 它用于划分组件名称和信号,但是可以组成几个组件。
方法isSignalReceiver()验证组件(第一个参数)是否是信号(第二个参数)的接收器。第二个参数可以省略 - 然后它找出组件是否是任何信号的接收器。如果第二个参数为TRUE,它将验证组件或其后代是否是信号的接收器。
在任何阶段前面的句柄{Signal}可以通过调用processSignal()方法手动执行信号,processSignal()负责信号执行。接收器组件(如果没有设置它是演示者本身)并发送它的信号。
例如:
~~~
if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, 'sorting')) {
$this->processSignal();
}
~~~
信号过早执行,不会再次调用。
### 子请求与请求
信号和请求之间的差异:
* 子请求保留所有组件
* 一个请求只保留持久性组件
## 无效和摘要
在信号的处理期间,可能发生需要重新呈现组件的改变。 这些方法:redrawControl()和isControlInvalid()。 他们是使用AJAX与Nette的基本元素。
Nette还提供了更高的有效性粒度,只有在组件级别,使用片段。
代码段可以逐个失效(任何组件可以根据需要拥有尽可能多的代码段)。 如果整个组件无效,那么它的每个片段也会失效。 如果某个组件的任何子组件(组件)无效,则该组件被视为无效。
## 持久参数
通常需要在组件中保留某个参数,以便对组件进行全部处理。 它可以是例如分页中的页的编号。 此参数应使用注释@persistent标记为persistent。
~~~
class PollControl extends Control
{
/** @persistent */
public $page = 1;
...
}
~~~
此参数将作为GET参数在每个链接中自动传递,直到用户离开具有此组件的页面。
永远不要盲目地相信永久性参数,因为它们可以容易地伪造(通过覆盖URL)。 例如,验证页码是否在正确的时间间隔内。
## 高级使用组件
组件大多是可渲染的。 但也存在不可描述的组件。 一些组件可能有一些不是后代。 Nette Framework为所有这些类型的组件引入了几个类和接口。
对象继承允许我们像现实世界中一样具有类的层次结构。 我们可以通过扩展创建新类。 这些扩展类是原始类的后代,并继承其参数和方法。 扩展类可以向继承的类添加自己的参数和方法。
需要知识类层次结构才能正确理解事物的工作原理。
~~~
Nette\ComponentModel\Component { IComponent }
|
+- Nette\ComponentModel\Container { IContainer }
|
+- Nette\Application\UI\Component { ISignalReceiver, IStatePersistent }
|
+- Nette\Application\UI\Control { IPartiallyRenderable }
|
+- Nette\Application\UI\Presenter { IPresenter }
~~~
### Nette\ComponentModel\IComponent
接口Nette \ ComponentModel \ IComponent必须由每个组件实现。 它需要方法getName(),它返回它的名称,getParent()返回它的父级。 可以使用方法setParent()设置名称和父项 - 第一个参数是父项,第二个参数是名称。
### Nette\ComponentModel\Component
Nette \ ComponentModel \ Component是IComponent的标准实现。 它是所有组件的共同祖先,包括表单元素。 它有几种方法来遍历:
lookup($ type)在层次结构中查找给定类或接口的对象。 例如$ component-> lookup('Nette \ Application \ UI \ Presenter')返回presenter,如果组件绑定到它(甚至在层次结构中)。
lookupPath($ type)返回path - 通过连接$ type的当前和组件之间的所有组件的名称而创建的字符串。 例如$ component-> lookupPath('Nette \ Application \ UI \ Presenter')返回组件向演示者的唯一标识符。
### Nette\ComponentModel\IContainer
父组件不仅实现了IComponent,还实现了Nette \ ComponentModel \ IContainer。 包含用于在组件上添加,删除,获取和迭代的方法的接口。 这样的组件可以创建层次结构 - 控制器可以包含表单,这些表单可以包含表单输入。 组件的整个树层次由IContainer对象和IComponent树叶的分支创建。
### Nette\ComponentModel\Container
Nette \ ComponentModel \ Container是IContainer接口的标准实现。 它是例如一个或多个控件和控制器的祖先。
它提供了正确添加,获取和删除对象的方法,当然还有对其内容的迭代。 尝试调用未定义的子进程调用factory createComponent($ name)。 方法createComponent($ name)调用当前组件中的方法createComponent <component name>,它将组件的名称作为参数传递。 创建的组件然后作为其子进程传递到当前组件。 我们称之为theese组件工厂,它们可以在继承自Container的类中实现。
### Nette\Application\UI\Component
类Nette \ Application \ UI \ Component是演示者中使用的所有组件的祖先。 演示者中的组件是由演示者在其生命周期期间保持的对象。
它们具有彼此影响的能力,将它们的状态保存到URL中并响应用户命令(信号),并且不必是可呈现的。
### Nette\Application\UI\Control
控件是一个可渲染组件。 它是一个可重用的Web应用程序的一部分,这是整个章节。 当谈论组件这是我们通常想到的类。 它记住它的一部分应该在AJAX请求中呈现,我们已经谈到了。
控件不是表示网页的可视部分,而是逻辑部分。 可以重复地,有条件地并且每次使用不同的模板来呈现它。
### 组件树
如果给定不同的名称,可以在页面上多次呈现相同的组件(这可以动态地完成)。 父代可以是控制器,组件或实现IContainer的其他对象。
层次结构可以如下所示:
~~~
Nette\Application\UI\Presenter { root of tree of components is always a presenter }
|
--Nette\Application\UI\Control { implements IContainer => can be parent }
|
--Nette\ComponentModel\Component
|
--Nette\ComponentModel\Component { does not implement IContainer => cannot be parent }
|
--Nette\Application\UI\Control
|
--Nette\ComponentModel\Component
~~~
## 延迟组成
Nette中的组件模型提供了非常动态的树工作流(组件可以删除,添加,移动)。 在创建组件时,依赖于knowing parent(在构造函数中)将是一个错误。 在大多数情况下,父组件在创建组件时并不知道。
~~~
$control = new NewsControl;
// ...
$parent->addComponent($control, 'shortNews');
~~~
## 监控更改
如何找出组件何时添加到控制器的树? 观看父级的更改是不够的,因为父级的父级可能已添加到控制器。 方法监视器($ type)是在这里帮助。 每个组件都可以监视任意数量的类和接口。 通过调用附加的方法($ obj)(detached($ obj)个别)报告添加或删除,其中$ obj是受监视类的对象。
示例:类UploadControl,表示在Nette \ Forms中上传文件的表单元素,必须将表单的属性类型设置为值multipart / form-data。 但在创建对象的时候,它不必附加到任何形式。 什么时候修改窗体? 解决方案很简单 - 我们在构造函数中创建一个监视请求:
~~~
class UploadControl extends Nette\Forms\Controls\BaseControl
{
public function __construct($label)
{
$this->monitor('Nette\Forms\Form');
// ...
}
// ...
}
~~~
并且在表单可用时调用附加的方法:
~~~
protected function attached($form)
{
parent::attached($form);
if ($form instanceof Nette\Forms\Form) {
$form->getElementPrototype()->enctype = 'multipart/form-data';
}
}
~~~
使用查找监视和查找组件或路径是非常精确优化的,以获得最佳性能。
## Iterating over children
有一个方法getComponents($ deep = FALSE,$ type = NULL)。 第一个参数确定是否应该深入(递归地)查找组件。 使用TRUE,它不仅迭代所有它的孩子,而且它的所有孩子的孩子等。第二个参数服务器作为一个可选的过滤器通过类或接口。
例如,这是表单验证如何在内部执行的方式:
~~~
$valid = TRUE;
foreach ($form->getComponents(TRUE, 'Nette\Forms\IControl') as $control) {
if (!$control->getRules()->validate()) {
$valid = FALSE;
break;
}
}
~~~
- Nette简介
- 快速开始
- 入门
- 主页
- 显示文章详细页
- 文章评论
- 创建和编辑帖子
- 权限验证
- 程序员指南
- MVC应用程序和控制器
- URL路由
- Tracy - PHP调试器
- 调试器扩展
- 增强PHP语言
- HTTP请求和响应
- 数据库
- 数据库:ActiveRow
- 数据库和表
- Sessions
- 用户授权和权限
- 配置
- 依赖注入
- 获取依赖关系
- DI容器扩展
- 组件
- 字符串处理
- 数组处理
- HTML元素
- 使用URL
- 表单
- 验证器
- 模板
- AJAX & Snippets
- 发送电子邮件
- 图像操作
- 缓存
- 本土化
- Nette Tester - 单元测试
- 与Travis CI的持续集成
- 分页
- 自动加载
- 文件搜索:Finder
- 原子操作