simplon/form

数据验证和灵活的表单渲染,包括语义界面结构。


README

     _                 _                __                      
 ___(_)_ __ ___  _ __ | | ___  _ __    / _| ___  _ __ _ __ ___  
/ __| | '_ ` _ \| '_ \| |/ _ \| '_ \  | |_ / _ \| '__| '_ ` _ \ 
\__ \ | | | | | | |_) | | (_) | | | | |  _| (_) | |  | | | | | |
|___/_|_| |_| |_| .__/|_|\___/|_| |_| |_|  \___/|_|  |_| |_| |_|
                |_|                                             

Simplon/Forms

Simplon/Forms 通过利用 Semantic-UI 库,帮助验证数据,并在需要时通过一些小部件构建表单视图。

  1. 快速示例
    1.1 字段
    1.2 验证
    1.3 视图
  2. 字段
    2.1 普通字段
    2.2 带选项的字段
    2.3 规则
    2.4 过滤器
  3. 视图
    3.1 简单示例
    3.2 区块和行
    3.3 元素
  4. 区块字段克隆
    4.1 构建字段
    4.2 构建视图
    4.3 构建模板
  5. 设置
    5.1 字段必填/可选标签
  6. 示例

1. 快速示例

1.1 字段

为了验证数据,我们需要创建至少一个字段,它可以包含任意数量的规则来定义其有效性。字段还可以包含任意数量的过滤器,这些过滤器将应用于字段值。

一些字段示例

//
// easiest setup
//

(new FormFields())->add(
	new FormField('email')
);

//
// another way with a filter
//

(new FormFields())->add(
	(new FormField('email'))->addFilter(new CaseLowerFilter()) // lower case field value
);

//
// fields can also have rules
// 

