Integration guide

Before continuing, make sure you’ve read up on the terminology first.

Reef requires a reasonable amount of set-up and integration before it operates within your website. Depending on your requirements, you will have to write code in these languages:

  • In PHP, code for preparing the builder and forms, receiving submissions and passing them to Reef, and providing feedback to the browser where required. In addition, an entry point should be created for internal requests. Most likely, you will also have to check authorization here: determining who may create forms and who may fill them in.
  • In HTML, some code in which the resulting Reef HTML will be injected. Probably a wrapper <div> or the like
  • In JavaScript, creating an instance of the Reef or ReefBuilder class, in order for the interactive elements to work

The integration is divided into four parts: the general Reef setup, implementing internal requests, builder integration and form integration.

Initializing Reef

Reef is initialized in two steps: first you create a ReefSetup object, which is used to build to configuration. Then, by passing this setup to a new Reef instance, the setup is frozen and checked. One of the main checks that is performed is compatibility checks, checking that the components, extensions and layouts are compatible with each other. If not, an exception is thrown.

An example of creating a Reef setup:

$Setup = new \Reef\ReefSetup(
        new \Reef\Storage\PDO_MySQL_StorageFactory($YourPDOObject),
        new \Reef\Layout\bootstrap4\bootstrap4(),
        new \Reef\Session\PhpSession()
);

The first argument defines the specific storage class that is to be used, you can choose from PDO_MySQL_StorageFactory, PDO_SQLite_StorageFactory and NoStorageFactory, of which the latter can only be used if you do not want to store forms and submissions.

Note

When using one of the PDO factories, Reef requires the PDO attribute PDO::ATTR_ERRMODE to be set to PDO::ERRMODE_EXCEPTION. Additionally, if you use MySQL, the connection charset should be set to utf8mb4.

The second argument defines the layout you want to use. Each layout has its own set of configurable options that you can set at creation. Note you can also pass an array of layout objects.

The third argument specifies the session implementation to use. For most basic components, a session object is not required, in which case you can use NoSession, but for example the reef:upload component requires a session object.

After creating the ReefSetup object, one can start adding additional components and extensions. The ReefSetup is initialized by default with some basic native components that need not be added manually:

  • reef:text_line
  • reef:textarea
  • reef:checkbox
  • reef:radio
  • reef:check_list
  • reef:text_number
  • reef:heading
  • reef:paragraph
  • reef:hidden
  • reef:option_list
  • reef:select
  • reef:condition
  • reef:submit

Other components, from reef-extra, a third-party developer, or from yourself, can be added to this list using ReefSetup::addComponent(). For example, to add the upload component, one would do:

$Setup->addComponent(new \Reef\Components\Upload\UploadComponent);

Similarly, one can add extensions using ReefSetup::addExtension(). By default, no extensions are loaded into Reef. To load the reef-extra::required-stars extension, you could for instance do:

$Setup->addExtension(new \ReefExtra\RequiredStars\RequiredStars);

Once the setup is finished, one can initialize Reef itself:

$Reef = new \Reef\Reef(
        $Setup,
        [
                'cache_dir' => './cache/',
                'locales' => ['en_US'],
                'internal_request_url' => './reefrequest.php?hash=[[request_hash]]',
        ]
);

The second parameter here is an array of configuration settings. The cache dir defines where Reef may put cache files. It is not optional, as currently the assets functionality relies on caching being present. The locales setting defines which locales to use. You can pass any number of locales here; passing multiple locales allows you to switch between locales and creating forms in these multiple locales. The internal_request_url setting defines the entry point of internal requests. Internal requests are used for assets like JS and CSS files and images, while it also provides the possibility for callacks to PHP for e.g. uploading files. This setting is also non-optional. More options are available, these can all be found TODO.

This concludes this section on creating the Reef object. Next we will use it to integrate the builder into your website.

Implementing internal requests

Before we start integrating the builder or form, we should start with one prerequisite: the internal requests entry point.

With the internal request URL configured as above, the request hash will be available to the internal request controller in $_GET['hash']. This hash should be passed to Reef in the following way, using the $Reef object as created above:

$Reef->internalRequest($_GET['hash']??null, [
        'form_check' => function($Form) {
                // Check that user is allowed to view $Form
        },
        'submission_check' => function($Submission) {
                // Check that user is allowed to view $Submission
        },
]);

The result of the internal request can be anything: JS code, CSS code, an image or, more generally, any (binary) file. Make sure your code can handle this.

At this stage, you probably do not yet know how to check whether a user is allowed to view something. For the time being, we can leave them empty. When finished, you can implement them. If a user is not allowed to view view the given form or submission, you should make sure to stop execution by either throwing an exception or exiting the script.

