Skip to content

Migrating from WSC 3.1 - Form Builder#

Example: Two Text Form Fields#

As the first example, the pre-WoltLab Suite Core 5.2 versions of the forms to add and edit persons from the first part of the tutorial series will be updated to the new form builder API. This form is the perfect first examples as it is very simple with only two text fields whose only restriction is that they have to be filled out and that their values may not be longer than 255 characters each.

As a reminder, here are the two relevant PHP files and the relevant template file:

files/lib/acp/form/PersonAddForm.class.php
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
<?php
namespace wcf\acp\form;
use wcf\data\person\PersonAction;
use wcf\form\AbstractForm;
use wcf\system\exception\UserInputException;
use wcf\system\WCF;
use wcf\util\StringUtil;

/**
 * Shows the form to create a new person.
 * 
 * @author  Matthias Schmidt
 * @copyright   2001-2019 WoltLab GmbH
 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @package WoltLabSuite\Core\Acp\Form
 */
class PersonAddForm extends AbstractForm {
    /**
     * @inheritDoc
     */
    public $activeMenuItem = 'wcf.acp.menu.link.person.add';

    /**
     * first name of the person
     * @var string
     */
    public $firstName = '';

    /**
     * last name of the person
     * @var string
     */
    public $lastName = '';

    /**
     * @inheritDoc
     */
    public $neededPermissions = ['admin.content.canManagePeople'];

    /**
     * @inheritDoc
     */
    public function assignVariables() {
        parent::assignVariables();

        WCF::getTPL()->assign([
            'action' => 'add',
            'firstName' => $this->firstName,
            'lastName' => $this->lastName
        ]);
    }

    /**
     * @inheritDoc
     */
    public function readFormParameters() {
        parent::readFormParameters();

        if (isset($_POST['firstName'])) $this->firstName = StringUtil::trim($_POST['firstName']);
        if (isset($_POST['lastName'])) $this->lastName = StringUtil::trim($_POST['lastName']);
    }

    /**
     * @inheritDoc
     */
    public function save() {
        parent::save();

        $this->objectAction = new PersonAction([], 'create', [
            'data' => array_merge($this->additionalFields, [
                'firstName' => $this->firstName,
                'lastName' => $this->lastName
            ])
        ]);
        $this->objectAction->executeAction();

        $this->saved();

        // reset values
        $this->firstName = '';
        $this->lastName = '';

        // show success message
        WCF::getTPL()->assign('success', true);
    }

    /**
     * @inheritDoc
     */
    public function validate() {
        parent::validate();

        // validate first name
        if (empty($this->firstName)) {
            throw new UserInputException('firstName');
        }
        if (mb_strlen($this->firstName) > 255) {
            throw new UserInputException('firstName', 'tooLong');
        }

        // validate last name
        if (empty($this->lastName)) {
            throw new UserInputException('lastName');
        }
        if (mb_strlen($this->lastName) > 255) {
            throw new UserInputException('lastName', 'tooLong');
        }
    }
}
files/lib/acp/form/PersonEditForm.class.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<?php
namespace wcf\acp\form;
use wcf\data\person\Person;
use wcf\data\person\PersonAction;
use wcf\form\AbstractForm;
use wcf\system\exception\IllegalLinkException;
use wcf\system\WCF;

/**
 * Shows the form to edit an existing person.
 * 
 * @author  Matthias Schmidt
 * @copyright   2001-2019 WoltLab GmbH
 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @package WoltLabSuite\Core\Acp\Form
 */
class PersonEditForm extends PersonAddForm {
    /**
     * @inheritDoc
     */
    public $activeMenuItem = 'wcf.acp.menu.link.person';

    /**
     * edited person object
     * @var Person
     */
    public $person = null;

    /**
     * id of the edited person
     * @var integer
     */
    public $personID = 0;

    /**
     * @inheritDoc
     */
    public function assignVariables() {
        parent::assignVariables();

        WCF::getTPL()->assign([
            'action' => 'edit',
            'person' => $this->person
        ]);
    }

    /**
     * @inheritDoc
     */
    public function readData() {
        parent::readData();

        if (empty($_POST)) {
            $this->firstName = $this->person->firstName;
            $this->lastName = $this->person->lastName;
        }
    }

    /**
     * @inheritDoc
     */
    public function readParameters() {
        parent::readParameters();

        if (isset($_REQUEST['id'])) $this->personID = intval($_REQUEST['id']);
        $this->person = new Person($this->personID);
        if (!$this->person->personID) {
            throw new IllegalLinkException();
        }
    }

    /**
     * @inheritDoc
     */
    public function save() {
        AbstractForm::save();

        $this->objectAction = new PersonAction([$this->person], 'update', [
            'data' => array_merge($this->additionalFields, [
                'firstName' => $this->firstName,
                'lastName' => $this->lastName
            ])
        ]);
        $this->objectAction->executeAction();

        $this->saved();

        // show success message
        WCF::getTPL()->assign('success', true);
    }
}
acptemplates/personAdd.tpl
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
{include file='header' pageTitle='wcf.acp.person.'|concat:$action}

<header class="contentHeader">
    <div class="contentHeaderTitle">
        <h1 class="contentTitle">{lang}wcf.acp.person.{$action}{/lang}</h1>
    </div>

    <nav class="contentHeaderNavigation">
        <ul>
            <li><a href="{link controller='PersonList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.person.list{/lang}</span></a></li>

            {event name='contentHeaderNavigation'}
        </ul>
    </nav>
</header>

