FormControl Taglib

Im Entwickler-Forum können Implementierungsdetails sowie Alternativen der Umsetzung diskutiert werden. // Here, developers can discuss implementation details of features of their projects.
Antworten
Thalo
Beiträge: 244
Registriert: 10.08.2009, 16:56:52

FormControl Taglib

Beitrag von Thalo » 02.08.2017, 01:09:01

Hallo zusammen,

der Einsatz von Frontend-Frameworks wie Bootstrap erzeugt eine Menge Boilerplate-Code für z.B. Formulare:

Code: Alles auswählen

<div class="form-group">
  <label for="example" class="col-md-6 control-label">Example</label>
  <div class="col-md-6">
   <form:text name="example" class="form-control" id="example"/>
    <form:listener control="example">
      <span class="help-block">Example validator</span>
    </form:listener>
  </div>
</div>
Wie lässt sich das ganze hinsichtlich Wiederverwendbarkeit in einer Taglib kapseln ohne jedes Form-Control neu implementieren zu müssen?

Benutzeravatar
dr.e.
Administrator
Beiträge: 4533
Registriert: 04.11.2007, 16:13:53

Re: FormControl Taglib

Beitrag von dr.e. » 03.08.2017, 16:56:11

Hallo Thalo,

aus meiner Sicht gibt es zwei Möglichkeiten: entweder das Markup genau wie gepostet übernehmen und entsprechend mehr Code verwalten oder die Definition eines solchen (komplexeren) Formular-Elements in einer eigenen Formular-Komponente kapseln.

Für die Verwendung ist sicher Option 2 die einfachere, wenn du allerdings unterschiedliche Anforderungen an das Styling hast, könnte es sich wieder lohnen den Mehraufwand für jedes Control zu akzeptieren. Bei PAYBACK gehen wir beide Wege, zumeist sind die Formulare aber mit explizitem Markup (siehe dein Beispiel) aufgebaut. Den ersten Weg nutzen wir z.B. bei Felder, die hinsichtlich ihres Verhaltens (anzeigen: ja/nein, Länge, ...) über das CMS steuerbar sind.

Ich kann nicht abschätzen, wie viele Felder dein Formular/deine Formulare haben, die Implementierung eines solchen gekapselten Form-Controls könnte sich da allerdings schon relativ schnell rechnen. Das allerdings nur, wenn du wirklich identisches Markup hast.

Lass mich wissen, wenn ich dir Beispiele für die Implementierung geben soll. Hier könntest du beispielsweise auch eine Mischung aus Tag und Template implementieren um das Control zwar durch einen Tag zu repräsentieren, das genaue Aussehen dann allerdings wieder in ein Template auslagern. So wärst du sehr flexibel neue Formate zu erstellen.
Viele Grüße,
Christian

Thalo
Beiträge: 244
Registriert: 10.08.2009, 16:56:52

Re: FormControl Taglib

Beitrag von Thalo » 04.08.2017, 00:53:12

Hallo Christian,

Beispiele wären schön! :-) Im Backend der Applikation sind es sehr viele Formulare alle mit identischem Style. Basiert eure Implementierung auf dem Interface? Dann habt ihr auch die Mapper neu implementiert?

Benutzeravatar
dr.e.
Administrator
Beiträge: 4533
Registriert: 04.11.2007, 16:13:53

Re: FormControl Taglib

Beitrag von dr.e. » 04.08.2017, 09:17:02

Hallo Thalo,

ich stelle dir ein Beispiel zusammen.

Die Mapper setzen wir aktuell nicht ein, wären aber für solche Felder dann neu zu implementieren, wenn der Mapper das innen liegende Feld nicht via getFormElementBy*() finden kann (z.B. dann, wenn du das oben aufgeführte Text-Feld als lokale Variable in so einem Tag hälst statt als "echtes" Kind einhängst).
Viele Grüße,
Christian

Benutzeravatar
dr.e.
Administrator
Beiträge: 4533
Registriert: 04.11.2007, 16:13:53

Re: FormControl Taglib

Beitrag von dr.e. » 06.08.2017, 21:51:38

Hallo Thalo,

ich habe auf Basis deines Markup ein Beispiel für die Definition eines wiederverwendbaren Tags zusammengestellt.

Ein "statisches" - also diskret implementiertes Beispiel - für komplexe Strukturen findest du im APF z.B. mit dem Tag DateSelectorTag. Dieser beinhaltet 3 Text-Felder und generiert zudem entsprechendes Markup im Tag. Ein weiteres Beispiel wäre der ReCaptchaTag.

Um der komplexen und sich verändernden HTML-Struktur Rechnung zu tragen, habe ich mich auf ein dynamisches Beispiel beschränkt.

