Eigene Formulare ändern und umgestalten mit hook_form_alter()

Wie man ein Formular erstellt und wie man dieses dann gestalten kann, war bereits Thema vergangener Artikel. Die Form API stellt aber noch weitere nützliche Funktionen zur Verfügung. Eine davon nennt sich hook_form_alter und kann dafür genutzt werden Tabellen vor ihrem Rendering zu verändern. Das kann nützlich sein, wenn man zwei Module geschrieben hat, wovon Modul A ein Formular bereitstellt und Modul B - wenn aktiviert - dieses Formular verändern oder erweitern soll. Hier ein Beispiel...

Angenommen wir haben unser Formular in Modul A gebaut mit folgenden Funktionen:

function blockslider_admin_select() {
  //mein Formular
}

function blockslider_admin_select_submit($form, &$form_state) {
  //die Submit-Funktion meines Formulars
}

function theme_blockslider_admin_select($form) {
  //die Theme-Funktion meines Formulars
}

function blockslider_theme() {
  //die Registrierung meiner Theme-Funktion
}

Das ganze gibt mir ein schönes Formular aus, das einen eigenen Submit-Handler hat und auch ein eigenes Theme (z.B. eine Tabelle mit Tabledrag-Funktion.

In Modul B möchte ich jetzt dieses Formular verändern. Dazu benutze ich folgenden Hook:

/**
* Implementation of hook_form_alter().
*/
function campaignmode_form_alter(&$form, $form_state, $form_id) {
}

Dieser Hook wird jetzt immer ausgeführt, wenn irgendwo ein Formular gerendert werden soll. Das heisst auch beim Formular von Modul A. Wir wollen aber, dass der Code in unserem Hook "nur" bei Formular A ausgeführt wird. Dafür erweitern wir den Hook wiefolgt:

/**
* Implementation of hook_form_alter().
*/
function campaignmode_form_alter(&$form, $form_state, $form_id) {
  switch ($form_id) {
    case 'blockslider_admin_select':
      //hier kommt später unser Code rein
    break;
  }
}

Wir fangen die Form-ID ab, prüfen diese in einem Switch und reagieren, sobald die ID unseres Formulars gefunden wurde. Der Grund für einen Switch ist, dass man auf diese Weise schnell und einfach weitere Formulare beeinflussen kann. Hat man dies nicht vor, kann man natürlich auch einfach die Form ID mit einer If-Schleife prüfen.

Die Form-ID ist übrigens i.d.R. identisch mit dem Namen der Funktion, mit der ihr das Formular in Modul A erstellt. Ganz sicher könnt ihr gehen, indem ihr das Formular im Browser aufruft und im Quellcode nachschaut, oder einfach oben in den Hook ein

var_dump($form_id);

schreibt und dann ebenfalls das Formular im Browser aufruft.

In den Case kommt dann der Code. Der sieht so aus, dass ihr einfach die Elemente des Formulars, die ihr ändern wollt, neu deklariert. Wenn also z.B. in Modul A steht:

$form['blocksliderregionname'] = array(
  '#type' => 'fieldset',
  '#title' => t('Blockslider settings'),
  '#description' => t('Refresh the list of available blocks.'),
);

und ihr wollt bei Aktivierung von Modul B die Beschreibung anpassen, dann müsst ihr in den hook_form_alter einfach nur schreiben:

$form['blocksliderregionname'] = array(
  '#type' => 'fieldset',
  '#title' => t('Blockslider settings'),
  '#description' => t('Refresh the list of available blocks and change the name of the region they should appear in.'),
);

Genauso könnt ihr natürlich auch zusätzliche Elemente erstellen. Angenommen in Modul A steht bereits folgendes:

$form['blocksliderregionname'] = array(
  '#type' => 'fieldset',
  '#title' => t('Blockslider settings'),
  '#description' => t('Refresh the list of available blocks.'),
);

$form['blocksliderregionname']['active'] = array(
  '#type' => 'checkbox',
  '#title' => t('Activate block'),
);

Dann können wir in Modul B schreiben:

$form['blocksliderregionname'] = array(
  '#type' => 'fieldset',
  '#title' => t('Blockslider settings'),
  '#description' => t('Refresh the list of available blocks and change the name of the region they should appear in.'),
);

$form['blocksliderregionname']['regionname'] = array(
  '#type' => 'textfield',
  '#title' => t('Region name'),
);

Wir haben dann also sowohl die Beschreibung des Fieldsets angepasst, als auch ein vollkommen neues Element in die Formstruktur eingebunden.

Das Problem, das wir jetzt haben ist unser eigener Submit-Handler. Wir haben ja jetzt ein neues Feld. Das Formular wird aber weiterhin an den Submit Handler aus Modul A gesendet, und der weiß ja gar nichts von dem neuen Feld.

Dazu müssen wir zuerst in hook_form_alter folgendes schreiben:

$form['#submit'] = array('blocksliderregionname_alteredsubmit');

Wir haben jetzt den Submit-Handler aus Modul A überschrieben und das Modul so geändert, dass es einen Submit-Handler namens blocksliderregionname_alteredsubmit erwartet. Das können wir ja jetzt ganz einfach machen. In Modul B schreiben wir also eine neue Funktion:

function blocksliderregionname_alteredsubmit($form, &$form_state) {
  //unser neuer Submit-Handler-Code
}

Auf die gleiche Weise können Validierung und auch das Theme angepasst werden. Einfach schreiben

$form['#theme'] = 'blocksliderregionname_alteredtheme';
$form['#validate'] = 'blocksliderregionname_alteredvalidation';

und dann die entsprechenden neuen Funktionen erstellen, die dann die Funktionen aus Modul A ablösen. Bei einer eigenen Theme Funktion, natürlich auch daran denken, die Theme-Funktion in Modul B zu registrieren.