Skip to content

Latest commit

 

History

History
382 lines (262 loc) · 13 KB

README.md

File metadata and controls

382 lines (262 loc) · 13 KB

FormBuilder - A Laravel package

The Business Case

This package reduces time and cost of application development by providing solutions to the following requirements:

  • The business process involves filling out many, long, complicated forms.
  • Form "ownership" is passed between different user roles.
  • Forms move through "states" during their life-cycle with different fields becoming editable/visible.
  • Versioning - The questions on a form may change at a future date, but the answers recorded against them must remain true to what the question was at the time the form was filled out.
  • Reporting across multiple versions of a form. In the case where a few questions on a form have changed, it must be possible to produce reports containing both versions.

The Developer Case

FormBuilder aims to reduce complexity and repetition for the developer by providing the following features:

  • The ability to define long complicated forms as a JSON structure and letting the application markup the HTML.
  • The ability to choose CSS class names applied to elements in markup.
  • Artisan commands to create the required database migrations, models, controllers and to create new form schemas.

The Accessibility Case

Although ultimate responsibility for making an interface accessible lies with the developer, FormBuilder does not stand in the way. Current standards have been followed when defining how the forms are markedup but provisions to override all parts of the system are in place should an edge case need to be implemented.

Form "states"

Each form can have multiple states and each field in the form can be marked-up in 1 of 4 ways in each state.

The possible values are:

  • editable - visible and editable
  • readonly - visible but cannot be edited
  • hidden - not visible but editable (eg. Via custom JavaScript)
  • ignore - not included in the form markup

They can be imagined on a 2x2 matrix of editability and visibility.

            ---------------------
    ^       | hidden | editable |
    |       |--------|----------|
editability | ignore | readonly |
            ---------------------
              visibility -->

Let's use a school homework assignment as a simple example. Imagine a simple quiz form is started by a student who has to fill 3 answer fields. After submission, the form is passed to a teacher, those first 3 answer fields now become read-only and a 4th field called 'feedback' is revealed that only the teacher can fill. When the teacher submits the form, all 4 fields become read-only and neither student nor teacher can edit the fields but both can view them in a read-only state.

In the above example the form has 3 states:

  1. The way the fields are displayed for the student
  2. The way the fields are displayed for the teacher
  3. The way the fields are displayed for the final state

They are set for each field via an object of key-pair values in the schema. The states are given key names, a human description relevant to that form, and the value is how the particular field is displayed.

This is how the states object might be set for the first answer field:

"states": {
    "student-answering": "editable",
    "teacher-marking": "readonly",
    "final-view": "readonly"
}

Legacy state values

The codebase currently contains some support for a number of deprecated values. These use-case-specific values are legacy of the application that FormBuilder was original developed for and will not be supported in future releases.

For reference only and should not be used:

  • editable_if_true_else_ignore
  • editable_if_true_else_readonly
  • hidden-for-learner
  • readonly_for_owner

Display Mode

Display mode describes how the user is interacting with the form during the request. It can only be 1 of the 4 CRUD verbs: creating, reading, updating, deleting.

  • You can set it on an instance of FormBuilder using the ->setDisplayMode(string $verb) method.
  • You can check if FormBuilder is in a particular display mode using ->isDisplayMode(string $verb) method which returns a boolean.
  • You can get it using the ->getDisplayMode() method.

It's mostly implemented simply as an enumerated variable that can be checked to determine things like what button or wording is displayed in a custom component (eg. Does the button say "Create" or "Update").

However, one built-in use of display mode is when fields are being rendered. If a form is being rendered in the display mode reading then fields that are 'editable' in the current state are overridden to be 'readonly'. (Note: The opposite affect is not applied, readonly fields are not forced to editable if the display mode is 'updating').

Getting started

Installation is easy with the following steps:

Add the package to composer.json using the install command:

$ composer require nomensa/form-builder

Add the service provider in config/app.php file:

'providers' => [
    ...
    Nomensa\FormBuilder\FormBuilderServiceProvider::class,
];

Add aliases to config/app.php file:

'aliases' => [
    ...
    'FormBuilder' => Nomensa\FormBuilder\FormBuilder::class,
    'CSSClassFactory' => Nomensa\FormBuilder\BootstrapCSSClassFactory::class,
],
  • Run php artisan formbuilder:install
  • Add routes declaration
  • Run db migrations to rebuild database schema
  • Include the FormBuilder JavaScript script

Creating a basic first form

Run the artisan command to create a form, let's call it "My First Form".

$ php artisan formbuilder:make-form MY-FORM "My First Form"

Now open /app/FormBuilder/Forms/MY-FORM/schema.json in your editor and paste in the following code:

[
 {
   "type": "dynamic",
   "rows": [
     {
       "editing_instructions": "Welcome to my great form."
     },
     {
       "columns": [
         {
           "field": "first-form-name",
           "label": "Name",
           "type": "text",
           "helptext": "What people call you",
           "states": {
             "state-1": "editable",
             "state-2": "readonly"
           }
         }
       ]
     },
     {
       "title": "Time",
       "columns": [
         {
           "field": "first-form-love-kittens",
           "label": "I love kittens",
           "type": "checkbox",
           "states": {
             "state-1": "editable",
             "state-2": "readonly"
            }
         }
       ]
     }
   ]
 }
]

