The fifth and final part of our spotlight series for WoltLab Suite 5.2 highlights two new major updates for developers, form builder and improvements and new features for our integrated developer tools.
Form Builder
Prior to WoltLab Suite Core 5.2, creating forms required writing similar code over and over again: declaring the PHP controller’s properties that stores the input field values, reading the values from the request data, validating them, passing them to the IDatabaseObjectAction instance to create or edit the relevant object, and finally assigning the relevant variables to the template. In the template, each input field required its own dedicated form element which generally shares much of the same code with similar fields except for the field label and description.
With the new version of WoltLab Suite Core, we have added a new component that makes creating forms much easier: form builder. Using form builder greatly simplifies the tasks of creating and maintaining forms: An input field generally requires only a single PHP object that is not only used to hold the business logic, but also generates the HTML code for the template. The controller template requires only a single line to render the entire form.
The following PHP code creates a form with two input fields, a simple required text input field whose value may not be foo and a yes/no selection (corresponding to a boolean value), inside of a labeled section of the form.
protected function createForm() {
parent::createForm();
$this->form->appendChild(
FormContainer::create('data')
->label('wcf.global.form.data')
->appendChildren([
TextFormField::create('name')
->label('wcf.foo.name')
->description('wcf.foo.name.description')
->required()
->maximumLength(255)
->addValidator(new FormFieldValidator('notFoo', function(TextFormField $formField) {
if ($formField->getValue() === 'foo') {
$formField->addValidationError(
new FormFieldValidationError(
'isFoo',
'wcf.foo.name.error.isFoo'
)
);
}
})),
BooleanFormField::create('isCool')
->label('wcf.foo.isCool')
->value(true)
])
);
}
Display More
Form controllers using form builder have to extend AbstractFormBuilderForm and have to set the values of $objectActionClass and $formAction in addition to overriding createForm() as shown above. The save() method automatically executes the relevant action, create or update, using an $objectActionClass object.
Within the template, it is sufficient to call the getHtml() method of the form:
The form above will then look like this:
WoltLab Suite 5.2 will deliver many form field classes that should cover most forms. For special cases, you can implement your own form field that you are then also able to reuse for multiple forms in your package.
Lastly, we want to highlight the dependency system included in form builder. Forms can include additional complexity, such as input elements that are only available if certain requirements, e.g. form values, are met. With form builder, adding such a dependency requires just a few lines of PHP code:
In this case, the $foo field is only available, i.e. visible in the form, if the current value of the $bar field is baz.
Using form builder is not restricted to controllers but you can also use it in dialogs. Outside of a controller, however, you need to manually create the IFormDocument object that AbstractFormBuilderForm creates automatically:
class FooAction extends AbstractDatabaseObjectAction {
/**
* @var DialogFormDocument
*/
protected $form;
/**
* @return DialogFormDocument
*/
protected function getForm() {
if ($this->form === null) {
$this->form = DialogFormDocument::create('fooDialogForm')
->appendChildren([
// append relevant containers and fields
]);
$this->form->build();
}
return $this->form;
}
public function validateGetFooForm() {
// access validation etc.
}
public function getFooForm() {
return [
// `dialog` and `formId` are required
'dialog' => $this->getForm()->getHtml(),
'formId' => $this->getForm()->getId()
];
}
public function validateSubmitFooForm() {
// access validation etc.
$this->getForm()->requestData($this->parameters['data'] ?? []);
$this->getForm()->readValues();
$this->getForm()->validate();
}
public function submitFooForm() {
// show form again in case of validation errors
if ($this->getForm()->hasValidationErrors()) {
return [
// `dialog` and `formId` are required
'dialog' => $this->getForm()->getHtml(),
'formId' => $this->getForm()->getId()
];
}
// actions based in valid form data
return [
// custom return values without any restrictions
];
}
}
Display More
<a href="#" class="button" id="openFooFormButton">{lang}wcf.foo.button.openFooForm{/lang}</a>
<script data-relocate="true">
require(['WoltLabSuite/Core/Form/Builder/Dialog'], function(FormBuilderDialog) {
var dialog = new FormBuilderDialog(
'fooDialog',
'wcf\\data\\foo\\FooAction',
'getFooForm',
{
destroyOnClose: true,
dialog: {
title: '{lang}wcf.foo.fooForm.title{/lang}'
},
submitActionName: 'submitFooForm',
successCallback: function(data) {
console.log(data);
}
}
);
elById('openFooFormButton').addEventListener('click', function() {
dialog.open();
});
});
</script>
Display More
In the code example above, clicking on the openFooFormButton button will open a dialog showing the form generated by FooAction::getFooForm(). The entered data are processed by FooAction::submitFooForm() after the form has been submitted. In case of any validation error, the form will be shown again with the relevant error message. Otherwise, the return values of FooAction::submitFooForm() will be passed to successCallback.
The new interfaces and classes introduced by form builder are documented extensively and we will also update our developer documentation with general information on form builder and on how to migrate old forms to form builder forms.
New Developer Tools
For the new version, we have also added many new features to the developer tools available in the admin panel.
It is now possible to install packages directly from the projects list without having to create an archive first and then install that package via the package management. Instead, the developer tools directly use the original files from the project directory to install the package.
When adding a new project, in addition to importing an existing package, it is now also possible to set up a new package which shows you a form to fill out all relevant information from which a package.xml file will be generated. Based on this new feature, it is now also possible to edit the package.xml file of existing projects via a graphical user interface which validates the input in place or after submitting the form.
Another new and quite extensive new feature of the developer tools is the ability to add, edit, and delete entries of package installation plugins via a graphical user interface.
The following animated image shows the form for adding a new object type to a package and illustrates the strengths of the graphical user interface as the form adapts to the selected object type definition and shows all of the additional fields relevant for the selected object type definition.
When deleting an entry, it will be removed from database and the import instruction is deleted but, if the relevant package installation plugin supports it, an explicit deletion instruction can also be added.
Be aware that only package installation plugins implementing the IGuiPackageInstallationPlugin interface are available via the graphical user interface.
Both, the new setup and edit project forms and package installation plugin entry add and edit forms, use form builder.
This concludes our spotlight series for WoltLab Suite 5.2 and we hope that you are looking forward to using the new version. Before the final release, we will post an overview with many of the other changes coming with version 5.2.