A short tutorial to the CustomHtmlForm module

SilverStripe has some great features which take away a lot of work. For example forms are built by defining a form on a controller and then just calling it in a template. But every time someone else does work for you you are loosing control over how this work is done.

When I learned SilverStripe and then augmented my html skills by reading some good books I realized that the form markup created by the framework was not as I wanted it to be. I remember that every input field was surrounded by a div which I did not need. First I tweeked the core classes to let forms match my needs, but If you do change core files, you will end up in chaos sooner or later.

Later we decided to use the CSS framework YAML, and again our forms needed a special markup but the framework was not able to deliver it.

Sascha was the first one to do something about that by creating a module that let you customize forms in any way: Markup, validation, JavaScript. First we gave it the name of our small firm calling it Pixeltricks-module. But as we published it we choose a more comprehensive name: CustomHtmlForm.

I want to give you a short introduction to that beloved module of mine by creating a registration form with you.

A blueprint for each form

Each form has it´s own class. I called it RegistrationForm.php and placed it inside the projects /code folder:

 /*
 * @copyright Pixeltricks GmbH
 * @since 09.03.2011
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
 */
class RegistrationForm extends CustomHtmlForm {

    protected $formFields = array(
        'FirstName' => array(
            'type' => 'TextField',
            'title' => 'firstname',
            'checkRequirements' => array(
                'isFilledIn' => true,
                'hasMinLength' => 3
            )
        ),
        'Surname' => array(
            'type' => 'TextField',
            'title' => 'surname',
            'checkRequirements' => array(
                'isFilledIn' => true,
                'hasMinLength' => 3
            )
        ),
        'Email' => array(
            'type' => 'TextField',
            'title' => 'email address',
            'checkRequirements' => array(
                'isEmailAddress' => true,
                'isFilledIn' => true,
                'callBack' => 'doesEmailExistAlready'
            )
        ),
        'Password' => array(
            'type' => 'PasswordField',
            'title' => 'Password',
            'checkRequirements' => array(
                'isFilledIn' => true,
                'hasMinLength' => 6,
                'mustNotEqual' => 'FirstName',
            )
        ),
        'PasswordCheck' => array(
            'type' => 'PasswordField',
            'title' => 'Password check',
            'checkRequirements' => array(
                'mustEqual' => 'Password'
            )
        )
    );

    /**
     * Form callback: Does the entered Email already exist?
     *
     * @param string $value the email address to be checked
     *
     * @return array to be rendered in the template
     * @author Roland Lehmann 
     * @since 4.3.2011
     */
    public function doesEmailExistAlready($value) {
        $emailExistsAlready = false;

        $results = DataObject::get_one(
                        'Member',
                        "Email = '" . $value . "'"
        );

        if ($results) {
            $emailExistsAlready = true;
        }

        return array(
            'success' => !$emailExistsAlready,
            'errorMessage' => _t('SilvercartPage.EMAIL_ALREADY_REGISTERED', 'This Email address is already registered')
        );
    }

    /**
     * success action
     *
     * @param SS_HTTPRequest $data     SS session data
     * @param Form           $form     the form object
     * @param array          $formData CustomHTMLForms session data
     *
     * @author Roland Lehmann 
     * @since 4.3.2011
     * @return void
     */
    protected function submitSuccess($data, $form, $formData) {
        // Create new regular customer and perform a log in
        $customer = new Member();
        $customer->castedUpdate($formData);
        $customer->write();
        $customer->logIn();
        $customer->changePassword($formData['Password']);

        // Redirect to welcome page
        Director::redirect(DataObject::get_one('RegisterWelcomePage')->Link());
    }
}

$formFields defines the forms fields and configuration in a multi dimensional array. The field FirstName is an instance of TextField with the label firstname. It has to be filled in ('isFilledIn') by a minimum length of 3 characters.

$Email is an instance of TextField too but has to meet some requirements: It must be a valid email address, it must be filled in and it has a callback function named 'doesEmailExistAlready' which is defined later in the class. It checks if somebody already registered with that email because the email is mostly a unique identifier in registration procedures.

There are quiet a few requirements which can be set on a field: hasSpecialSigns, isEmailAddress, isCurrency, isDate, isFilledIn, isFilledInDependantOn, isNumbersOnly, hasMinLength, hasLength, mustEqual, mustNotEqual.

Finally the method submitSuccess() defines the form action: We create a new member and redirect to a page.

HTML

Now for the interesting part: The custom html. Corresponding to the controller I created a file called RegistrationForm.ss inside templates/Layout. It looks like this:

This is a special markup needed by the YAML CSS framework which may not be familiar to you. But each field is called separately and can be put anywhere.

Controller implementation

The form has to be registered on the controller which is supposed to show the on the frontend. Just add one line to the init():

    public function init() {
        $this->registerCustomHtmlForm('RegistrationForm', new RegistrationForm($this));
        parent::init();
    }

This is it ;)