Symfony关于“自定义表单渲染”的留档似乎含糊不清,具有误导性。我试图在窗体上呈现自定义元素,但当我试图这样做时,我看到了令人费解的行为。
问题:
我希望呈现多列选择框。这里准确地描述了一个功能等价物:jQuery从下拉列表中获取选定选项
上下文
<select "user_orgs">
<option value="1">Organization 1</option>
</select>
<button id="add"> << </button>
<button id="remove"> << </button>
<select "available_orgs">
<option value="1">Organization 1</option>
<option value="2">Organization 1</option>
</select>
我正在使用“bootstrap3_form_horizontal.html.twig模板进行表单渲染。为了捕获上述功能,我需要提供一种方法将上述html的变体添加到我的表单中。
解决方案1
将方法添加到可从表单调用的细枝扩展,并呈现html。
对于所有解决方案,我创建了一个AbstractType,为表单呈现提供控件:
//Predefined form types:
use Symfony\Component\Form\Extension\Core\Type;
class UserType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add( 'email', Type\EmailType::class, [
'label' => 'Email / Username',
])
. . .
->add('organizations', Type\ChoiceType::class, [
'label' => 'Organizations',
'selections' => $options['organizations'],
'multiple' => true,
])
->add('submit', Type\SubmitType::class, [
'attr' => ['class' => 'btn-success btn-outline'],
]);
}
}
user_create.html.twig:
{% form_theme form _self %}
{% block body %}
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_row(form.email) }}
{{ multi_select(form.organizations, selected_organizations)|raw }}
{{ form_end(form) }}
{% endblock %}
“多选”是一个自定义细枝扩展:
class CustomTwigExtension extends \Twig_Extension {
public function getFunctions() {
return array(
new \Twig_SimpleFunction('multi_select', [$this, 'multiSelect']),
}
public function multiSelect(\Symfony\Component\Form\FormView $formView, $selections) {
$formData = $formView->vars;
$formLabel = $formData['label'];
$selectName = $formData['full_name'];
$formId = $formData['id'];
$optionsId = $formId . '_options';
$rhsOptions = "";
$lhsOptions = "";
foreach($selections as $key => $value) {
$lhsOptions .= '<option value="' . $value . ' selected">' . $key . '</option>';
}
foreach($formData['choices'] as $option) {
$rhsOptions .= '<option value="' . $option->value . ' selected">' . $option->label . '</option>';
}
$html = <<<HTML
<div class="form-group">
<label class="col-sm-2 control-label required" for="$formId">$formLabel</label>
<div class="col-sm-2">
<select id="$formId" class="form-control" name="$selectName" multiple>
$lhsOptions
</select>
</div>
<div class="col-sm-1" style="width: 4%;">
<button class="btn btn-default" id="m-select-to">«</button>
<br /> <br />
<button class="btn btn-default" id="m-select-from">»</button>
</div>
<div class="col-sm-2">
<select id="$optionsId" class="form-control" multiple>
$rhsOptions
</select>
</div>
</div>
HTML;
return $html;
}
不是很优雅,但它呈现出想要的效果。(为了更准确地说明我的最终结果,我加入了herdeochtml)。
问题是,“form_start”Twig方法除了上面的控件外,还呈现一个组织选择框——我已经指示它通过在用户的抽象类型中设置“ChoiceType”来实现这一点。
形式大致类似:(相关部分用注释括起来)
<form name="user" method="post" action="/admin/users/save/" class="form-horizontal">
<!-- THIS IS THE CUSTOM GENERATED CONTROL -->
<div class="form-group">
<label class="col-sm-2 control-label required" for="user_organizations">Organizations</label>
<div class="col-sm-2">
<select id="user_organizations" class="form-control" name="user[organizations][]" multiple="">
</select>
</div>
<!-- END CUSTOM GENERATED CONTROL -->
<div class="col-sm-1" style="width: 4%;">
<button class="btn btn-default" id="m-select-to">«</button>
<br> <br>
<button class="btn btn-default" id="m-select-from">»</button>
</div>
<div class="col-sm-2">
<select id="user_organizations_options" class="form-control" multiple="">
<option value="1 selected">Organization1</option>
<option value="2 selected">Organization2</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label required" for="user_email">Email / Username</label>
<div class="col-sm-10">
<input id="user_email" name="user[email]" required="required" class="form-control" type="email">
</div>
<!-- THIS IS THE SYMFONY GENERATED CONTROL -->
<div class="form-group">
<label class="col-sm-2 control-label required" for="user_organizations">Organizations</label>
<div class="col-sm-10">
<select id="user_organizations" name="user[organizations][]" class="form-control" multiple="multiple">
<option value="1 selected">Organization1</option>
<option value="2 selected">Organization2</option>
</select>
</div>
</div>
<!-- END SYMFONY GENERATED CONTROL -->
</div><input id="user__token" name="user[_token]" value="imatoken" type="hidden"></form>
正如你所看到的,有两种不同的情况正在发生。一个通用的“开箱即用”选择标记和我的自定义渲染标记。
form_start方法似乎在迭代UserType的子级,并在我指定的控件之外呈现它们的控件。
解决方案2
创建自定义模板以控制表单的呈现。
遵循中概述的程序http://symfony.com/doc/current/form/form_customization.html并在SO post中阐明:如何创建自定义Symfony2细枝表单模板块以及:带有细枝的自定义表单字段模板
我做了以下工作:
创建要从自定义模板渲染的自定义类型(从Symfony docs链接):
自定义类型:AppBundle\Form\MultiSelectType:
class MultiSelectType extends AbstractType {
public function buildView(FormView $view, FormInterface $form, array $options) {
$view->vars['selections'] = $this->setOptions($options['selections']);
//The following lines will assign the select box containing
//already defined organizations.
$entity = $view->parent->vars['value'];
$field = $options['entity_field_name'];
$method = 'get' . $field;
$values = call_user_func(array($entity, $method));
$view->vars['choices'] = $values;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'selections' => array(),
'custom_label' => 'slapping juice'
));
}
private function setOptions($options) {
$ar = [];
foreach($options as $k => $v) {
$ar[] = ['value' => $v, 'label' => $k];
}
return $ar;
}
}
我真的不知道该在这里具体说明什么。我应该覆盖哪些方法以及如何覆盖?Symfony文档敦促您查看类似的实例化。选择类型类有800行长!我需要800行PHP来呈现20行HTML吗?!
我在我的用户类型中引用它,因此:
class UserType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
. . .
$builder->add('organizations', MultiSelectType::class, [
'label' => 'Organizations',
'selections' => $options['organizations'],
'entity_field_name' => 'organizations',
])
. . .
}
}
在我的Twig扩展中,我添加了一个引用(由于在上面链接的Symfony文档中没有提到这一点,所以使用了魔法)
类CBMShellPerExtension扩展\Twig_扩展{
public function getFunctions() {
return array(
new \Twig_SimpleFunction('multiselect_widget', null, [
'node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode',
'is_safe' => [ 'html' ]])
);
}
aaaa最后,我的几乎完整的自定义模板,按照指示从现有的细枝模板复制:
资源/视图/表格/fields.html.twig
{% use "form_div_layout.html.twig" %}
{% use "bootstrap_3_horizontal_layout.html.twig" %}
{% block multi_select_widget %}
{% spaceless %}
<div class="form-group">
{{ form_label(form) }}
<div class="{{ block('form_group_class') }}">
<select {{ block('widget_attributes') }} multiple="multiple">
{%- set options = selections -%}
{% for choice in options %}
<option value="{{ choice.value }}" selected="selected">{{ choice.label }}</option>
{% endfor %}
</select>
</div>
<div class="{{ block('form_group_class') }}">
{% for organization in choices %}
<p>{{ organization.name }}</p>
{% endfor %}
</div>
</div>
{% endspaceless %}
{% endblock %}
(以上内容已添加到config.yml中)
现在的问题与上面的问题类似。
正在呈现一个额外的“组织”标签!!我将省略你的html转储,但基本上它所做的是:
|| form tag ||
| form group: |
| label - email | input box for email |
| form group: |
| label - organizations | form group |
| | label - organizations |
| | custom template |
它试图让组织独立运作,尽管我尽我所能告诉它不要这样做。
问题
我如何实现自定义表单呈现,以便按照Symfony的“最佳实践”从HTML呈现自定义表单模板而不必纠结,或者创建不可伸缩的解决方案?
额外问题:是只有我还是在Symfony中开发,就像试图构建Rube Goldberg设备一样?
我不能回答为什么要呈现额外的内容。从基本Twig模板form_div_layout.html.twig
中,没有任何内容指示正在发生的行为:
{%- block form_start -%}
{% set method = method|upper %}
{%- if method in ["GET", "POST"] -%}
{% set form_method = method %}
{%- else -%}
{% set form_method = "POST" %}
{%- endif -%}
<form name="{{ name }}" method="{{ form_method|lower }}"{% if action != '' %} action="{{ action }}"{% endif %}{% for attrname, attrvalue in attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}{% if multipart %} enctype="multipart/form-data"{% endif %}>
{%- if form_method != method -%}
<input type="hidden" name="_method" value="{{ method }}" />
{%- endif -%}
{%- endblock form_start -%}
渲染过程中出现了一些混乱。但是,通过删除此块,只会呈现指定的表单元素(所有form\u行
和form\u错误
块)。
那么,为什么不扩展解决方案1并在form_start中添加重写并形成结束方法呢?
class MyTwigExtension extends \Twig_Extension {
public function getFunctions() {
return array(
new \Twig_SimpleFunction('custom_form_start', [$this, 'customFormStart']),
new \Twig_SimpleFunction('custom_form_end', [$this, 'customFormEnd']),
new \Twig_SimpleFunction('multi_select', [$this, 'multiSelect']),
new \Twig_SimpleFunction('multi_select_js', [$this, 'multiSelectJS'])
);
}
. . .
public function customFormStart(\Symfony\Component\Form\FormView $formView) {
$formData = $formView->vars;
$html = '<form name="' . $formData['name'] . '" method="' . $formData['method'] . '"'
. ' action="' . $formData['action'] . '" class="form-horizontal">';
return $html;
}
public function customFormEnd(\Symfony\Component\Form\FormView $formView) {
$formData = $formView->vars;
$token = $formView->children['_token']->vars;
$html = '<input id="' . $token['id'] .'" name="' . $token['full_name']
. '" value="' . $token['value'] . '" type="hidden">';
return $html;
}
}
这将生成提交表单所需的其余元素。应该与Symfony生成的内容相同。
这可能不会填满可伸缩性和最佳实践等的所有复选框,但它确实会将数据返回到控制器。
附加问题:不,开发Symfony不像创造Rube Goldberg装置。这更像是在空中建造一架飞机 . . . 。