2016-11-18 127 views
0

Symfony有关“自定义窗体渲染”的文档看起来模糊且容易让人误解。我试图在窗体上渲染一个自定义元素,但是当尝试这样做时,我看到了莫名其妙的行为。Symfony 3覆盖默认窗体渲染

问题:

我希望呈现一个多列选择框。功能等价物被描述准确位置: jQuery Get Selected Option From Dropdown

语境

  • 模型 - 我有一个用户实体。用户可以属于零个,一个或多个组织。
  • 控制器 - 我的控制器进行API调用,该调用获取用户和组织(如果有用户属于)以及用户可以添加的组织列表。
  • 查看 - 我正在渲染一个创建/编辑窗体Twig模板显示所有用户字段以及用户可能订阅的组织列表。有两个选择框并排。其中显示用户可以属于组织,另一种是用户可以添加可能组织名单:

<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_horizo​​ntal.html.twig模板形式呈现。为了捕捉上述功能,我需要提供一种方式来上面的HTML的一个变种添加到我的形式。

解决方案1 ​​

向Twig扩展添加一个方法,该方法可以从窗体调用并呈现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">&laquo;</button> 
     <br />&nbsp;<br /> 
     <button class="btn btn-default" id="m-select-from">&raquo;</button> 
    </div> 
    <div class="col-sm-2"> 
     <select id="$optionsId" class="form-control" multiple> 
      $rhsOptions 
     </select> 
    </div> 
</div> 
HTML; 
     return $html; 
    } 

不是很ele gant,但它会呈现所需的结果。 (我将HEREDOC html包含在我的最终结果中更精确)。

的问题是,在“form_start”枝杈方法呈现的组织除了从上述控制一个选择框 - 我已指示它做的是通过在用户的抽象类型设置“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>&nbsp;<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后的程序: How to create a custom Symfony2 Twig form template block 除了: Custom form field template with twig

我做了以下内容:

创建自定义类型从渲染自定义模板(来自Symfony文档链接):

自定义类型: 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', 

      ]) 
    . . . 
    } 
} 

在我的枝条扩展,我添加了一个参考

(通过魔术,因为在该Symfony的文档没有提到这上面链接。)类CBMSHelperExtension扩展\ {Twig_Extension

public function getFunctions() { 
    return array(
     new \Twig_SimpleFunction('multiselect_widget', null, [ 
      'node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 
      'is_safe' => [ 'html' ]]) 
    ); 
} 

Aaaaand Finaly,我几乎完全自定义模板,复制 - 按照指示 - 从现有的树枝模板:

资源/视图/形式/ 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  | 

这是试图来呈现它自己的组织,即使我在我的权力做的一切,告诉它不要。

这是从实际应用证明该截图: enter image description here

问题

我如何能实现自定义表单呈现以呈现从HTML自定义表单模板下面的Symfony的“最佳实践“没有埋头苦干,或创建一个不可扩展的解决方案?

红利问题: 仅仅是我还是在Symfony中开发像试图构建Rube Goldberg设备?

+0

此链接可能会对您有所帮助:http://symfony.com/doc/current/bundles/SensioGeneratorBundle/index.html#overriding-skeleton-templates –

回答

0

我无法回答额外内容呈现的原因。从基础枝条模板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_rowform_errors块)。

那么为什么不扩展解决方案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装置。这更像是建造一架飞机。 。 。而它在空中。