Developing new Input Blocks

Form Reform Developer provides a growing suite of resources to assist those developing blocks, handlers and more complex forms for Form Reform.

Inputs as Block Types

Each type of form control in Form Reform is implemented as a block type. By the time you read this you should already have seen the list of Form Reform Blocks and read about Developing new Input Block Templates.

If you need a specialized template or a custom input element, you can design new templates or new block types for form elements as you would any block type.

If your requirement is for a variation of text input, first have a look at Text Input Variants. The Masked Text input and Custom Text input can be configured to cover many possibilities.

Nevertheless, some applications will require special to purpose input types and Form Reform has been designed from the start to facilitate adding your own custom form inputs. Here we will look at the general structure of a Form Reform block and provide guidance on a new block type for form reform.

You should read about Developing new Input Block Templates before you read this. You should also be familiar with the Concrete CMS core documentation for Creating a New Block Type.

Then look through existing Form Reform Blocks and pick something close as a starting point. You can copy an existing block and rename it (and the database table), let your block controller extend the controller of an existing block, or code a block from scratch. Use whichever suits your purpose and developer abilities.

The information below is comprehensive, but for most adaptations of existing blocks you will only need a few small parts of it.

FormBlockController class

Form input block types in Form Reform extend the FormBlockController class, which in turn extends the core BlockController class. So in your block controller, begin with:

use JtF\FormReform\FormBlockController;
class Controller extends FormBlockController {...}

You don't have to do this, but inheriting from FormBlockController provides a lot of common functionality and saves a hell of a lot of code. You can then concentrate on the specifics of your form input block and let the inherited FormBlockController take care of all the general functionality.

Extending the usual controller methods

The FormBlockController class already extends many of the usual controller methods. on_start(), on_before_render()registerViewAssets(), __construct(), save().

If you override any of these, you will likely need to begin or end by calling the parent::methodName() to ensure that essential functionality is maintained, or to make sure you replicate essential functionality in your overriding method.

Some controller methods specific to Form Reform

Many of the methods in the controller are simply embellishments of core methods. There are also some new methods that you may or will need to implement for your own form input blocks.

public function getBlockTypeHelp()

This method should return text/html suitable for the built in documentation. You can see that implemented on the Form Reform Blocks page here, and once you have Form Reform installed, at /dashboard/system/form_reform/form_reform_blocks on your site.

public function validateSubmission($form_data)

This method handles validation of a form submitted by a user. It is a bit like a regular block validate() method, but that method is for edit mode and this method is for view mode.

$form_data is an associative array of raw data from the form, think of it as coming (almost) straight from a $_POST super-global.

When a form is submitted, Form Reform's ValidateSubmission handler calls this method for every block rendered in the form and consolidates any $error lists reported.

Like the regular block validate() method, the validateSubmission() method should return an $errors object or null.

In general, it should only validate the block's own input(s) and leave other inputs for their respective controllers to validate.

Our example below is taken from the Text Input block controller. Always bear in mind that in-browser validation is good for user feedback, but is not sufficiently secure. So all validation needs to be enforced in the validateSubmission() method.

        
public function validateSubmission($form_data)
{
    $error = $this->app->make('helper/validation/error');
    if(
        // no input name, more an edit issue, but guard here
        empty($this->input_name) ||

        // no input name in form_data, likely a hack
        (!array_key_exists($this->input_name, $form_data)) ||

        // its supposed to be a string, likely a code issue
        (!is_string($form_data[$this->input_name])) ||

        // now our block specific tests for validity
        (!empty($this->minlength) && 
            strlen($form_data[$this->input_name])<$this->minlength) ||
        (!empty($this->maxlength) && 
            strlen($form_data[$this->input_name])>$this->maxlength)
    ){
        /*
         * Use our custom message if we have one
         */
        if(!empty($this->validation_fail_message)){
                $error->addError(
                    LinkAbstractor::translateFrom(
                        $this->validation_fail_message
                        ), 
                true, $this->input_name, $this->label
                );
        }else{
            $error->addError(
                t('%s failed validation', $this->label), 
                false, $this->input_name, $this->label
                );
        }
    }

    if($error->has()){
        return $error;
    }
}    

public function checkSpam($form_data)

If a block's purpose is not spam detection, it doesn't need this method!

This method enables spam detection blocks to integrate with the SpamCheck handler and should return an $errors list object if the block thinks it is being spammed. In some cases it can simply call validateSubmission().

