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
orReefBuilder
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 astorage_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 usingBuilder::addComponents()
andBuilder::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.