2016-11-18 4 views
0

Die Dokumentation von Symfony über "benutzerdefinierte Formular-Rendering" scheint vage und irreführend. Ich versuche, ein benutzerdefiniertes Element in einem Formular zu rendern, aber ich sehe unerklärliche Verhaltensweisen, wenn ich das versuche.Symfony 3 überschreiben Standard-Formular-Rendering

Das Problem:

Ich möchte eine mehrspaltige Auswahlfeld machen. Ein funktionales Äquivalent ist genau hier beschrieben: jQuery Get Selected Option From Dropdown

Kontext

  • Model - Ich habe einen Benutzer Entity. Ein Benutzer kann zu keiner, einer oder mehreren Organisationen gehören.
  • Controller - Mein Controller führt einen API-Aufruf aus, der einen Benutzer und die Organisationen abruft, sofern einer dieser Benutzer gehört, und eine Liste der Organisationen, die der Benutzer hinzufügen kann.
  • Ansicht - Ich rendere ein Formular zum Erstellen/Bearbeiten Twig Vorlage zeigt alle Benutzerfelder sowie eine Liste der Organisationen, die der Benutzer abonnieren kann. Es gibt zwei Auswahlfelder nebeneinander. Man zeigt die Organisationen ein Benutzer gehören kann, die andere ist die Liste der möglichen Organisationen ist ein Benutzer hinzufügen können:

<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>

Ich bin mit dem „bootstrap3_form_horizontal.html.twig Vorlage für Formular-Rendering. um die oben genannte Funktionalität zu erfassen, muss ich einen Weg, um eine Variante des oben html zu meiner Form hinzuzufügen.

Lösung 1

Fügen Sie eine Methode zu einer Zweig-Erweiterung hinzu, die über das Formular aufgerufen werden kann und den HTML-Code rendern kann.

  • (positiv) einfachste und effektivste
  • (negativ) Hat Zweig nicht nutzen. Ducktypen benötigt Formwerte.
  • (negativ) Nicht sehr trocken.

für alle Lösungen, ich habe eine Abstract Bereitstellung Kontrollen für Formular-Rendering erstellt:

//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 %} 

"Multi wählen Sie" ist ein Brauch Zweig Erweiterung:

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; 
    } 

Nicht sehr ele gant, aber es bringt das gewünschte Ergebnis. (Ich habe die HEREDOC html genauer gesagt in Bezug auf mein Endergebnis).

Das Problem ist, die „form_start“ Twig Methode auch von oben auf die Steuerung eine Auswahlbox von Organisationen zusätzlich macht - ich habe es gerichtet, das zu tun, indem Sie den „Choice“ im Benutzerhandbuch abstrakten Typ eingestellt werden.

Die Form grob ähnelt: (Die entsprechenden Teile durch die Kommentare eingeklammert werden)

<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>

Wie Sie sehen können, gibt es zwei verschiedene Dinge geht. Ein generisches, "out-of-the-box" -Auswahl-Tag und mein benutzerdefiniertes.

Die form_start -Methode scheint durch die untergeordneten Elemente des UserType zu iterieren und ihre Steuerelemente zusätzlich zu der angegebenen zu rendern.

Lösung 2

eine eigene Vorlage erstellen Rendering der Form zu steuern.

  • (positiv) Gefolgt von Best Practices dokumentiert mein Ergebnis für das Erreichen
  • (negativ) nicht vollständig dokumentiert. Funktioniert nicht.

den Verfahren folgend in http://symfony.com/doc/current/form/form_customization.html skizziert und im SO Post geklärt: How to create a custom Symfony2 Twig form template block Neben: Custom form field template with twig

Ich habe folgendes:

einen benutzerdefinierten Typ Geschaffen von einer zu erbringenden Benutzerdefinierte Vorlage (aus dem Symfony-Dokument-Link):

Benutzerdefiniert: 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; 
    } 
} 

Ich habe buchstäblich keine Ahnung, was ich hier angeben soll. Welche Methoden überschreibe ich und wie? Die Symfony-Dokumentation fordert Sie auf, sich eine ähnliche Instanziierung anzusehen. Die Choices Type-Klasse ist 800 Zeilen lang! Benötige ich mehr als 800 Zeilen PHP, um 20 HTML zu rendern ?!

I Referenz es in meinem Usertype thusly:

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', 

      ]) 
    . . . 
    } 
} 

In meinem Zweig Erweiterung, füge ich eine Referenz

(durch Magie, da es keinen Hinweis in der oben verlinkten Symfony doc ist.) Klasse CBMSHelperExtension erstreckt \ Twig_Extension {

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

uuuuund Finaly, My nahezu vollständige benutzerdefinierte Vorlage kopiert - wie angewiesen - aus bestehendem Zweig Vorlagen:

Resources/Ansichten/form/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 %} 

Das Problem ist jetzt (wie oben config.yml hinzugefügt wurde) ähnlich oben auf den einen.

Es gibt ein zusätzliches "Organisationen" -Label, das gerendert wird !! Ich werde Ihnen die HTML-Dump verschonen, aber im Grunde, was es tut, ist dies:

|| form tag || 
| form group:   | 
| label - email   | input box for email | 
| form group:   | 
| label - organizations | form group   | 
|      | label - organizations | 
|      | custom template  | 

Es ist versucht die Organisationen auf seine eigenen zu machen, obwohl ich alles in meiner Macht mache ihm zu sagen, nicht zu tun.

Hier ist ein Screenshot von der tatsächlichen Anwendung demonstriert dies: enter image description here

Die Frage

Wie kann ich benutzerdefinierte Formular-Rendering implementieren Best Practices zu machen entweder eine benutzerdefinierte Formularvorlage von HTML folgenden symfonys“ "ohne Kludging oder eine nicht skalierbare Lösung zu erstellen?

BONUS FRAGEN: Ist es nur ich oder in Symfony entwickelt wie der Versuch, ein Rube Goldberg-Gerät zu bauen?

+0

Dieser Link kann Ihnen helfen: http://symfony.com/doc/current/bundles/SensioGeneratorBundle/index.html#overriding-skeleton-templates –

Antwort

0

Ich kann die Gründe nicht beantworten, warum Extrainhalt gerendert wird. Von der Basis Zweig Vorlage form_div_layout.html.twig, gibt es nichts, dass das Verhalten zeigt, was geschieht:

{%- 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 -%} 

Etwas in der Wiedergabe immer verwirrt. Durch Entfernen dieses Blocks werden jedoch nur bestimmte Formularelemente gerendert (alle form_row und form_errors Blöcke).

Also, warum nicht Lösung 1 erweitern und Überschreibungen zu den Endmethoden form_start und form hinzufügen?

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; 
} 

}

Dadurch werden die restlichen Elemente erzeugen benötigt, um Ihre Form einzureichen. Sollte nicht anders sein als was sollte von Symfony generiert worden sein.

Dies füllt möglicherweise nicht alle Checkboxen für Skalierbarkeit und Best Practices usw., aber es bringt Daten zurück zum Controller.

BONUS FRAGE: Nein, das Entwickeln von Symfony ist nicht wie das Erstellen einer Rube Goldberg-Vorrichtung. Es ist mehr wie ein Flugzeug zu bauen. . . während es in der Luft liegt.