The essential difference is that checkSpam() is called by the SpamCheck handler while validateSubmission() is called by the ValidateSubmission handler, and these handlers can have different actions. For example, a SpamCheck can be configured to fail silently.

$form_data is again the data posted from the form submission.

public function extractSubmissionData($form_data)

Where form data elements in the POST data to not map 1:1 onto the actual data your element provides, this is where you can modify the data.

Where the form data elements of your block do map 1:1, the parent Form Block Controller class provides a default implementation and you don't need this method.

$form_data is again the data posted from the form submission. This method should return an associative array containing only the data that needs to be recorded for ONLY the current input element of the form (so do not mess with data from other inputs).

Existing uses include

  • Making sure an un-checked checkbox always has a value.
  • Applying geolocation to country etc. inputs.
  • Attaching hidden data after a form is submitted.
  • Removing data used for spam detection so it does not get recorded
  • Removing duplicated data from 'confirm' inputs (because validateSubmission() takes care of confirming a match).

inputPersistence($value = null)

In most cases you should leave this alone. This is a callback from the customised form helper and the main reson for overriding would be to always return false for some of the spam catching block types such as the Honeypot where an input should never persist.

Some utility methods provided by FormBlockController

The FormBlockController class also provides some utility methods that can be generally useful in block controllers, edit dialogues and views.

These are available to use or ignore as fits with your own block requirements.

getRichTextForDisplay($rich_text)

A convenience to use translateFrom from the core LinkAbstractor.

getRichTextForEdit($rich_text)

A convenience to use translateFromEditMode from the core LinkAbstractor.

getPlainTextForDisplay($rich_text)

A convenience to create plain text from rich text. Internally it uses translateFrom from the core LinkAbstractor.

abstractLinks($rich_text)

A convenience to use translateTo from the core LinkAbstractor.

getEmTags()

A convenience to return the opening and closing the tags used throughout the built in help systems to emphasize text. This is actually <b> and </b>.

For example:

[$a, $b] = $this->getEmTags();
return t('Some text with %semphasis%s', $a, $b);

 

Common validation assistance

  • protected function validateInputName($args, $error = null)
  • protected function validateMinMaxLength($args, $error = null)
  • protected function validateMinMaxValue($args, $error = null)
  • protected function validateChoices($args, $error = null)

These FormBlockController methods facilitate common code in block controller validate($args) methods for validating configuration data from the edit dialogue. They all return an $error object which should be passed to the next method called, added to with further validation tests, or returned.

protected function validateInputName($args, $error = null)

Checks an input name has been provided.

protected function validateMinMaxLength($args, $error = null)

Checks minimum text length is less than maximum text length

protected function validateMinMaxValue($args, $error = null)

Checks minimum number range is less than maximum number range

protected function validateChoices($args, $error = null)

Confirms there are at least 2 choices, that each choice has a unique value and a unique name.

The block edit dialogue

Tabs

Most Form Reform blocks implement a tabbed dialogue. The edit.php code sets up a table of tabs, where each tab is a file beneath ./edit/.

The actual tabs are up to you. In general, they should follow the convention

Form Control - configuring the actual front end form control

Validation - how that form control will be validated when submitted

Persistence - how the input value will be pre-populated, this interacts with the custom form helper

Behaviour - when the form control should be hidden or not rendered

Within each tab, much of the edit code is repeated elements driven by looping through a table. You can use these elements and the loops, or replace or intersperse them with edit dialogue code specific to your form control.

In general, the Behaviour and Persistence tabs can be left alone.

The block view

The default block view.php is in essence, just another block template, so please refer back to that section. The essential difference is that you will probably be coding more from scratch and specific to your block, rather than making minor changes to existing block view code.

As general guidance, study the code of as many existing Form Reform blocks as you can and follow the conventions they use unless you have very good reason to do different.

 

Ajax Considerations for Form Control Blocks

Refresh After Ajax

After an AJAX submission has completed, any form block with the class 'form-reform-refresh-after-ajax' will be rendered again and the newly rendered view will replace the original view.

This functionality is used by the Captcha block for some captchas to create a new captcha ready for the next submission. After the new block view has been loaded, the event 'script_restart.form_reform' will be triggered on the newly loaded block.

Notify After Ajax

After an AJAX submission has completed, any form block with the class 'form-reform-notify-after-ajax' will be notified by triggering the event 'ajax_submit_completed.form_reform'. This is also used by the Captcha block for some captchas.

Other blocks

Whilst the above refers to the Captcha block, the functionality is available for use by any block.

Repeatable Group Considerations for Form Control Blocks

