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">«</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;
}
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> <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:
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?
Dieser Link kann Ihnen helfen: http://symfony.com/doc/current/bundles/SensioGeneratorBundle/index.html#overriding-skeleton-templates –