Important

The internal requests URL can be called with both GET and POST methods, so be sure the code answers to both of these.

Integrating the builder

Note

If you do not want to use the builder interface, you can skip this section

To integrate the builder, we expect a $Reef object is available, as generated in the previous section. Getting a builder object is not hard. Actually, it is as easy as:

$Builder = $Reef->getBuilder();

Next you may want to change some settings:

$Builder->setSettings([
        'submit_action' => 'path/to/builder/submit',
        'definition_form_creator' => function($Creator) {
                // ...
        },
        'components' => [
                // ...
        ],
]);

The available settings are:

  • The submit_action is the entry point to which the builder data (the form configuration) may be submitted (POSTed).
  • The definition_form_creator is a callback function with which you can modify the form configuration form (read: the form containing the form configuration). By default, this form contains a storage_name field which you may or may not want (you may delete it, but then you should provide your own value programmatically when submitting, more on that further below), and you may add your own configuration values.
  • The components array defines which components you want your users to be able to use in the builder. It should (of course) be a subset of the components added to your Reef setup. You can also add and remove components using Builder::addComponents() and Builder::removeComponents().

Next, we want to display the builder. Before creating the builder, we have to create the form we want to edit. An existing stored form can be fetched using $Form = $Reef->getForm($i_formId);, a new form can be created using $Form = $Reef->newTempStorableForm(['storage_name' => 'some_unique_table_name']);. Note that the storage name should match a certain format, please refer to the information on names on in the terminology.

Having obtained the form, we can obtain the HTML as follows:

$s_html = $Builder->generateBuilderHtml($Form, [
        'definition_submission' => [
                // ...
        ],
]);

The definition_submission setting is optional, and can provide values for fields you added to the form configuration form you added earlier to the definition_form_creator.

The JS code can be obtained using:

$s_js = $Reef->getReefAssets()->getJSHTML('builder', [
        'exclude' => [
                'jquery',
                'popper',
                'bootstrap4',
        ],
]);

Here, exclude defines which libraries your website has already loaded and hence should not be included again. The CSS code can be obtained similarly using getCSSHTML().

Include the JS and CSS code in your <head>, and the $s_html in somewhere in your <body>, like:

<div class="builderWrapper">
        <?php echo($s_html); ?>
</div>

At this stage you should be able to see the builder interface on your builder page. However, it will not be interactive yet, as we have not done anything with JavaScript yet. To attach javascript, you use something like:

var builder;
$(function() {
        builder = new ReefBuilder('.builderWrapper', {
                submit_before : function(ajaxParams) {
                        ajaxParams.data.some_custom_variable = "some_custom_value";
                }
        });
});

The first argument defines the element where the builder HTML resides in. In this case we use the div.builderWrapper as used above. The second argument may contain some settings. The submit_before setting may be used to alter the AJAX request to the server before performing it; it can be used to add additional parameters. Of course, you can also ommit the second argument.

Having added the javascript, reloading the builder interface you should now have achieved an interactive builder. The last step remaining is implementing the submit entry point.

Once a user saves a form, a request is POSTed to the submit_action URL provided in the $Builder settings, with the form data in the $_POST['builder_data'] variable. In most cases, you will want to do something along these lines:

$Builder->processBuilderData_write($Form, $_POST['builder_data'], function(&$a_return, $DefinitionSubmission) use(&$Form) {
        if($a_return['result']) {
                // Here you may want to perform some database actions of your own, probably saving $Form->getFormId() somewhere

                $a_return['redirect'] = 'some/url/to/redirect/to/after/success';
        }
});

Important

Note that in order for the updater to be able to correctly migrate the old form state to the new form state, you will need to use the same $Builder and $Form objects in both generateBuilderHtml() and processBuilderData_write().

The processBuilderData_write() method processes the builder data, which might be a two-step process:

  • In the first request, the JS builder indicates no data loss is permitted. If the updater recognizes that dataloss might or will occur, the form is not updated and information about the data loss is returned instead, asking the user whether he/she really wants to do this.
  • If the user accepts data loss, another request is done, with the indication that data loss is permitted.

When the builder may proceed, the form is migrated from the old state to the new state. After the form has been migrated successfully to the new state, the callback you may pass to the third argument of processBuilderData_write() is performed. Note that whenever you want to use $Form in the callback, you have to pass it by reference, as the form in the new state will be a new Form instance. The first parameter of the callback ($a_return) contains the data returned to the JS builder. If $a_return['result'] is truthy, the form has updated successfully. You may provide an URL for redirect to redirect the user to on success. The second parameter of the callback ($DefinitionSubmission) contains the submission of the form configuration form. If you used definition_form_creator earlier on to augment this form, here is your chance to read out the values the user submitted.