(new FormFields())->add(
	(new FormField('email')->addRule(new EmailRule()) // make sure that we get an email address
);

//
// we can also combine these things
// 

(new FormFields())->add(
	(new FormField('email')
		->addRule(new EmailRule())
		->addFilter(new CaseLowerFilter())
);

预填充字段

我们可以使用我们已有的数据来预填充我们的字段,而不是 request 数据

//
// our fields
//

$fields = (new FormFields())->add(
	(new FormField('email'))->addRule(new EmailRule())
);

//
// set our data
//

$fields->applyInitialData(
	['email' => 'foo@bar.com']
);

//
// testing ...
//

$fields->get('email')->getInitialValue(); // foo@bar.com
$fields->get('email')->getValue(); // foo@bar.com

//
// these values change if we enter for instance
// "hello@bar.com" in our form and hit submit ...
//

$fields->get('email')->getInitialValue(); // foo@bar.com
$fields->get('email')->getValue(); // hello@bar.com

1.2 验证

FormValidation 预期至少一组 FormFields。让我们以上面的例子为基础,添加一个额外的 name 字段。我们为我们的 request data 设置一个空数组。只要它的组织形式是一个数组,你可以从任何来源获取值。

//
// request data
//

$requestData = [
	'name' => 'Johnny',
	'email' => '',
];

//
// define fields
//

$fields = new FormFields();

$fields->add(
	new FormField('name')
);

$fields->add(
	(new FormField('email'))->addRule(new EmailRule())
);

//
// validation
//

$validator = new FormValidator($requestData);

if($validator->hasBeenSubmitted()) // any request data?
{
    if($validator->validate()->isValid())
    {
	    // all validated field data as array
    	var_dump($fields->getAllData());
    }
    else
    {
	    // array of error messages
    	var_dump($validator->getErrorMessages());

		// OR ...
		
    	// array of error fields    	
    	var_dump($validator->getErrorFields());
    }
}

FormValidator::hasBeenSubmitted 检查我们接收到的数据是否为空或已填写。如果我们收到数据,我们可以通过 FormValidator::validateFormValidator::isValid 运行所有应用规则,以检查所有字段是否通过了其要求。

如果成功,我们可以通过 FormFields::getAllData 收集所有字段值。否则,您可以通过通过 FormValidator::getErrorMessages 收集错误消息或通过收集所有错误字段 FormValidator::getErrorFields 来检查错误。后者还包含每个字段的错误消息。

1.3 视图

为了渲染您的字段,我们需要将它们应用到 Elements 上。这些元素可以直接应用到 FormView 上,或者应用到 FormBlock 上,它将我们的表单自动渲染为一种固定的结构。我们将继续使用之前定义的字段

//
// define fields
//

$nameElement = (new InputTextElement($fields->get('name')))
	->setLabel('Email address')
	->setDescription('Required in order to send you a confirmation');

$emailElement = (new InputTextElement($fields->get('email')))
	->setLabel('Email address')
	->setDescription('Required in order to send you a confirmation');

//
// apply to a block w/ one row
//

$block = (new FormViewBlock('default')) // set block ID to default
    ->addRow(
	    (new FormViewRow())
	    	->autoColumns($nameElement)
	    	->autoColumns($emailElement)
    )
;

//
// set view
//

$formView = (new FormView())->addBlock($block);

我们可以设置更多的选项,但暂时就到这里。让我们将我们的视图传递给一个模板。对于以下示例,我们假设我们有访问 $formView 变量的权限。我们简单地将表单模板传递给 FormView::render 方法,并包含所有 核心资源

<?php
/**
 * @var FormView $formView
 */
use Simplon\Form\View\FormView;

?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">

    <title>simplon/form</title>

    <style type="text/css">
        body {
            padding: 0;
            background: #fff;
        }
    </style>
</head>
<body>
    <div class="ui container">
        <?= $formView->render(__DIR__ . '/form.phtml') ?>
    </div>

    <?= $formView->renderAssets() ?>
</body>
</html>

我们将主模板与实际表单模板分开,以便能够在模板的开始和结束处自动渲染所需的<form></form>标签以及所有必需的属性。同时,我们也会自动将FormView实例注入为$formView变量。

<?php
/**
 * @var FormView $formView
 */
use Simplon\Form\View\FormView;

?>

<?php if ($formView->hasErrors()): ?>
    <div class="ui basic segment">
        <?= $formView->renderErrorMessage() ?>
    </div>
<?php endif ?>

<div class="ui basic segment">
    <?= $formView->getBlock('default')->renderBlock() ?>
</div>

<div class="ui basic segment">
    <?= $formView->getSubmitElement()->renderElement() ?>
</div>

任何验证错误将显示在模板顶部。之后是定义好的ID为default的块。此语句将使您的两个字段并排显示,间距相等。最后,您可以放置您的提交元素,在我们的例子中,这是一个自动设置的元素。您也可以自己定义这个字段。

2. 字段

字段是您数据的抽象,设计上非常通用。目标是字段能够与传入的API请求以及常规HTML表单一起工作。

2.1. 通用字段

大多数字段只需要一条规则和一个过滤器。这些通常是RequiredRuleTrimFilter。后者默认已有。您绝对需要的是一个field-id,它需要在构造函数中传递。

$field = (new FormField('name'))->addRule(new RequiredRule()) // required field with ID "name"

2.2. 带有选项的字段

您可能有一些只能接受一组特定值的字段。在这种情况下,您可以将options作为元数据添加到字段中。经典示例是一个地址字段或国家选择。

$options = (new OptionsMeta())
	->add('DE', 'Germany')
	->add('FR', 'France')
	->add('US', 'United States')
	;
	
$field = (new FormField('shipping-to'))
	->addMeta($options)
	->addRule(new RequiredRule())
	;

2.3. 规则

我们使用了一些常见的规则来满足所有的表单需求。您可以在下面找到它们。然而,如果您需要更多的规则,添加/扩展规则非常简单。只需查看现有的规则之一,了解如何操作。

RequiredRule

标记一个字段为必填。

(new FormField('name'))->addRule(new RequiredRule())

UrlRule

确保字段值符合URL格式。

(new FormField('website'))->addRule(new UrlRule())

//
// set protocol if missing
//

(new FormField('website'))->addRule(new UrlRule('http'))

//
// allow only urls which hold foo.com
//

(new FormField('website'))->addRule(
    (new UrlRule())->setAdditionalRegex('/foo.com/')
)

EmailRule

确保字段值符合电子邮件格式。

(new FormField('email'))->addRule(new EmailRule())

ExactLengthRule

确保字段值符合特定长度。

(new FormField('photos'))->addRule(new ExactLengthRule(5))

MaxLengthRule

确保字段值符合最大长度。

(new FormField('photos'))->addRule(new MaxLengthRule(5))

MinLengthRule

确保字段值符合最小长度。

(new FormField('photos'))->addRule(new MinLengthRule(1))

CallbackRule

有时您需要对数据库进行检查或与其他字段进行交叉引用。在这种情况下,这个规则非常有用。它允许您处理验证,并只需返回一个布尔值来决定值是否有效。我们用它来确保在接收新用户注册之前,提供的电子邮件地址是唯一的。

第一个参数是callback,第二个参数是一个可选的错误信息

$callback = function(FormField $field)
{
	$model = $this->db->read(['email' => $field->getValue()]);

	return $model === null;
};

(new FormField('email'))->addRule(new CallbackRule($callback, 'Email address exists already'))

IfFilledRule

有时我们有可选字段,它们只有在具有值时才应针对一组规则进行验证。为此,我们有这个规则。

(new FormField('email'))->addRule(new IfFilledRule([new EmailRule()])

FieldDependencyRule

此规则允许您向另一个字段添加规则。想象一下,您的实际字段是可选的,并且只有在填充的情况下,您才想要验证其他字段。

$email = new FormField('email');

$depRule = new FieldDependencyRule($email, [new EmailRule()]);

(new FormField('newsletter'))->addRule(new IfFilledRule([$depRule]))

WithinOptionsRule

Semantic-UI的下拉字段将选择保存到隐藏字段中。对于多选字段,所选值将用逗号分隔在该隐藏字段中。为了确保提交的值仍然符合您给出的选项,我们可以使用WithinOptionsRule

$options = (new OptionsMeta())
	->add('DE', 'Germany')
	->add('FR', 'France')
	->add('US', 'United States')
	;
	
$field = (new FormField('shipping-to'))
	->addMeta($options)
	->addRule(new RequiredRule())
	->addRule(new WithinOptionsRule())
	;

2.4. 过滤器

过滤器会运行在您的提交字段值上。例如,为了确保文本字段不包含任何空白字符,您可以在字段中添加TrimFilter,simplon\form将确保在进一步处理之前清除字段值。以下是可用的过滤器列表,但就像规则一样,您也可以添加自己的过滤器。只需查看一个过滤器。最后,过滤器是可组合的。

CaseLowFilter

将字段值转换为全小写。

(new FormField('email'))->addFilter(new CaseLowFilter()); // Foo@BAR.com --> foo@bar.com

CaseTitleFilter

将字段值中的每个单词的首字母大写。

(new FormField('name'))->addFilter(new CaseTitleFilter()); // foo bar --> Foo Bar

CaseUpperFilter

将整个字段的值转换为大写。

(new FormField('name'))->addFilter(new CaseUpperFilter()); // foo bar --> FOO BAR

TrimFilter

每个字段默认都包含这个过滤器。 从字段值的开始和结束处去除空白(或其他字符)。

(new FormField('emai'))->addFilter(new TrimFilter()); // " foo@bar.com " --> "foo@bar.com"

您可以通过通过过滤器构造函数传递它们来覆盖默认的修剪字符

(new FormField('emai'))->addFilter(new TrimFilter("$")); // "$foo@bar.com$" --> "foo@bar.com"

而不是覆盖默认的修剪字符,您可以直接添加字符

(new FormField('emai'))->addFilter(
	(new TrimFilter())->addChars("$")
);

// " foo@bar.com$" --> "foo@bar.com"

TrimLeftFilter

TrimFilter相同,但仅针对字段值的左侧。

(new FormField('emai'))->addFilter(new TrimLeftFilter()); // " foo@bar.com" --> "foo@bar.com"

TrimRightFilter

TrimFilter相同,但仅针对字段值的右侧。

(new FormField('emai'))->addFilter(new TrimRightFilter()); // "foo@bar.com " --> "foo@bar.com"

XssFilter

使用此过滤器来避免XSS。该过滤器将尝试捕获并删除字段值中出现的所有与HTML相关的元素。

(new FormField('comment'))->addFilter(new XssFilter());

// "A comment <script>...</script>" --> "A comment"

3. 视图

视图可以帮助您以结构化的方式收集字段并将它们渲染到表单中。

3.1. 简单示例

对于这个示例,我们希望创建一个用于输入电子邮件地址的表单。请记住,HTML结构是基于Semantic-UI的网格和小部件构建的。

代码

//
// fields
//

$emailId = 'email';

$fields = (new FormFields())->add(
	(new FormField($emailId))->addRule(new EmailRule())
);

//
// validation
//

$validator = (new FormValidator($_POST))->addFields($fields)->validate();

if ($validator->hasBeenSubmitted())
{
	// do something when form is OK
}

//
// build view
//

$view = (new FormView())->addElement(
	(new InputTextElement($fields->get($emailId)))->setLabel('Email address')
);

//
// render view
// https://github.com/fightbulc/simplon_phtml
// 

echo (new Phtml())->render('page.phtml', ['formView' => $view]);

页面模板

/**
 * @var FormView $formView
 */
use Simplon\Form\View\FormView;

?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">

    <title>simplon/form</title>

    <link href="/assets/vendor/semantic-ui/2.2.x/semantic.min.css" rel="stylesheet">
    <link href="/assets/vendor/simplon-form/base.min.css" rel="stylesheet">
</head>
<body>
    <div class="ui container">
        <?= $formView->render('form.phtml') ?>
    </div>

    <script src="/assets/vendor/jquery/3.2.x/jquery.min.js"></script>
    <script src="/assets/vendor/semantic-ui/2.2.x/semantic.min.js"></script>
    <script src="/assets/vendor/simplon-form/base.min.js"></script>

    <?= $formView->renderAssets() ?>
</body>
</html>

表单模板

/**
 * @var FormView $formView
 */
use Simplon\Form\View\FormView;

?>

<?php if ($formView->hasErrors()): ?>
    <div class="ui basic segment">
        <?= $formView->renderErrorMessage() ?>
    </div>
<?php endif ?>

<div class="ui basic segment">
    <?= $formView->getElement('email')->renderElement() ?>
</div>

<div class="ui basic segment">
    <?= $formView->getSubmitElement()->renderElement() ?>
</div>

3.2. 块 & 行

在前一个示例中,我们通过直接将电子邮件元素添加到视图中来构建我们的视图。块和行帮助我们轻松地自动排列元素。

块直接添加到您的视图中,并持有在模板中稍后引用它们所需的唯一ID。您可以添加尽可能多的块。

$block = new FormViewBlock('foo');

(new FormView())->addBlock($block);

行添加到您的块中,并持有您的元素。行在列中结构化您的元素。您的块没有行限制。让我们继续块示例

$block = new FormViewBlock('foo');

$someElement = ... ; // some element

$block->addRow(
	(new FormViewRow())->autoColumns($someElement) // takes up all columns
);

(new FormView())->addBlock($block);

您可以看到我们正在使用autoColumns()来设置我们的元素。这意味着该元素将占据该行可用的空间。例如,如果我们为该行设置了第二个元素,则两个元素都将占据行宽度的50%。

// before

// all auto columns

$block->addRow(
	(new FormViewRow())
		->autoColumns($someElement) // takes up half of all columns
		->autoColumns($someOtherElement) // takes up half of all columns
);

// after

也可以为每个元素分别设置特定宽度,以将auto-widthspecific-width结合使用。

// before

// two rows with mixed width specifications

$block
	->addRow(
		(new FormViewRow())
			->threeColumns($someElement) // takes up 3 columns
			->autoColumns($someOtherElement) // takes everything what is left (13 columns)
	)
	->addRow(
		(new FormViewRow())
			->tenColumns($someElement) // takes up 10 columns
			->sixColumns($someOtherElement) // takes up 6 columns
	)
;

// after

3.3. 元素

大多数元素都需要一个FormField才能构建。以下元素随simplon\form版本一起提供。您始终可以构建自己的元素,这些元素应继承抽象的Element类。

InputTextElement

这构建了一个单行文本字段。

$element = new InputTextElement(
	new FormField('name')
);

$element
	->setLabel('Your name')
	->setPlaceholder('Enter your name ...')
	->setDescription('Name is needed so that we can address your properly')
	;
	
// attach to FormView ...

InputPasswordElement

这构建了一个单行密码字段。此字段继承自InputTextElement

$element = new InputPasswordElement(
	new FormField('password')
);

$element
	->setLabel('Your password')
	->setPlaceholder('Enter your password ...')
	->setDescription('Needed so that you can login')
	;

// attach to FormView ...

InputHiddenElement

这构建了一个隐藏字段。此字段继承自InputTextElement

$element = new InputHiddenElement(
	new FormField('counter')
);

// attach to FormView ...

TextareaElement

这构建了一个多行文本字段。

$element = new TextareaElement(
	new FormField('comment')
);

$element
	->setLabel('Your comment')
	->setPlaceholder('Enter your comment ...')
	->setRows(10) // determines rows-height; default is 4
	;
	
// attach to FormView ...

CheckboxElement

我们使用复选框元素来处理提供一个或多个选项进行选择的字段 - 但通常只有少量选项。它至少需要一项选项。如果已选择任何选项,您将收到一个选择选项的数组。

$options = (new OptionsMeta())->add('yes', 'I herewith confirm ...');

$confirmElement = new CheckboxElement(
	(new FormField('confirm'))->addMeta($options)
);

// attach to FormView ...

// [ ] I herwith confirm ...

多值示例。我们只需要添加更多选项。请注意,为每个选项定义一个值就足够了。该值将用于标签。

$options = (new OptionsMeta())
	->add('Magazines')
	->add('Books')
	->add('Newspapers')
	;

$confirmElement = new CheckboxElement(
	(new FormField('reading'))->addMeta($options)
);

// attach to FormView ...

// [ ] Magazines
// [ ] Books
// [ ] Newspapers

RadioElement

我们使用单选按钮元素来处理提供少量选项但只需要一个选择的字段。请注意,为每个选项定义一个值就足够了。该值将用于标签。

$options = (new OptionsMeta())
	->add('Magazines')
	->add('Books')
	->add('Newspapers')
	;

$confirmElement = new RadioElement(
	(new FormField('reading'))->addMeta($options)
);

// attach to FormView ...

// ( ) Magazines
// ( ) Books
// ( ) Newspapers

SubmitElement

这构建了一个提交按钮,可以添加到您的FormView中。它不需要任何字段,但您可以设置按钮标签并添加CSS类。

$element = new SubmitElement('Save data', ['foo-class', 'bar-class']); // values are optional
	
// attach to FormView ...

DropDownElement

我们使用下拉元素来为字段提供可供选择的一个或多个选项。至少需要有一个选项。如果选择了任何选项,您将在请求数据中收到它作为一个字符串。多个选择将以逗号分隔的字符串传递。

下拉元素可以接受单选或多选选项。我们还可以动态添加新选项。此外,我们还可以对所有选项进行筛选。

$options = (new OptionsMeta())
	->add('DE', 'Germany')
	->add('FR', 'France')
	->add('US', 'United States')
	->add('...')
	->add('...')
	->add('...')
	;

$element = new DropDownElement(
	(new FormField('countries'))->addMeta($options)
);

$element
	->setLabel('City')
	->setDescription('Search for a city')
	->enableMultiple()		// allows selection of multiple options
	->enableSearchable()	// lets user search over options
	->enableAdditions()		// lets user add new options
;

// attach to FormView ...

TimeListElement

此元素渲染一个具有给定分钟间隔的时间选项的下拉列表。此字段继承自 DropDownElement

$element = new TimeListElement(
	new FormField('time')
);

$element
	->setInterval(30) 	// build time options with 30 minutes interval
	->enableNone() 		// add "none" option
	;

// attach to FormView	

// - None
// - 00:00
// - 00:30
// - 01:00
// - 01:30
// - ...
// - ...
// - ...

DateListElement

此元素渲染一个从给定起始日期开始、持续指定天数的日期选项的下拉列表。此字段继承自 DropDownElement

$element = new DateListElement(
	new FormField('date')
);

$element
	->setFormatOptionLabel('D, d.m.Y') 	// label for each option; e.g. Sat, 01.04.2017
	->setFormatOptionValue('Y-m-d') 	// value for each option; e.g. 2017-04-01
	->setStartingDate('2017-04-01') 	// set starting date; YYYY-MM-DD or unix time stamp
	->setDays(7) 						// build date options for n days from given start date
	->enableNone() 						// add "none" option
	;

// attach to FormView	

// - None
// - Sat, 01.04.2017
// - Sun, 02.04.2017
// - Mon, 03.04.2017
// - Tues, 04.04.2017
// - ...
// - ...
// - ...

DateCalendarElement

我们还可以实现一个基于Semantic-UI的日历扩展的日历,该扩展由日历扩展构建。以下是该扩展的一些外观和感觉。我们还在渲染实际日期时使用momentjs,同时尊重区域信息。

$element = new DateCalendarElement(
	new FormField('date')
);

$calendar
	->setLabel('Date')
	->dateOnly() 					// only show dates
	->setDateFormat('DD/MM/YYYY') 	// format of the selected date; e.g. 01/04/2017

// attach to FormView	

日期/时间选项:您可以看到有一个选项 dateOnly(),它限制日历只允许用户选择日期。默认情况下,用户会被要求输入日期/时间组合。相关选项包括:

  • timeOnly()
  • monthOnly()
  • yearOnly()

格式选项:setDateFormat() 旁边,还有以下格式选项: setTimeFormat()setDateTimeFormat()

定义范围:还可以有两个日历直接相关,这样用户就可以定义一个特定的日期范围。

//
// start date range
//

$startDateElement = (new DateCalendarElement(new FormField('startDate')))
	->setLabel('Start date')
	->dateOnly()
	->setDateFormat('DD/MM/YYYY')
	;

//
// end date range
//

$endDateElement = new DateCalendarElement(
	new FormField('endDate'),
	$startDateElement // pass in related DateCalendarElement instance
);

$endDateElement
	->setLabel('End date')
	->dateOnly()
	->setDateFormat('DD/MM/YYYY')
	;

DropDownApiElement

此元素允许您对定义的API发送搜索请求并显示其结果。由于处理响应取决于您,因此它可以与任何API一起使用。开箱即支持Algolia place searchSemantic-UI的API响应处理

$jsOptions = (new AlgoliaPlacesApiJs())
	->setType(AlgoliaPlacesApiJs::TYPE_CITY)
	;

$cityElement = new DropDownApiElement(new FormField('city'), $jsOptions);

$cityElement
	->enableMultiple()
	->setLabel('City')
	->setDescription('Search for a city')
	;

ImageUploadElement

此元素处理图像上传的客户端。

$imageElement = new ImageUploadElement(new FormField('urlImage'));

以下是一个展示ImageUploadElement实现示例的代码片段。

<div class="ui basic segment">
    <h3>Image</h3>
    <?= $formView->getBlock('image')->render() ?>
</div>

上传的图像将作为 dataURI 提供,因此您将决定在表单成功验证后如何处理这些数据。

4. 块字段克隆

此功能允许用户动态地克隆或删除字段块。例如,如果您需要输入一组未知的人的地址,那么您将有一组可以简单点击克隆的 core 字段,这样您就可以立即输入新的地址,直到您完成所有地址的输入。

4.1 构建字段

$requestData = $_POST;

//
// required structure to apply stored data
// each block represents a cloned field block
//

$storedData = [
    'clones' => [
        'address' => [
            'address' => 'Mr.',
            'firstname' => 'Peter',
            'lastname' => 'Foo',
            'email' => 'peter.foo@bar.com',
        ]
    ]
];

//
// define core clone fields
//

$cloneBlock = (new CloneFields('address'))
    ->add((new FormField('address'))->addMeta((new OptionsMeta())->add('Mr.')->add('Mrs.'))->addRule(new RequiredRule()))
    ->add((new FormField('firstname'))->addRule(new RequiredRule()))
    ->add((new FormField('lastname'))->addRule(new RequiredRule()))
    ->add((new FormField('email'))->addRule(new EmailRule()))
;

//
// add clone fields to our form fields
// and apply stored data if available
//

$fields = (new FormFields())
    ->add($cloneBlock)
    ->applyBuildData($storedData, $requestData)
;

$validator = (new FormValidator($requestData))->addFields($fields)->validate();

if ($validator->hasBeenSubmitted())
{
    if ($validator->isValid())
    {
        var_dump($fields->getAllData());
        
        // expected result depending on how many blocks you cloned ...
        // results are wrapped in their clone field block ID

        // [        
        //     'clones' => [
        //          'address' => [
        //              [
        //                  'address' => 'Mr.',
        //                  'firstname' => 'Peter',
        //                  'lastname' => 'Foo',
        //                  'email' => 'peter.foo@bar.com',
        //              ],
        //              [
        //                  ...
        //              ]
        //          ]
        //     ]
        // ]

    }
}

4.2 构建视图

//
// build view
// 

$build = function (FormViewBlock $viewBlock, string $token) use ($fields) {
    $addressElement = (new DropDownElement($fields->get('address', $token)))->enableMultiple()->setLabel('Address');
    $firstnameElement = (new InputTextElement($fields->get('firstname', $token)))->setLabel('First name');
    $lastnameElement = (new InputTextElement($fields->get('lastname', $token)))->setLabel('Last name');
    $emailElement = (new InputTextElement($fields->get('email', $token)))->setLabel('Email address')->setDescription('Required in order to send you a confirmation');

    return $viewBlock
        ->addRow((new FormViewRow())->autoColumns($addressElement))
        ->addRow((new FormViewRow())->autoColumns($firstnameElement)->autoColumns($lastnameElement))
        ->addRow((new FormViewRow())->autoColumns($emailElement))
        ;
};

$addressBlocks = (new CloneFormViewBlock($cloneBlock))->build($build);

$view = (new FormView())
    ->setComponentDir('../../assets/vendor')
    ->addBlocks($addressBlocks)
;

//
// render view
// https://github.com/fightbulc/simplon_phtml
//

echo (new Phtml())->render(__DIR__ . '/page.phtml', ['formView' => $view]);

4.3 构建模板

页面模板

<?php
/**
 * @var FormView $formView
 */
use Simplon\Form\View\FormView;

?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">

    <title>simplon/form</title>

    <style type="text/css">
        body {
            padding: 0;
            background: #fff;
        }
    </style>

    <?= $formView->renderAssets(FormView::ASSET_TYPE_CSS) ?>
</head>
<body>
    <div class="ui container">
        <?= $formView->render(__DIR__ . '/form.phtml') ?>
    </div>

    <?= $formView->renderAssets(FormView::ASSET_TYPE_JS) ?>
</body>
</html>

表单模板

/**
 * @var FormView $formView
 */
use Simplon\Form\View\FormView;

?>

<?php if ($formView->hasErrors()): ?>
    <div class="ui basic segment">
        <?= $formView->renderErrorMessage() ?>
    </div>
<?php endif ?>

<div class="ui basic segment">
    <h3>Default</h3>
    <?= CloneFormViewBlock::render(
            $formView->getCloneBlocks('address'),
            function(FormViewBlock $block) { return $block->render(); }
        )
    ?>
</div>

<div class="ui basic segment">
    <?= $formView->getSubmitElement()->renderElement() ?>
</div>

5. 设置

5.1 字段必填/可选标签

字段可以被标记为 requiredoptional - 根据您的偏好,可以是两种状态中的任何一种。您还将有机会覆盖默认显示的单词/字符,例如,以本地化表单。

在必填/可选之间切换

//
// mark only optional fields
//

FormView::useOptionalLabel(true);

//
// mark only required fields
//

FormView::useOptionalLabel(false); // default value

覆盖标签文本

//
// override optional text
//

FormView::setOptionalLabel('opcional'); // translated to Spanish

//
// override required text
//

FormView::setRequiredLabel('*');

6. 示例

即将推出