To be compatible with repeatable groups of form inputs, a form input block must be capable of re-initialising when it is copied. If the form input block is straight forward HTML input fields and CSS, that will usually not require any further thought. The Repeatable Group block will take care of copying it, clearing any data, and the HTML of the block view will sort itself out.

Where a block view and its interaction is managed by JavaScript, then perhaps some further handling is required to attach the JavaScript to the replicated input.

Have a look at some of the existing form input blocks with a view.js.

The block view.js should identify the block DOM and input elements by selecting on .classes or [attributes], not by #id attributes. You will see various ways of doing this in the existing view.js files.

For particularly complex blocks, the Repeatable Group block triggers the event init.form_reform when a repeatable group is initialized (Initial Value - input persistence) and copy.form_reform ​​​​​​​when a repeatable group is copied.

Alternative strategies:

  • A block's view.js could handle DOM mutations.
  • A block's view.js could run a polling loop
  • A block could add script attributes to elements rather than a separate view.js
  • A block could be built about a library such as vue.js which takes care of DOM mutations

None of these alternatives are currently used within Form Reform form input blocks..

 

Additional Pages

Reform the way you add new input controls

If you need a specialized template or a custom input element, you can design new templates  or new block types for form elements as you would any block type.

Blocks are easy for third party addition or extension. Block templates and are the first thing any Concrete CMS developer learns to code. They are one of the easiest things to code. The underlying mechanisms are well established and reliable.

Reform what you can do with form data

Form handlers are built about the same extensible plugin system as many of my other addons (Universal Content Puller, Omni Gallery, Extreme Clean ...).

The whole system is aimed at easy extension within Form Reform, by third party addons, by agencies and by site building developers.

Handlers can be easily added to do whatever you want with the form data.

Reform where you can save form data

Saving form data with Form Reform is simply a handler in the processing pipeline. You can save to multiple locations or just one location.

If you need to save data elsewhere, such as to a dedicated table, a table provided through another addon, to another database, send it to an API, forward it to another server, or anywhere you can imagine, you can adapt or develop a form handler to do so.

The complexity of the code depends on where you are saving or sending the data, but wrapping that into a form handler plugin for Form Reform is straight forward.

The Form Reform handler plugin system is designed for easy extension.

Form Reform

Reform the way forms are built. Build a form out of blocks. Take control of how form submissions are processed and how the submitted data is stored. Easy to extend. Easy to reconfigure. Tangible data. Easy to add your own integrations.

Form Reform Display

List and display form submissions from Form Reform.

Form Reform UTM

Not just Form Reform and not just UTM! Capture and hold incoming UTM (or other) tags and make the tag values available to Form Reform and/or Conditional Redirect as {{place_holders}}. You don't need Form Reform to use this.

Form Reform Dynamics

Form handlers for querying Microsoft Dynamics, forwarding and updating form data to Microsoft Dynamics.

Snapshot

A suite of advanced image capture and upload tools. Enhanced drag and drop file uploading. Make screengrabs from within Concrete CMS. Capture images directly from device webcams. Edit images before uploading.

Form Reform Attributes, Express and Users

Save submitted forms to Express objects and user attributes. Add and remove users from groups.

Form Reform Image Picker

Form Reform Image Picker provides an image picking input block for Form Reform. The Image Picker Input is preconfigured to connect to most Omni Gallery gallery and slider display widgets, the core gallery block, and thumbnail showing templates for the core page list block. Advanced settings allow the Image Picker Input to be configured to pick images from other galleries and sliders.

Form Reform Data Picker

Form Reform Data Picker provides data picking input blocks for Form Reform. The Table Picker Input is preconfigured to connect to Universal Content Puller table display widgets. Advanced settings allow the Table Picker Input to be configured to pick data from other HTML tables.

Form Reform Developer

A growing suite of resources to assist those developing blocks, handlers and more complex forms for Form Reform.

Learn with a simple form

While you may have plans to implement some much more complex forms using Form Reform, we strongly recommend you start with a simple form such as our contact form example in order to review the basic principles of using Form Reform before you move onto anything bigger.

  1. Start by submitting the form at Getting Started - Your First Form a few times, even making some deliberate mistakes.
  2. Watch our Getting Started with Form Reform video to see how the form is built.
  3. Read through the rest of Getting Started - Your First Form for more details of how this form is built.
  4. Create a test page on your site to build your own version of Getting Started - Your First Form and experiment.
  5. Develop your test page with some of the concepts introduced by our further examples and experiment with some of the other form inputs.