Das "dynamische" Formular-Tag erlaubt es an Hand weniger Attribute eine komplexe HTML-Struktur zu definieren. Diese kannst du (im Rahmen der Implementierung) beliebig verändern und damit dein HTML entsprechend deinen Bedürfnissen anpassen.

Formular:
Im einfachsten Fall beinhaltet dein Formular ein Feld, das über den generischen Tag gekapselt ist:

Code: Alles auswählen

<html:form ...>
   <complex:text-field label="Example" representation="test_field" name="foo" listener="Example Validator"/>
</form>
Der Tag nimmt die variablen Informationen entgegen, die pro Feld unterschiedlich sind. Der Einfachheit wegen habe ich die variablen Anteile als Tag-Attribute übergeben. Sollen diese dynamisch evaluiert werden (z.B. in einem Controller) muss die Implementierung angepasst werden.

Tag:
Der Tag läd an Hand des Attributs representation ein HTML-Template (hier: test_field.html) und initialisiert die darin vorkommenden Elemente (Label, Text-Feld, Listener) mit den definierten Werten:

Code: Alles auswählen

class ComplexTextFieldTag extends FormGroupTag {

   public function onParseTime() {

      $this->loadDesign(__NAMESPACE__ . '\templates', $this->getAttribute('representation'));

      // re-label fields
      $name = $this->getAttribute('name');

      $label = $this->getChildNode('name', 'dummy-label', FormLabelTag::class);
      $label->setAttribute('for', $name);
      $label->deleteAttribute('name'); // maybe not relevant due to white list
      $label->setContent($this->getAttribute('label'));

      $field = $this->getChildNode('name', 'dummy-field', TextFieldTag::class);
      $field->setAttribute('name', $name);
      $field->setAttribute('id', $name);

      /* @var $listener ValidationListenerTag */
      $listener = $this->getChildNode('name', 'dummy-listener', ValidationListenerTag::class);
      $listener->setAttribute('control', $name);
      $listener->setAttribute('name', $name . '-Listener');

      // fill content of listener
      $listener->setPlaceHolder('content', $this->getAttribute('listener'));

   }

   /**
    * @return ValidationListenerTag
    */
   public function &getListener() {

      $name = $this->getAttribute('name');
      return $this->getChildNode('name', $name . '-Listener', ValidationListenerTag::class);
   }

}
Die Methode getListener() habe ich zur Manipulation des Zustands für den Unit Test eingeführt.

Unit Test:
Im folgenden Unit Test kannst du sehen, wie ein einfacher Test-Fall mit und ohne Information des Listeners so wie ein komplexer Testfall aussieht:

Code: Alles auswählen

class ComplexTextFieldTagTest extends \PHPUnit_Framework_TestCase {

   public static function setUpBeforeClass() {
      Document::addTagLib(ComplexTextFieldTag::class, 'complex', 'text-field');
   }

   public function testComplexField() {
      $form = $this->getForm();

      echo $html = $form->transformForm();

      $this->assertContains('for="foo">Example</label>', $html);
      $this->assertContains('name="foo" class="form-control" id="foo"', $html);
   }

   public function testComplexFieldWithListenerNotified() {

      $form = $this->getForm();

      /* @var $field ComplexTextFieldTag */
      $field = $form->getFormElementByName('foo');
      $listener = $field->getListener();
      $listener->notify();

      echo $html = $form->transformForm();

      $this->assertContains('<span class="help-block">Example Validator</span>', $html);
   }

   public function testFormWithMultipleInstances() {

      $form = new HtmlFormTag();
      $form->setContent('<complex:text-field label="Example 1" representation="test_field" name="foo1" listener="Example 1 Validator"/>
<complex:text-field label="Example 2" representation="test_field" name="foo2" listener="Example 2 Validator"/>
<complex:text-field label="Example 3" representation="test_field" name="foo3" listener="Example 3 Validator"/>');

      $doc = new Document();
      $form->setParentObject($doc);

      $form->onParseTime();
      $form->onAfterAppend();

      /* @var $field ComplexTextFieldTag */
      $field = $form->getFormElementByName('foo2');
      $listener = $field->getListener();
      $listener->notify();

      echo PHP_EOL . PHP_EOL . 'Form w/ 3 fields: ' . PHP_EOL . PHP_EOL;

      echo $html = $form->transformForm();

      $this->assertContains('<span class="help-block">Example 2 Validator</span>', $html);

      $this->assertContains('for="foo1">Example 1</label>', $html);
      $this->assertContains('for="foo2">Example 2</label>', $html);
      $this->assertContains('for="foo3">Example 3</label>', $html);

      $this->assertContains('name="foo1" class="form-control" id="foo1', $html);
      $this->assertContains('name="foo2" class="form-control" id="foo2', $html);
      $this->assertContains('name="foo3" class="form-control" id="foo3', $html);

   }

   /**
    * @return HtmlFormTag
    */
   protected function getForm() {
      $form = new HtmlFormTag();
      $form->setContent('<complex:text-field label="Example" representation="test_field" name="foo" listener="Example Validator"/>');

      $doc = new Document();
      $form->setParentObject($doc);

      $form->onParseTime();
      $form->onAfterAppend();

      return $form;
   }

}
Template:
Das Formular-Element-Template ist nach deinem Beispiel oben aufgebaut und wurde in einigen Punkten leicht modifiziert um die Initialisierung durch den Tag zu ermöglichen (z.B. feste Werte für Namen, ...):

Code: Alles auswählen

<div class="form-group">
    <form:label name="dummy-label" class="col-md-6 control-label"/>
    <div class="col-md-6">
        <form:text name="dummy-field" class="form-control" />
        <form:listener name="dummy-listener">
            <span class="help-block">${content}</span>
        </form:listener>
    </div>
</div>
Durch die festen Werte kann der Tag die entsprechenden Instanzen konfigurieren und ihnen die "richtigen" Werte geben, die später im Formular relevant sind.


Ich hoffe das Beispiel hilft dir! :)
Viele Grüße,
Christian