{include file='formError'}

{if $success|isset}
    <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
{/if}

<form method="post" action="{if $action == 'add'}{link controller='PersonAdd'}{/link}{else}{link controller='PersonEdit' object=$person}{/link}{/if}">
    <div class="section">
        <dl{if $errorField == 'firstName'} class="formError"{/if}>
            <dt><label for="firstName">{lang}wcf.person.firstName{/lang}</label></dt>
            <dd>
                <input type="text" id="firstName" name="firstName" value="{$firstName}" required autofocus maxlength="255" class="long">
                {if $errorField == 'firstName'}
                    <small class="innerError">
                        {if $errorType == 'empty'}
                            {lang}wcf.global.form.error.empty{/lang}
                        {else}
                            {lang}wcf.acp.person.firstName.error.{$errorType}{/lang}
                        {/if}
                    </small>
                {/if}
            </dd>
        </dl>

        <dl{if $errorField == 'lastName'} class="formError"{/if}>
            <dt><label for="lastName">{lang}wcf.person.lastName{/lang}</label></dt>
            <dd>
                <input type="text" id="lastName" name="lastName" value="{$lastName}" required maxlength="255" class="long">
                {if $errorField == 'lastName'}
                    <small class="innerError">
                        {if $errorType == 'empty'}
                            {lang}wcf.global.form.error.empty{/lang}
                        {else}
                            {lang}wcf.acp.person.lastName.error.{$errorType}{/lang}
                        {/if}
                    </small>
                {/if}
            </dd>
        </dl>

        {event name='dataFields'}
    </div>

    {event name='sections'}

    <div class="formSubmit">
        <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
        {@SECURITY_TOKEN_INPUT_TAG}
    </div>
</form>

{include file='footer'}

Updating the template is easy as the complete form is replace by a single line of code:

acptemplates/personAdd.tpl
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{include file='header' pageTitle='wcf.acp.person.'|concat:$action}

<header class="contentHeader">
    <div class="contentHeaderTitle">
        <h1 class="contentTitle">{lang}wcf.acp.person.{$action}{/lang}</h1>
    </div>

    <nav class="contentHeaderNavigation">
        <ul>
            <li><a href="{link controller='PersonList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.person.list{/lang}</span></a></li>

            {event name='contentHeaderNavigation'}
        </ul>
    </nav>
</header>

{@$form->getHtml()}

{include file='footer'}

PersonEditForm also becomes much simpler: only the edited Person object must be read:

files/lib/acp/form/PersonEditForm.class.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
namespace wcf\acp\form;
use wcf\data\person\Person;
use wcf\system\exception\IllegalLinkException;

/**
 * Shows the form to edit an existing person.
 * 
 * @author  Matthias Schmidt
 * @copyright   2001-2019 WoltLab GmbH
 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @package WoltLabSuite\Core\Acp\Form
 */
class PersonEditForm extends PersonAddForm {
    /**
     * @inheritDoc
     */
    public $activeMenuItem = 'wcf.acp.menu.link.person';

    /**
     * @inheritDoc
     */
    public function readParameters() {
        parent::readParameters();

        if (isset($_REQUEST['id'])) {
            $this->formObject = new Person(intval($_REQUEST['id']));
            if (!$this->formObject->personID) {
                throw new IllegalLinkException();
            }
        }
    }
}

Most of the work is done in PersonAddForm:

files/lib/acp/form/PersonAddForm.class.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php
namespace wcf\acp\form;
use wcf\data\person\PersonAction;
use wcf\form\AbstractFormBuilderForm;
use wcf\system\form\builder\container\FormContainer;
use wcf\system\form\builder\field\TextFormField;

/**
 * Shows the form to create a new person.
 * 
 * @author  Matthias Schmidt
 * @copyright   2001-2019 WoltLab GmbH
 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @package WoltLabSuite\Core\Acp\Form
 */
class PersonAddForm extends AbstractFormBuilderForm {
    /**
     * @inheritDoc
     */
    public $activeMenuItem = 'wcf.acp.menu.link.person.add';

    /**
     * @inheritDoc
     */
    public $formAction = 'create';

    /**
     * @inheritDoc
     */
    public $neededPermissions = ['admin.content.canManagePeople'];

    /**
     * @inheritDoc
     */
    public $objectActionClass = PersonAction::class;

    /**
     * @inheritDoc
     */
    protected function createForm() {
        parent::createForm();

        $dataContainer = FormContainer::create('data')
            ->appendChildren([
                TextFormField::create('firstName')
                    ->label('wcf.person.firstName')
                    ->required()
                    ->maximumLength(255),

                TextFormField::create('lastName')
                    ->label('wcf.person.lastName')
                    ->required()
                    ->maximumLength(255)
            ]);

        $this->form->appendChild($dataContainer);
    }
}

But, as you can see, the number of lines almost decreased by half. All changes are due to extending AbstractFormBuilderForm:

  • $formAction is added and set to create as the form is used to create a new person. In the edit form, $formAction has not to be set explicitly as it is done automatically if a $formObject is set.
  • $objectActionClass is set to PersonAction::class and is the class name of the used AbstractForm::$objectAction object to create and update the Person object.
  • AbstractFormBuilderForm::createForm() is overridden and the form contents are added: a form container representing the div.section element from the old version and the two form fields with the same ids and labels as before. The contents of the old validate() method is put into two method calls: required() to ensure that the form is filled out and maximumLength(255) to ensure that the names are not longer than 255 characters.

Last update: 2021-05-03