# Forms
Nette \ Forms极大地方便了创建和处理Web表单。 它可以做什么?
1、验证客户端(JavaScript)和服务器端发送的数据
2、提供高水平的安全性
3、多个渲染模式
Nette Framework非常努力地做到安全性,既然表单是最常见的用户输入,Nette表单就像不可穿透一样好。 所有都是动态和透明地维护,不需要手动设置。 众所周知的漏洞(例如跨站脚本(XSS)和跨站点请求伪造(CSRF))被过滤,以及特殊的控制字符。 检查所有输入的UTF-8有效性。 在验证时,将检查每个多项选择,选择框和类似项的伪造值。 听起来不错? 让我们试试看。
使用Nette / Forms,您可以减少常规任务,例如在服务器端和客户端双重验证。 您还可以避免常见的错误和安全问题。
# 开始表单
让我们为我们的应用创建一个简单的注册表单。 表单使用组件添加到控制器:
~~~
use Nette\Application\UI;
class HomepagePresenter extends UI\Presenter
{
// ...
protected function createComponentRegistrationForm()
{
$form = new UI\Form;
$form->addText('name', 'Name:');
$form->addPassword('password', 'Password:');
$form->addSubmit('login', 'Sign up');
$form->onSuccess[] = [$this, 'registrationFormSucceeded'];
return $form;
}
// called after form is successfully submitted
public function registrationFormSucceeded(UI\Form $form, $values)
{
// ...
$this->flashMessage('You have successfully signed up.');
$this->redirect('Homepage:');
}
}
~~~
![](https://box.kancloud.cn/31ea676879d60d5891156179befe8da6_700x620.png)
注意一定要引用use Nette\Application\UI;
这样一个表单组件就完成了。下面应是在模板中渲染使用控制标注了。
~~~
{control registrationForm}
~~~
我们先把app\presenters\templates\Homepage\default.latte
里清空,再把上面代码放进去,刷新主页就可以看到以下图(记得,现在操作只是实例,如果开发的话自已去做自已控制器和视图)
![](https://box.kancloud.cn/5ffd8ba945946c8b255176c4be8b2944_489x313.png)
我们创建了一个简单的表单,在提交并成功验证后调用registrationFormSucceeded()。 表单本身作为第一个参数传递。 提交的值作为Nette \ Utils \ ArrayHash对象中包含的第二个参数传递。 如果你喜欢一个简单的数组,你可以键入提示第二个参数数组$ values。 您还可以使用$ values = $ form-> getValues()检索提交的值。
现在我们发现提交时没有任何的反映,原来是我们没有引用他特有的JS,所以我们再把代码改成这样。
~~~
{block content}
<div id="content">
{control registrationForm}
</div>
{/block}
{block scripts}
{include parent}
<script src="https://files.nette.org/sandbox/jush.js"></script>
{/block}
~~~
现在我们试一下提交,就会发现出现
~~~
You have successfully signed up.
~~~
说明框架中的JS一定要引用他的JS。
这个表单呈现的形式遵循基本的Web辅助功能。 所有标签都生成为<label>元素并与其输入相关联。
$values中存储的数据不包含表单按钮的值,因此它们可以进行更多操作(例如插入数据库)。 值得注意的是,在输入的左侧和右侧的空格被删除。
虽然我们提到验证,我们的表单没有。 让我们修复它。 为了要求用户输入名字,在表单项上调用setRequired()方法。 您可以将错误消息作为可选参数传递,如果用户未填写他的姓名,则会显示错误消息:
~~~
$form->addText('name', 'Name:')
->setRequired('Please fill your name.');
~~~
![](https://box.kancloud.cn/fe135965c38a61dd5f0bc33e43ae6e2f_525x198.png)
我们试一下没有输入任何数据在name.它就会自动验证并提示信息(这个功能我超喜欢,不会让你再去学JS验证)。
![](https://box.kancloud.cn/acd248ad858610d12c401bd10d79edcd_1007x528.png)
~~~
如果你不使用nette / sandbox,你需要链接netteForms.js以启用JavaScript验证。 您可以在src / assets文件夹中找到该文件。
<script src="netteForms.js"></script>
~~~
Nette Framework将所需的类添加到所有的强制性元素。 添加以下样式到模板中将名称输入的标签变为红色。
~~~
<style>
.required label { color: maroon }
</style>
~~~
![](https://box.kancloud.cn/d41d01f813bf48cfa7534050eeb7e499_466x216.png)
即使默认验证规则可能很方便,但是我们将添加另一个验证规则,这次使用addRule()方法创建自已规则。
例如表单将获得另一个条件输入年龄,它是可选的,它必须是一个数字(Form :: INTEGER)和在某些边界(Form :: RANGE)。 这次我们将使用addRule()创建。
我们在HomepagePresenter 增加以下代码。
~~~
$form->addText('age', 'Age:')
->setRequired(FALSE)
->addRule(Form::INTEGER, 'Your age must be an integer.')
->addRule(Form::RANGE, 'You must be older 18 years and be under 120.', [18, 120]);
~~~
![](https://box.kancloud.cn/c5212a7ea8d4c6ff3439f488529199a0_707x292.png)
注意自已创建一定要加上提示这句代码
![](https://box.kancloud.cn/5fd79392ad603c60bd4244845916230f_466x215.png)
如果不加就会出错,为了这个错误本人足足找了好几天才明白。因为官网没有提示。
现在再刷新一次。
![](https://box.kancloud.cn/65e931d62823667fd7405cc2db62e29a_479x246.png)
好,可以试一试输入错误数值,它就会象上面弹出提示。超喜欢。
Nette Framework支持HTML5,包括新的表单元素。 感谢,我们可以设置年龄输入为数字:
~~~
$form->addText('age', 'Age:')
->setType('number')
~~~
这样除了数字外其他都输不进去了。安全性又提高。
在最先进的浏览器(即Google Chrome,Safari和Opera)中,输入旁边会显示微小箭头。 iPhone的Safari显示带数字的优化键盘。
让我们回到密码字段,这应该是必需的。 此外,我们可以限制最小密码长度(Form :: MIN_LENGTH),同样以下代码修改:
~~~
$form->addPassword('password', 'Password:')
->setRequired('Pick a password')
->addRule(Form::MIN_LENGTH, 'Your password has to be at least %d long', 3);
~~~
我们将再添加一个输入passwordVerify,其中用户将被提示再次输入他的密码,以检查拼写错误。 使用验证规则,我们将检查两个字段是否包含相同的值(Form :: EQUAL)。 注意动态第三个参数,其实是密码控制本身:
~~~
$form->addPassword('passwordVerify', 'Password again:')
->setRequired('Fill your password again to check for typo')
->addRule(Form::EQUAL, 'Password missmatch', $form['password']);
~~~
![](https://box.kancloud.cn/0c732c1f29d10b3f348478b0f271e7ca_683x397.png)
如果表单不用于注册,而是用于编辑记录,那么可以设置默认值。
这是一个完整的,完全工作的注册表,具有客户端和服务器端验证。 自动处理魔术引号,检查无效的UTF-8字符串等。一切都准备好了,没有在我们这边的努力 - Nette已经照顾它。
# 表单输入
**产生标准表单文本类输入方法**
**addText($name, $label = NULL)**
添加这些文本字段(类文本输入)。 Nette自动会把输入左侧和右侧的空格去除。 除了预设的验证规则,以下也是可用的:
| Form::MIN_LENGTH | 最小字符串长度 |
| --- | --- |
| Form::MAX_LENGTH | 最大字符串长度 |
| Form::LENGTH | 精确长度 |
| Form::EMAIL | 查看是否有效的电子邮件地址 |
| Form::URL | 查看是否有效URL地址 |
| Form::PATTERN | 测试正则表达式的整个值,有点像是在^和a $里面 |
| Form::INTEGER | 查看是否整数 |
| Form::NUMERIC | Form :: INTEGER的别名 |
| Form::FLOAT | 查看值是否浮点数 |
| Form::MIN | 最小的整数值 |
| Form::MAX | 最大的整数值 |
| Form::RANGE |数值是否在范围内 |
使用方法
~~~
$form->addText('zip', 'Postcode:')
->setRequired()
->addRule(Form::PATTERN, 'Postcode must have exactly 5 numerals', '([0-9]\s*){5}');
~~~
我们试一下
![](https://box.kancloud.cn/1b8d4e58f634d21e2de884ce45c18c03_809x489.png)
我们输入错误数值就会弹出以下图错误信息。
![](https://box.kancloud.cn/bdd2ef630624f89a4f91ed7900ab90b2_910x510.png)
**addPassword($name, $label = NULL)**
添加密码字段(TextInput类)。 也会自动修剪左侧和右侧的空格。 支持与addText相同的验证规则集。
~~~
$form->addPassword('password', 'Password:')
->setRequired()
->addRule(Form::MIN_LENGTH, 'Password has to be at least %d characters long', 3)
->addRule(Form::PATTERN, 'Password must contain a number', '.*[0-9].*');
~~~
**addTextArea($name, $label = NULL)**
添加多行文本字段(TextArea类)。支持与addText相同的验证规则集。 与在线输入不同,它不修剪任一边缘上的输入空白。
~~~
$form->addTextArea('note', 'Note:')
->setRequired(FALSE) // optional
->addRule(Form::MAX_LENGTH, 'Your note is way too long', 10000);
~~~
**addUpload($name, $label = NULL)**
添加文件上传字段(类UploadControl)。 除了预设的验证规则,以下是可用的:
| Form::MAX_FILE_SIZE | 验证最大文件大小 |
| --- | --- |
| Form::MIME_TYPE | 检查MIME类型是否有效 |
| Form::IMAGE | 检查上传的文件是JPEG,PNG还是GIF |
~~~
$form->addUpload('thumbnail', 'Thumbnail:')
->setRequired(FALSE) // optional
->addRule(Form::IMAGE, 'Thubnail must be JPEG, PNG or GIF')
->addRule(Form::MAX_FILE_SIZE, 'Maximum file size is 64 kB.', 64 * 1024 /* v bytech */);
~~~
**addMultiUpload($name, $label = NULL)**
添加多个文件上传字段。 验证规则与addUpload()相同,并添加以下内容:
| | |
| --- | --- |
| Form::MIN_LENGTH | 最少文件数值 |
| Form::MAX_LENGTH | 最大文件数值 |
| Form::LENGTH | 上传文件的精确数值 |
~~~
$form->addMultiUpload('files', 'Files');
~~~
**addHidden($name, $default = NULL)**
添加隐藏字段(类HiddenField)。
~~~
$form->addHidden('user_id');
~~~
**addCheckbox($name, $caption = NULL)**
添加一个复选框(类复选框)。 返回值为布尔TRUE或FALSE,选中或未选中。
~~~
$form->addCheckbox('agree', 'I agree with terms')
->setRequired('You must agree with our terms');
~~~
**addRadioList($name, $label = NULL, array $items = NULL)**
添加单选按钮(RadioList类)。 提供值的数组作为第三个参数传递。
~~~
$sex = [
'm' => 'male',
'f' => 'female',
];
$form->addRadioList('gender', 'Gender:', $sex);
~~~
**addCheckboxList($name, $label = NULL, array $items = NULL)**
添加用于选择多个元素的复选框列表。 值的数组作为第三个参数传递。 组件检查提交的值是否来自给定范围。
~~~
$form = new Form;
$form->addCheckboxList('colors', 'Colors:', [
'r' => 'red',
'g' => 'green',
'b' => 'blue',
]);
~~~
**addSelect($name, $label = NULL, array $items = NULL)**
添加选择框(SelectBox类)。 提供值的数组作为第三个参数传递。 也许是二维的。 第一个项目通常用作号召性用语消息,但实际选择时没有价值 - 这就是setPromt()方法。
![](https://box.kancloud.cn/0cf516ac84d24fa5aeb4d9bf51648a87_228x157.png)
~~~
$countries = [
'Europe' => [
'CZ' => 'Czech republic',
'SK' => 'Slovakia',
'GB' => 'United Kingdom',
],
'CA' => 'Canada',
'US' => 'USA',
'?' => 'other',
];
$form->addSelect('country', 'Country:', $countries)
->setPrompt('Pick a country');
~~~
**addMultiSelect($name, $label = NULL, array $items = NULL)**
添加多个选择框
~~~
$form->addMultiSelect('options', 'Pick many:', $options);
~~~
**addSubmit($name, $caption = NULL)**
添加提交按钮
~~~
$form->addSubmit('submit', 'Register');
~~~
如果您不想在按下提交按钮时验证表单(例如“取消”或“预览”按钮),可以使用setValidationScope([])将其关闭。
可以添加多个提交按钮。 要找出他们中的哪一个被点击,请使用
~~~
if ($form['submit']->isSubmittedBy()) {
// ...
}
~~~
或者
~~~
if ($form->isSubmitted() === $form['submit']) {
// ...
}
~~~
**addButton($name, $caption)**
添加按钮(类Button),无提交功能。 它对于将其他功能绑定到id很有用,例如JavaScript动作。
~~~
$form->addButton('raise', 'Raise salary')
->setAttribute('onclick', 'raiseSalary()');
~~~
**addImage($name, $alt = NULL)**
以图片的形式添加提交按钮
~~~
$form->addImage('submit', '/path/to/image');
~~~
**addContainer($name)**
添加一个子表单(类Container)或一个容器,可以像表单一样处理。 这意味着你可以使用setDefaults()或getValues()方法。
~~~
$sub1 = $form->addContainer('first');
$sub1->addText('name', 'Your name:');
$sub1->addEmail('email', 'Email:');
$sub2 = $form->addContainer('second');
$sub2->addText('name', 'Your name:');
$sub2->addEmail('email', 'Email:');
~~~
# Low-level forms
要向表单添加项目,您不必调用$ form-> addXyz()。 从版本2.1开始,表单项可以仅在模板中引入。 例如,如果您需要生成动态项目。
~~~
{foreach $items as $item}
<p><input type=checkbox name="sel[]" value={$item->id}> {$item->name}</p>
{/foreach}
~~~
提交后,您可以检索值:
~~~
$values = $form->getHttpData($form::DATA_TEXT, 'sel[]');
$values = $form->getHttpData($form::DATA_TEXT | $form::DATA_KEYS, 'sel[]');
~~~
在第一个参数中,指定元素类型(类型=文件为DATA_FILE,文本,密码或电子邮件等单行输入为DATA_LINE,其余为DATA_TEXT)。 第二个参数与HTML属性名称匹配。 如果需要保留密钥,可以将第一个参数与DATA_KEYS组合。 这对select,radioList或checkboxList很有用。
getHttpData()返回已清理的输入。 在这种情况下,它将始终是有效的UTF-8字符串数组,无论什么是由表单发送。 如果你想接收安全的数据,这是一个替代使用$ _POST或$ _GET直接。
# 验证规则
大多数已提及的输入形式都支持以下规则:
| | |
| --- | --- |
| Form::FILLED | 是元素填充? |
| Form::REQUIRED | Form :: FILLED的别名 |
|Form::EQUAL |值是等于? |
| Form::NOT_EQUAL | 该值不能等于给定值 |
| Form::IS_IN | 检查值是否在数组中 |
| Form::IS_NOT_IN | 检查值是否不在数组中 |
| Form::VALID | 输入验证? |
| Form::BLANK | 该项目不能填充 |
我们可以为所有验证规则设置自定义错误消息,或者使用默认错误消息。 多语言表单的邮件会自动翻译。
在错误消息文本中可以使用以下特殊替换字符串:
| | |
| --- | --- |
| %label | 替换为标签文本 |
| %name | 替换为输入标识 |
| %value | 替换为提交的输入值 |
除了验证规则,可以设置条件。 它们的设置非常像规则,但我们使用addRule()而不是addCondition(),当然我们保留它没有错误消息(条件只是询问):
~~~
$form->addPassword('password', 'Password:')
// if password is not longer than 5 characters ...
->addCondition(Form::MAX_LENGTH, 5)
// ... then it must contain a number
->addRule(Form::PATTERN, 'Must contain number', '.*[0-9].*');
~~~
条件可以链接到另一个元素,然后它的使用。 用addConditionOn()替换addCondition()并将其他输入设置为第一个参数。 在以下情况下,只有在复选框被选中(即它的布尔值为TRUE)时才需要电子邮件:
~~~
$form->addCheckbox('newsletters', 'send me newsletters');
$form->addEmail('email', 'Email:')
// if checkbox is checked ...
->addConditionOn($form['newsletters'], Form::EQUAL, TRUE)
// ... require email
->setRequired('Fill your email address');
~~~
所有条件都可以用〜(波浪号)取反,即。 addCondition(〜Form :: NUMBER,...)如果字段未填充,则通过验证。 条件可以使用elseCondition()和endCondition()方法分组为复杂结构。
正如你所看到的,验证规则和条件的语言是强大的。 即使所有的结构在服务器端和客户端,在JavaScript中工作。
我们可以添加自己的验证器。 方法addRule()和addCondition()也接受回调函数或lambda函数:
~~~
// user validation: checks if $item is divisible by $arg
function divisibilityValidator($item, $arg)
{
return $item->value % $arg === 0;
}
$form->addInteger('number', 'Number:')
->setRequired(FALSE)
->addRule('divisibilityValidator', 'Number must be divisible by %d.', 8);
~~~
# 自定义错误
我们经常发现用户的输入是错误的,即使每个表单的都有验证方法。 例如,插入新数据库行时,输入错误或无效的登录凭据。 在这种情况下,我们可以使用addError()方法手动添加错误。 它可以在指定输入或表单本身上调用:
~~~
try {
$values = $form->getValues();
$this->user->login($values->username, $values->password);
$this->redirect('Homepage:');
} catch (Nette\Security\AuthenticationException $e) {
$form->addError($e->getMessage());
}
~~~
建议将错误直接链接到表单元素,因为当使用默认渲染器时,错误将显示在它旁边。
~~~
$form['date']->addError("We apologize but this date has been already taken.");
~~~
# 全局默认消息
要全局更改验证错误消息,请修改静态数组Nette \ Forms \ Rules :: $ defaultMessages。
~~~
Nette\Forms\Rules::$defaultMessages[Form::FILLED] = "All fields are obligatory.";
~~~
# 自定义验证功能
如果需要自定义验证功能,可以为整个表单定义自己的验证函数,并将它们绑定到onValidate事件。 通常,这用于验证值的组合:
~~~
protected function createComponentSignInForm()
{
$form = new Form();
...
$form->onValidate[] = [$this, 'validateSignInForm'];
return $form;
}
public function validateSignInForm($form)
{
$values = $form->getValues();
if (...) { // validation logic
$form->addError('Password does not match.');
}
}
~~~
您可以将多个函数绑定到事件。 如果不使用$ form-> addError()添加任何错误,该函数被认为是成功的。
# JavaScript
验证规则通过HTML5属性data-nette-rules传递到JavaScript部分,其中包含所有规则和条件的JSON。 验证本身由另一个脚本处理,它关联所有提交事件,迭代所有输入并运行相应的验证。 默认实现是netteForms.js文件,可以在src / assets找到。 你需要做的是链接它。
~~~
<script src="netteForms.js"></script>`
~~~
可以通过扩展Nette.validators对象来添加自定义验证规则:
~~~
<script>
Nette.validators.divisibilityValidator = function(elem, args, val) {
return val % args === 0;
};
</script>
~~~
如果我们的PHP验证回调是一个类中的静态方法,我们通过删除所有反斜杠\并用单个下划线_替换双冒号来创建JavaScript的名称。 App \ MyValidator :: divisibilityValidator被转换为AppMyValidator_divisibilityValidator。
# 禁用验证
在某些情况下,您需要禁用验证。 如果提交按钮在提交后不应运行验证(例如取消或预览按钮),您可以通过调用$ submit-> setValidationScope([])来禁用验证。 您还可以通过指定要验证的项目来部分验证表单。
~~~
$form->addText('name')->setRequired();
$details = $form->addContainer('details');
$details->addInteger('age')->setRequired('age');
$details->addInteger('age2')->setRequired('age2');
$form->addSubmit('send1'); // Validates the whole form
$form->addSubmit('send2')->setValidationScope(FALSE); // Validates nothing
$form->addSubmit('send3')->setValidationScope([$form['name']]); // Validates only 'name' input
$form->addSubmit('send4')->setValidationScope([$form['details']['age']]); // Validates only 'age' input
$form->addSubmit('send5')->setValidationScope([$form['details']]); // Validates 'details' container
~~~
表单上的onValidate事件总是被调用,不受setValidationScope的影响。 只有在指定此容器进行部分验证时,才会调用容器上的onValidate事件。
# 处理表单
通过方法getValues()访问提交的值,如果我们传递TRUE作为第一个参数,它返回一个ArrayHash类的实例或一个简单的数组。
~~~
$values = $form->getValues(); // Nette\Utils\ArrayHash
$values = $form->getValues(TRUE); // array
~~~
# 禁用输入
为了禁用输入,可以调用$ control-> setDisabled(TRUE)。
~~~
$form->addEmail('email', 'E-mail:')->setDisabled(TRUE);
~~~
输入不能被写入,它的值不会被getValues()返回。如果我们需要只读输入(输入将被呈现为禁用,但其值将显示),我们先禁用输入,然后设置值。 原因是setDisabled()方法重置输入的值。
~~~
$form->addText('readonly', 'Readonly:')->setDisabled()->setValue('readonly value');
~~~
如果只需要删除输入的值而不是禁用它,则可以使用setOmitted(TRUE)。 此选项对于例如拒绝反垃圾邮件输入很有用。
~~~
$form->addText('antispam', 'Antispam:')->setOmitted(TRUE);
~~~
# 修改输入值
使用addFilter方法,我们可以在处理表单之前修改检索的值。 我们可以结合addFilter和addCondition和addConditionOn方法。
~~~
$form->addText('zip', 'Postcode:')
->addCondition($form::FILLED)
->addFilter(function ($value) {
return str_replace(' ', '', $value);
});
~~~
当我们稍后在表单处理中访问值时,邮编不会包含任何空格。
# 其他表单扩展
你还可以扩展带有标签,类和其他事情的窗体。注意扩展的条件,规则和用法的顺序。
设置类或JavaScript属性:
~~~
$form->addInteger('number', 'Number:')
->setAttribute('class', 'bigNumbers');
$form->addSelect('rank', 'Order by:', ['price', 'name'])
->setAttribute('onchange', 'submit()'); // calls JS function submit() on change
// applying on whole $form
$form->getElementPrototype()->id = 'myForm';
$form->getElementPrototype()->target = '_top';
~~~
设置输入类型(HTML5):
~~~
$form->addText('email', 'Your e-mail:')
->setType('email')
->setAttribute('placeholder', 'Please, fill in your e-mail address');
~~~
设置描述(默认情况下在输入后呈现):
~~~
$form->addText('phone', 'Number:')
->setOption('description', 'This number will remain hidden');
~~~
为了添加HTML内容,可以使用Html类。
~~~
use Nette\Utils\Html;
$form->addText('phone', 'Phone:')
->setOption('description', Html::el('p')
->setHtml('This number remains hidden. <a href="...">Terms of service.</a>')
);
~~~
Html元素也可以用来代替label:$ form-> addCheckbox('conditions',$ label)。
# 控制器中的表单
当我们在控制器中用到表单这个组件,那我们就一定要加上
~~~
use Nette\Application\UI\Form;
~~~
它是Nette\Forms\Form扩展组件。
# 在多个控制器中使用同一个表单组件
在我们需要在多个控制器中使用一个共同表单的情况下,我们有两个选项:
1、把表单放入控制器的层次结构中成为一个共同的父类并在那里定义一个子方法
2、或在单独的子类中定义它,并在控制器的子类中创建它的实例。
这种类别的适当位置是例如。 app / forms / SignInFormFactory.php。 我们的子类将如下所示:
~~~
use Nette\Application\UI\Form;
class SignInFormFactory
{
/**
* @return Form
*/
public function create()
{
$form = new Form;
$form->addText('name', 'Name:');
// ...
$form->addSubmit('login', 'Log in');
return $form;
}
}
~~~
现在在每个控制器的子类中,使用我们的表单,我们使用create()方法使用我们的表单子类创建一个表单实例:
~~~
protected function createComponentSignInForm()
{
$form = (new SignInFormFactory)->create();
$form['login']->caption = 'Continue'; // we can also modify our form
$form->onSuccess[] = [$this, 'signInFormSubmitted']; // and add event handlers
return $form;
}
~~~
我们也可以在一个地方处理我们提交的表格。 这很简单,只需将我们的事件处理程序移动到我们的子类,将其从signInFormSubmitted重命名为 submitted。 或者我们可以使用匿名函数作为处理程序:
~~~
use Nette\Application\UI\Form;
class SignInFormFactory
{
/**
* @return Form
*/
public function create()
{
$form = new Form;
$form->addText('name', 'Name:');
...
$form->addSubmit('login', 'Log in');
$form->onSuccess[] = function (Form $form, \stdClass $values) {
// we process our submitted form here
};
return $form;
}
}
~~~
# 提交表单
如果表单包含多个提交按钮,我们需要区分,将处理程序绑定到onSubmit事件是有用的,该事件在onSuccess之前立即调用。
~~~
$form->addSubmit('login', 'Log in')
->onClick[] = [$this, 'signInFormSubmitted'];
~~~
如果通过按Enter键提交表单,则调用第一个提交按钮。
仅当提交的值通过验证时,才调用onSuccess和onClick事件的处理程序。 您不需要在处理函数中验证输入。 该表单还具有onSubmit事件,该事件在验证时被调用。
# 默认值
有两种方法可以设置默认值。 方法setDefaults():
~~~
$form->addText('name', 'Name:');
$form->addInteger('age', 'Age:');
$form->setDefaults([
'name' => 'John',
'age' => '33'
]);
~~~
或方法setDefaultValue()对单个输入:
~~~
$form->addEmail('email', 'Email:')
->setDefaultValue('user@example.com');
~~~
选择框和单选列表的默认值使用传递的值数组的键设置:
~~~
$form->addSelect('country', 'Country', [
'cz' => 'Czech republic',
'sk' => 'Slovakia',
]);
$form['country']->setDefaultValue('sk'); // country defaults to Slovakia
~~~
另一个有用的选项是“emptyValue”。 如果表单提交后的表单字段的值与其“emptyValue”相同,则该字段的行为为未填充。
~~~
$form->addText('phone', 'Phone:')
->setEmptyValue('+42');
~~~
# 表单外观
表单的外观可能大不相同。 事实上有两个极端。 一方面是需要再一次呈现一组非常相似的表单,几乎没有改变。 通常是UI和后端。
另一边是微小的甜蜜形式,每一个都是一件艺术品。 他们的布局最好用HTML编写。 当然,除了这些极端之外,还有许多形式。
# DefaultFormRenderer
渲染器自动渲染表单。 它在表单上设置了setRenderer()方法,当调用$ form-> render()或echo $ form时,它获得控制权。 如果我们没有设置自定义渲染器,则使用Nette \ Forms \ Rendering \ DefaultFormRenderer。 所以你要写的是:
~~~
echo $form;
~~~
所有输入字段都呈现在HTML表中。 输出可能如下所示:
![](https://box.kancloud.cn/f46a5ae7c525a90ffa11d65c9764a04e_676x318.png)
很好格式化,不是吗? :-)
这取决于你,不管是否使用表,许多网页设计师喜欢不同的标记,例如列表。 我们可以配置DefaultFormRenderer,以便它不会渲染到表中。 我们只需要设置合适的$ wrapper。 第一个索引总是表示一个区域,第二个索引是它的元素。 所有相应的区域如图所示:
![](https://box.kancloud.cn/0a87b5eb4227d397328f0e2e31d12b60_412x366.png)
~~~
默认情况下,一组控件被包裹在<table>中,每对都是一个包含一对标签和控件(表格<th>和<td>)的表行<tr>。 让我们更改所有的包装元素。 我们将包装控件到<dl>中,保留对本身,将标签放入<dt>并将控件包装到<dd>:
~~~
~~~
$renderer = $form->getRenderer();
$renderer->wrappers['controls']['container'] = 'dl';
$renderer->wrappers['pair']['container'] = NULL;
$renderer->wrappers['label']['container'] = 'dt';
$renderer->wrappers['control']['container'] = 'dd';
echo $form;
~~~
将结果转换为以下代码段:
~~~
<dl>
<dt><label class="required" for="frm-name">Name:</label></dt>
<dd><input type="text" class="text" name="name" id="frm-name" value="" /></dd>
<dt><label class="required" for="frm-age">Age:</label></dt>
<dd><input type="text" class="text" name="age" id="frm-age" value="" /></dd>
<dt><label>Gender:</label></dt>
...
</dl>
~~~
Wrappers 可以影响很多属性。
1、向每个表单输入添加特殊的CSS类
2、区分奇数行和偶数行
3、使必需和可选绘制不同
4、设置,错误消息是否显示在表单上方或靠近每个元素
[Bootstrap支持](https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58)
# 手动渲染
您可以手动呈现表单,以更好地控制生成的代码。 将表单放在{form myForm}和{/ form}对宏中。 输入可以使用渲染输入的{input myInput}宏和渲染其标签的{label myInput /}来渲染。
~~~
{form signForm}
<!-- Simple errors rendering -->
<ul class="errors" n:if="$form->hasErrors()">
<li n:foreach="$form->errors as $error">{$error}</li>
</ul>
<table>
<tr class="required">
<th>{label name /}</th>
<td>{input name}</td>
</tr>
<!--If you need manualy render radiolist -->
<p>{input radioList:itemKey} | {input radioList:itemKeyTwo}</p>
...
</table>
{/form}
~~~
您还可以通过使用n:name属性轻松地将表单与模板相连接。
~~~
function createComponentSignInForm()
{
$form = new Form;
$form->addText('user')->setRequired();
$form->addPassword('password')->setRequired();
$form->addSubmit('send');
return $form;
}
~~~
~~~
<form n:name=signInForm class=form>
<p><label n:name=user>Username: <input n:name=user size=20></label>
<p><label n:name=password>Password: <input n:name=password></label>
<p><input n:name=send class="btn btn-default">
</form>
~~~
~~~
您还可以使用n:name与<select>,<button>或<textarea>元素和内容。
~~~
您可以单独通过HTML元素呈现RadioList,复选框或CheckboxList。 这被称为局部渲染:
~~~
{foreach $form[gender]->items as $key => $label}
<label n:name="gender:$key"><input n:name="gender:$key"> {$label}</label>
{/foreach}
~~~
或者,您可以使用基本宏{input gender:$ key}和{label gender:$ key}。 诀窍是使用冒号。 对于一个简单的复选框,使用{input myCheckbox:}。
宏formContainer有助于在表单容器中呈现输入。
~~~
{form signForm}
<table>
<th>Which news you wish to receive:</th>
<td>
{formContainer emailNews}
<ul>
<li>{input sport} {label sport /}</li>
<li>{input science} {label science /}</li>
</ul>
{/formContainer}
</td>
...
</table>
{/form}
~~~
如何为HTML元素设置更多属性? 方法getControl()和getLabel()将元素作为Nette \ Utils \ Html对象返回,这可以很容易地调整。
~~~
{form signForm class => 'big'}
<table>
<tr class="required">
<th>{label name /}</th>
<td>{input name cols => 40, autofocus => TRUE}</td>
</tr>
~~~
它也可以“breathe life”到一个原始的HTML输入与n:name属性宏,它使用它的形式输入使用它的标识:
~~~
<table>
<tr class="required">
<th><label for="frm-name"></th>
<td><input cols=40 n:name="name"></td>
</tr>
~~~
# 分组输入
通过创建组,输入字段可以分组为可视字段集:
~~~
$form->addGroup('Personal data');
~~~
创建新组会激活它 - 所有进一步添加的元素都将添加到此组。 你可以建立这样的表单:
~~~
$form = new Form;
$form->addGroup('Personal data');
$form->addText('name', 'Your name:');
$form->addInteger('age', 'Your age:');
$form->addEmail('email', 'Email:');
$form->addGroup('Shipping address');
$form->addCheckbox('send', 'Ship to address');
$form->addText('street', 'Street:');
$form->addText('city', 'City:');
$form->addSelect('country', 'Country:', $countries);
~~~
# 跨站点请求伪造(CSRF)保护
Nette Framework保护您的应用程序免受跨站点请求伪造(CSRF)攻击。 攻击者在网页上诱骗受害者,该网站静静地向受害者登录的服务器执行请求。 服务器不会识别用户是否自愿发送请求。
保护很简单:
~~~
$form->addProtection('Security token has expired, please submit the form again');
~~~
生成和验证身份验证令牌可以防止此攻击。 它具有有限的到期时间:会话有效期。 感谢它不阻止在多个窗口中使用应用程序(只要它是相同的会话)。 第一个参数是显示的错误消息,如果令牌已过期。
此保护应添加到更改敏感数据的所有形式。
# 多语言表单
如果要创建多语言应用程序,则需要在许多语言突变中呈现非常相同的表单。 Nette框架表单具有内置的翻译支持。 你只需要设置一个转换器的形式,它是一个实现Nette \ Localization \ ITranslator接口的对象,只有一个方法translate()。
~~~
class MyTranslator implements Nette\Localization\ITranslator
{
/**
* Translates the given string.
* @param string message
* @param int plural count
* @return string
*/
public function translate($message, $count = NULL)
{
return ...;
}
}
$form->setTranslator($translator);
~~~
所有标签,错误消息和选择值都被透明地翻译成其他语言。
# Nette / Forms是独立的
Nette / Forms可以在没有整个框架的情况下作为独立包使用。 您可以通过Composer安装:
~~~
composer require nette/forms
~~~
该表单很容易创建如下:
~~~
use Nette\Forms\Form;
$form = new Form;
$form->addText('name', 'Name:');
$form->addPassword('password', 'Password:');
$form->addSubmit('send', 'Sign up');
echo $form; // renders the form
~~~
这种表单通过POST方法提交到同一页面。 你可以轻易改变:
~~~
$form = new Form;
$form->setAction('/submit.php');
$form->setMethod('GET');
...
~~~
我们可以通过调用$ form-> isSuccess()来确定表单是否已提交并通过验证。 如果是这样,让我们打印出数据。
~~~
if ($form->isSuccess()) {
echo 'Form was filled and submitted successfully';
$values = $form->getValues();
dump($values);
}
~~~
您可以访问表单项,如访问数组。 在我们的例子中,你可以在索引$ form ['name']上找到第一个文本输入。
建议在处理数据后重定向到其他页面。 您可以通过这种方式避免重复提交表单。
# 渲染表单
每个元素都有getLabel()和getControl()方法,它们返回标签的HTML代码和元素本身。 Nette提供getter和setter属性访问,就好像您正在访问属性本身一样。
~~~
<?php $form->render('begin') ?>
<?php $form->render('errors') ?>
<table>
<tr class="required">
<th><?php echo $form['name']->label // Calls getLabel() ?></th>
<td><?php echo $form['name']->control // Calls getControl() ?></td>
</tr>
<tr class="required">
<th><?php echo $form['age']->label ?></th>
<td><?php echo $form['age']->control ?></td>
</tr>
...
</table>
<?php $form->render('end') ?>
~~~
- Nette简介
- 快速开始
- 入门
- 主页
- 显示文章详细页
- 文章评论
- 创建和编辑帖子
- 权限验证
- 程序员指南
- MVC应用程序和控制器
- URL路由
- Tracy - PHP调试器
- 调试器扩展
- 增强PHP语言
- HTTP请求和响应
- 数据库
- 数据库:ActiveRow
- 数据库和表
- Sessions
- 用户授权和权限
- 配置
- 依赖注入
- 获取依赖关系
- DI容器扩展
- 组件
- 字符串处理
- 数组处理
- HTML元素
- 使用URL
- 表单
- 验证器
- 模板
- AJAX & Snippets
- 发送电子邮件
- 图像操作
- 缓存
- 本土化
- Nette Tester - 单元测试
- 与Travis CI的持续集成
- 分页
- 自动加载
- 文件搜索:Finder
- 原子操作