Thalo
Beiträge: 244
Registriert: 10.08.2009, 16:56:52

Re: FormControl Taglib

Beitrag von Thalo » 07.08.2017, 23:28:50

Hallo Christian,

vielen Dank für das schöne Beispiel! :-)

Wie könnte man dieses adaptieren um das Markup nicht zwischen verschiedenen Typen (text, select, textearea, ..) kopieren zu müssen?

Ich würde gerne abhängig davon ob die FormControl-Value leer ist, eine CSS-Klasse setzen. Die Value ist aber zur Parse-Time noch nicht bekannt. Korrekt?

Benutzeravatar
dr.e.
Administrator
Beiträge: 4533
Registriert: 04.11.2007, 16:13:53

Re: FormControl Taglib

Beitrag von dr.e. » 14.08.2017, 14:51:19

Hi Thalo,

entschuldige meine späte Antwort, ich war im Urlaub und habe deinen Beitrag erst jetzt wahrgenommen! :roll:
Wie könnte man dieses adaptieren um das Markup nicht zwischen verschiedenen Typen (text, select, textearea, ..) kopieren zu müssen?
Dazu könntest du die Implementierung des Tags so anpassen, dass er erkennt, welcher Feld-Typ gewünscht ist. Im Wesentlichen betrifft das ja die Zeile

Code: Alles auswählen

$field = $this->getChildNode('name', 'dummy-field', TextFieldTag::class);
Die Unterscheidung könnte beispielsweise an einem zusätzlichen Attribut im Tag erfolgten.
Ich würde gerne abhängig davon ob die FormControl-Value leer ist, eine CSS-Klasse setzen. Die Value ist aber zur Parse-Time noch nicht bekannt. Korrekt?
Korrekt. Statische Werte (Formular ist noch nicht abgeschickt) sind zu diesem Zeitpunkt noch nicht bekannt. Sofern das Formular abgeschickt ist, übernimmt die onParseTime() in den meisten Controls das Presetting und du kannst entscheiden, ob leer oder gefüllt. Um das Verhalten konsistent für alle Fälle umzusetzen schlage ich vor das in der transform()-Methode des ComplexTextFieldTag zu implementieren. Dort könntest du mit einem zusätzlichen setPlaceHolder() eine entsprechende Klasse setzen.

Hoffe das hilft dir! :)
Viele Grüße,
Christian

Thalo
Beiträge: 244
Registriert: 10.08.2009, 16:56:52

Re: FormControl Taglib

Beitrag von Thalo » 14.08.2017, 23:54:59

Hallo Christian,

kein Problem. Ich hoffe du hast dich entspannen können. :-)

Bei deinem Beispiel ist mir aufgefallen, dass das Presetting nicht funktioniert und der ListenerTag nicht benachrichtigt wird.

Beispiel:

Code: Alles auswählen

<html:form name="form">
   <core:addtaglib class="APP\pres\taglib\form\ComplexTextFieldTag" prefix="complex" name="text-field"/>
   <complex:text-field label="Example" representation="test_field" name="foo" listener="Example Validator"/>
   <form:button name="send" value="Save" />
   <form:addvalidator class="APF\tools\form\validator\TextLengthValidator" control="foo" button="send" />
</html:form>
Kannst du das mal testen? :-)

Benutzeravatar
dr.e.
Administrator
Beiträge: 4533
Registriert: 04.11.2007, 16:13:53

Re: FormControl Taglib

Beitrag von dr.e. » 16.08.2017, 09:05:40

Hallo Thalo,

das kann ich mir gerne mal ansehen. Soweit habe ich die Implementierung noch nicht getestet (siehe Unit Test). Melde mich. :)
Viele Grüße,
Christian

Antworten

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 1 Gast