Now we will create an instance of FormBuilder in our controller, with this form loaded in:

<?php
 
namespace App\Http\Controllers;
 
class MyGreatFormController extends Controller
{
    
    ...
    
    public function create() 
    {
        $entryForm = EntryForm::where('slug','my-form')->firstOrFail();
        
        $formVersion = $entryForm->currentFormVersion;
        $formBuilder = $formVersion->getFormBuilder();
        
        // TODO: In a future release this will be tidied so errors are pulled from the session automatically
        $errors = new MessageBag();
        
        $arrSession = session()->all();
        if (isset($arrSession['errors'])) {
            $errors = $arrSession['errors']->getBag('default');
        }
        $formBuilder->errors = $errors;
        
        $formBuilder->setState('editing');
        $formBuilder->setDisplayMode('creating');
        
        return view('form.create', $formBuilder->viewData);
    }
    
}    

And then in the view is where this library really saves you a lot of code!

// form/create.blade.php

<h1>My Great Form</h1>

{{ Form::open() }}

    {!! $formBuilder->markup() !!}
    
    {{ Field::submit('Save') }}

{{ Form::close() }}

TODO: Finish this section to describe FormSubmission creation/reading/updating and deleting.

Contributors

Read CONTRIBUTING.md

Dependencies

FormBuilder depends upon Laravel Collective's HTML and FORM packages.

Artisan commands

FormBuilder provides several Artisan commands to make life easier:

  • formbuilder:install - Creates the required database migrations, models and controllers in your application. It also creates a folder to hold the schemas and another for any blade templates.
  • formbuilder:make-form [Form Code] [Form Title] - Creates a new form schema in file system and accompanying entry in database table.

Layout

FormBuilder assumes a row-column type layout but there is no assumption as to any CSS framework. A Bootstrap class factory is provided out-the-box but if you prefer a different framework or your own custom CSS classes you can write a class that implements CSSClassFactory and switch your alias in config/app.php to refer to that instead.

Field types

As well as the obvious expected types such as text, textarea, select and checkbox (singular), FormBuilder also provides some multi-selects and lend themselves to a more accessible interface:

  • checkboxes - Multiple checkboxes all with a shared name prefix
  • radios - A group of radio buttons all with a shared name

Custom Components

Although most input fields on a form are the usual suspects, sometimes you just need to do something different. For that reason, any form schema can include Custom Components allowing you to do anything.

Firstly, create a Blade template in the folder for FormBuilder Components (eg. /views/components/formbuilder/example-component.blade.php)

Secondly, add a reference to it in your form schema as follows:

[
  {
    "type": "example-component"
  }
]

If your template is going to include any input fields that need to be read in the request, name them in the field-mappings

For example imagine your custom component contains a hidden input element with name="address[gps-location]". The following schema config will ensure it gets read from the request variables.

[
  {
    "type": "logbook-hospital-select",
    "field-mappings": {
      "address.gps-location": "GPS Location"
    }
  }
]

Finally, if you want to re-use the same custom component in multiple forms you may want to inject some variables into it to be used in the Blade. Do this by declaring any variables as key-value pairs in componentData in the schema:

[
  {
    "type": "file-upload",
    "componentData": {
      "instructions": "Please attach an image of a kitten."
    }
  }
]  

Cool edge cases

Because FormBuilder was developed as part of a client-specific application there are some cool edge cases accounted for that other FormBuilder packages do not:

  • Type of radio button where the client required the user to explicitly set a value, no pre-selected.

Database Model Tables

This library contains migrations for 8 tables. These will get auto-discovered by Laravel. If you want to disable discovery you can opt out of package discovery and copy the migrations into your application code.

entry_form_types

Categories for grouping Entry Forms (entry_forms).

entry_forms

Contains the Entry Forms (these could easily just be referred to as "forms"). Each row belongs to a row in entry_form_types.

form_versions

Defines current and legacy versions of a form's schema (What fields it contains) to accommodate both future changes and backwards compatibility for existing submissions. Each row belongs to a row in entry_forms.

workflows (deprecating)

This was originally introduced to store descriptions of workflow so an instance can belong to it. A workflow would describe if it was started by one role and then passed to another or vice versa. In practice this was confusing and workflow is much better represented by good use of form submission status (form_submission_status_id) or a meta-field on the form that is set by the controller.

form_instances (deprecating)

This is the link between form_submissions and form_versions and will be removed in future versions.

form_submission_statuses

Named stages that a form moves through in it's life.

form_submissions

A specific submission of data made by a user for a particular version of a form. Currently each row belongs to a row in form_instances but when that is deprecated it will belong to a row in form_versions.

form_submission_fields

Holds the data for a specific field of a submission of a form. Each row belongs to a row in form_submissions.

form_associations

A pivot table for associating form_submissions with other form_submissions. The type column is a human-friendly slug that describes the relationship of the root form submission to the destination form submission (eg. a_goal_of, or a_reflection_on).