This concludes the integration of the builder into your website. While you cannot yet view and fill out the forms themselves, you should now be able to create and edit forms. You can also implement a delete function using $Form->delete();.

Note

If there are no submissions to a form, the builder will never complain about data loss as there is no data to be lost. Keep this in mind if you are testing the builder integration at this stage.

Integrating the form viewer

To integrate the form viewer, we expect a $Form object is available, created in the builder and loaded through $Reef->getForm() or $Reef->getFormByUUID().

To view the form, we need a Submission object. For a new submission, this can be created using:

$Submission = $Form->newSubmission();
$Submission->emptySubmission();

To edit an existing submission stored by Reef, you can use:

$Submission = $Form->getSubmission($i_yourSubmissionId);

Now you can obtain the form HTML using:

$s_html = $Form->generateFormHtml($Submission, ['main_var' => 'form_data']);

You may also pass null for $Submission, in this case automatically a new submission will be used. The main_var defines which $_POST entry should be used for the form data.

In a similar way as with the builder, you can fetch the JS code using:

$Form->getFormAssets()->getJSHTML('form', [
        'exclude' => [
                'jquery',
                'popper',
                'bootstrap4',
        ],
]);

The CSS code can be obtained using getCSSHTML().

The HTML and JavaScript required to create an interactively checked form will look like this:

<form action="path/to/submission/submit" method="post" onsubmit="return reef.validate();">
        <div class="form-wrapper">
                <?php echo($s_html); ?>
        </div>
</form>

<script>
var reef;
$(function() {
        reef = new Reef($('.form-wrapper'));
});
</script>

In the controller behind the action URL, you can save the submission using:

if($Submission->processUserInput($_POST['form_data']??[])) {
        // Successfully saved. Here you can redirect or take some other action
}

Here, processUserInput() is a utility function performing fromUserInput(), validate() and save() inside a SQL transaction.

Tip

If you use the same controller for the view and submit actions, using e.g. if($_SERVER['REQUEST_METHOD'] == 'POST') to distinguish them, you should put this call to processUserInput() before generateFormHtml(). In this way, generateFormHtml() will include any validation errors if these occurred and JavaScript did (or could) not catch them!

You may probably want to also include functionality to delete a submission, this can be done using $Submission->delete();. With this last note, this concludes the integration of the form viewer.

Attention

Don’t forget to implement the form and submission permission checks in your internal request implementation!

Submitting the form using AJAX

You may wish to submit your form submissions through AJAX rather than through a standard POST request. In this case, the following changes should be applied compared to the instructions in the previous section.

You should give the <form> element an id (e.g. <form id="submission_form" ...>), and then use:

reef = new Reef($('.form-wrapper'), {
        submit_url : 'path/to/submission/submit',
        submit_form : $('#submission_form')
});

You can of course also implement your own submit call, using reef.validate() for validation.

In your submit PHP, you can then do something along the lines of:

$Submission->fromUserInput($_POST['form_data']??[]);
if($Submission->validate()) {
        $Submission->save();
}
else {
        $a_return = [
                'errors' => $Submission->getErrors(),
        ];

        echo(json_encode($a_return));
        die();
}

When errors occur, the automatic reef JS submitter will recognize the errors entry and add the errors to the respective fields. When implementing your own submit call, you can use reef.addErrors(response.errors); to achieve this.

Presenting hardcoded forms

You may want to present forms that are not saved by Reef, and store the submissions yourself. In this case, the following changes should be applied compared to the instructions in the integration section.

First, you will not be loading the $Form from Reef but creating it yourself. You could do this using the Creator object, like this:

$Form = $Reef->newTempForm();
$Form->newCreator()
        ->addField('reef:text_line')
                ->setName('name')
                ->setLocale(['title' => 'Your name'])
        ->apply();

Next, we need to replace the processUserInput() call with the following:

$Submission->fromUserInput($_POST['form_data']??[]);
if($Submission->validate()) {
        $a_yourStructuredData = $Submission->toStructured();
        // Save the data
}

Now, $a_yourStructuredData contains a structured representation of the submission data. You may for example json_encode() it and save it in a file or database.

The last line that needs to be replaced is the getSubmission() line, to import the data from the structured data representation:

$Submission = $Form->newSubmission();
$Submission->fromStructured($a_yourStructuredData);

Important

Note that not all components may be compatible with this way of using Reef. Forms that are not saved within Reef for example lack the ability to (reliably) use the filesystem functionality, and hence cannot use the reef:upload component.