abivia/nextform

Abivia Next Form Generator

1.10.0 2021-03-28 04:16 UTC

README

Latest Updates

  • Added Laravel-style validation rules. Implemented compact JSON forms for the Data\Presentation, Data\Store classes, and roles attribute of Data\Property.
  • Automatic labels setting in a segment simplifies message ID based translations.
  • Extended and simplified role-based field visibility.
  • Fixed semi-incoherent README.md

About NextForm

NextForm is a PHP based form generator with optional Laravel integration (see NextForm-Laravel). NextForm is designed primarily for applications that need to modify form definitions at runtime. Forms are defined either on the fly or by files in JSON or YAML. There is no form layout GUI.

NextForm can also write its data structures in JSON. You can build a form on the fly and save it for later use.

Form definitions are the same across rendering engines. Currently simple HTML and Bootstrap 4 engines are available, with more planned.

Forms are defined by two data structures. The Schema structure details the data that can appear on a form. The Form structure specifies the order and layout of those data elements. In the simplest case, the essence of a form definition is a list of elements defined in the schema:

"elements": ["firstName", "lastName", "email"]

If your design team decides they want the email address first, simply rearrange elements:

"elements": ["email", "firstName", "lastName"]

If they want the first and last names on one line:

"elements": [
    "email",
    {
        "type": "cell",
        "elements": ["firstName", "lastName"]
    }
]

NextForm is designed to eliminate other common problems:

  • No templating. There is no need to mix HTML and PHP or HTML and a template language. Inject your form into your page in one line with NextForm::body();.
  • No CSS. Get a decent looking form with basic settings, let NextForm handle the CSS. In Bootstrap, switching between a vertical and horizontal form layout is accomplished with one setting (layout:vertical or layout:horizontal).
  • Slightly different forms for different roles. If you have different user groups with different access roles, odds are you have multiple forms that have very similar definitions, one for each role. NextForm's role-based field permissions let you fold all these variants into one easy-to-maintain version.
  • Since forms are often exclusively defined in an application's view, they are traditionally inflexible and difficult to transform. NextForm provides for the programmatic transformation of forms before they are generated (eg. add a field, add/remove options in a radio group, etc.). This gives MVC applications capabilities that are usually only implemented via client-side frameworks like Vue.
  • Frameworks can do a good job of presentation and validation, but tend to not offer support for client-side interactions between form elements.

Too many forms are brittle, full of duplication, difficult to maintain, and a barrier to rapid development. NextForm offers good reusability, concise form specifications, fine grained access control, and portability across client-side frameworks.

Installation

Install with composer: composer require abivia/nextform

The tests in this package contain several examples of usage.

For Laravel integration require abivia/nextform-laravel. A simple but evolving (and thus potentially unstable) example of NextForm in use in Laravel can be found at NextForm Laravel Demo.

Examples

In the absence of a tutorial, the best examples can be found in the integrated test folder at tests/integrated/. AccessTest.php contains a basic schema and form, but does nothing useful. MemberTest.php is far more complex and illustrates most of NextForm's capabilities.

Note that if you manually create a tests/logs folder then most of the tests will write their output there.

Development Status

NextForm remains under active development. As of mid-summer 2020, NextForm has been successfully deployed in a mid-sized application. As that application pushes the limits of the current implementation minor bugs are being worked out and new features are being implemented (most notably plug-in Captcha fields and the simplified access control roles). Tests provide code coverage near 85%. The API should be stable or at least any changes should be backwards compatible. Note that this is not a guarantee but a statement of intent.

The next render target for NextForm will be Vue. The current architecture was developed with this target in mind, but it is highly likely that actual implementation will force changes, some of which might impact existing deployments. There is no timeline for this. If you want to help, get in touch via Gitlab.

NextForm Architecture

NextForm separates form generation into a data schema, form definitions, and rendering engines. NextForm also supports field-level access control and translations. NextForm's data and schema definitions are JSON or YAML based, loaded into structures that can be manipulated in PHP. Forms can also be defined entirely in PHP, allowing for application-driven form generation.

A Schema defines presentation and validation information for data that can be displayed on a form. A schema can contain multiple segments. Each segment typically represents a data model or a database table. Separating data elements allows for uniform presentation and validation of that data across multiple forms. Change the definition in one place and NextForm will change it everywhere.

Forms are composed of elements. These elements can be connected to data in schemas. In the simplest case, a form can be created from a list of data elements and a button. Forms can also define events that define interactions between elements. Using this mechanism form elements can be hidden or made visible based on user actions.

Many applications also need to maintain several variations of similar forms. Information contained on a form seen by a user with guest access might contain a subset of information on a manager level form, and an administrator might be able to change information that is only read-only for a manager. In most cases, this requires the implementation of multiple forms with significant duplication. NextForm's flexible role based access levels access control system allows a developer to use one form definition for multiple user roles.

Form Layout Overview

A form is a collection of form elements.

Form elements can be simple or compound.

The simple elements are static text, buttons (submit, reset, etc.), HTML, and input elements connected to data.

There are two types of compound elements: cells and groups. A cell contains a set of elements that are presented as a single unit. A section is a collection of related fields. Sections can contain cells, cells cannot contain sections.

All elements can be members of zero or more groups. Groups are used to enable/disable and hide/show form elements depending on user activity.

Data Schema Overview

A schema defines the data objects that can be assembled into forms.

The schema has provisions for data visibility based on a simple access control system. Data objects can be read/write, read only, hidden, or not present depending on the user's permissions. Schemas can be loaded from JSON or YAML files, or be generated by the application.

Each schema can have multiple segments. The intent is that segments map to models or other data structures used to persist data, but this is not a requirement. Each segment can contain one or more scalar objects.

Objects must have a unique name within the segment. Each object can define characteristics including the storage format and size, how the data should be displayed, text labels that describe it, which roles can see and modify the object and more.

Translation Overview

NextForm translation uses Laravel's translator interface. Specifically the get() method. A translation instance is not required. If none is provided, no translations occur.

This should make adapting to other translation systems straightforward.

Access Control Overview

Each object in a schema can define arbitrary access roles (or no roles at all).

The access permissions are, in order of precedence:

  • None. The object is not rendered on the form.
  • Hide. The object is embedded in the form as a hidden element.
  • Mask. The object is visible but the data is masked and not editable.
  • View. The object is displayable but not editable.
  • Write. The object is visible and modifiable by the user.

Role definitions include a wildcard role ("") that defines the default access level. If no roles are defined the default wildcard is ":write". If other roles are defined and no wildcard is specified, the default is "*:none". There are no other rules governing role names. An access request for any role that is not explicitly defined will be assigned the access level given by the wildcard role.

The role can be an array of role names. In this case, the role that returns the greatest level of access is returned.

Sample role definition

"roles": {
    "*": "mask",
    "guest": "hide",
    "admin": "view",
    "owner": "write"
}

Roles can also be expressed as a more compact compound string. This will produce the same definition as above:

"roles": "*:mask|guest:hide|admin:view|owner:write"

When a role is passed as an option to the form render engine, NextForm will change the generated form to match the rules for the role.

Note: NextForm 1.x includes an overly complex access control system that is deprecated. If no role is passed to form generation, this system is used. The default ACL, NullAccess, grants write permission to everything.

Rendering Overview

NextForm's schema and form definitions have been designed with the intent of being as independent of the final output as possible. By separating this information from the form implementation, the hope is that NextForm can support not only common HTML/CSS frameworks such as Bootstrap, but also client-side Javascript systems such as Vue. The RenderInterface specifies a simple requirement for the implementation of additional environments.

NextForm currently provides two render engines:

  • The Bootstrap4 engine generates event-capable, accessible forms. Render options support horizontal and vertical layouts and customization features.
  • The SimpleHtml engine generates basic HTML forms with no native event processing.

The Schema Data Structure

At the top level a schema has two properties, default and segments.

default

The default property specifies common values for all other elements. At this point that includes only labels. Thus the default can be used to set a common error message for all objects. This message can be overridden in an object definition.

segments

A segment contains a name, a list of objects, and an optional list of the object names that constitute a primary key for the data in the segment, and other properties described here.

segments.primary

This is either a string consisting of one object name or an array of object names.

segments.objects

Each object has these properties:

  • name The name of the object. Names must be unique within a segment.
  • description An optional string that describes the object.
  • labels Defines labels associated with the object.
  • population If the object can take values from a finite set of possible values, for example in the case of radio buttons, this defines the allowable values.
  • presentation The presentation describes the normal representation of the object on the form.
  • store The store describes the format and size of the data when it is stored.
  • validation Validation specifies the rules for an acceptable value.

segments.objects.labels

Labels can be a simple string or an object. If labels is a string, it is used as the heading property. The object can have these properties, all of which are optional:

  • accept Text to be displayed when a field passes validation.
  • after Text that immediately follows the input element. For example, if the object is intended to be an amount in whole dollars, this might be '.00' to aide the user in entering a whole number.
  • before Text that immediately precedes the input element. For example, this might be 'https:' to indicate a link, or '@' for a social media handle.
  • confirm If this is a string, it is the heading to be displayed when this is a confirmation field. For example 'Confirm password'. If it is an object then it can have any label property except confirm. These strings will override the parent strings on a confirmation field.
  • error The text to be displayed when a user input fails validation.
  • heading A title for the input object.
  • help Text to be displayed to assist the user in entering a value.
  • inner Text displayed inside the input element, for example as a placeholder.
  • mask Text that overrides the default mask string (*****).
  • translate A flag that indicates if the labels are to be translated. The default value is true.

Each of the text properties has a corresponding html flag, so for example setting help.html true means that the text in help will be treated as raw HTML and not escaped.

segments.objects.population

A population has the following properties:

  • list For fixed lists, this is a list of options, the possible values that the object can adopt (see segments.objects.population.option).
  • sidecar The sidecar is an arbitrary JSON encoded string that will be attached to the object in the generated code.
  • source How the population is created. Currently the only valid source is 'fixed'. The possible values are contained in the list property.
  • translate A flag that indicates if the values are to be translated. The default value is true.

segments.objects.population.option

The possible values in a population are stored in an Option. Options have these properties:

  • enabled A flag that indicates if this option is currently selectable. Default is true.
  • label The text displayed to the user for this option.
  • memberOf A list of groups that this option belongs to.
  • name An optional name for the option that can be used to refer to it on the rendered form.
  • sidecar The sidecar is an arbitrary JSON encoded string that will be attached to the object in the generated code.
  • value The value returned when this option has been selected at the time of form submission.

Options also support a shorthand notation, a string of the form "label:value". Thus list: ["One:1", "Two:2", "More:X"] is a valid list definition. Strings and object forms can be mixed in the same list.

segments.objects.presentation

The presentation specifies how an object should be displayed on the form. Not all properties make sense for all input types. Anything that doesn't make sense is ignored. A presentation has these properties:

  • cols The number of columns, using a 12 column grid system, to use to display the object.
  • confirm A flag indicating if NextForm should generate a second input to confirm that the user entered a correct value.
  • rows The number of rows to use when displaying a combo, text area, etc.
  • type The input type. Possible values include: 'button', 'checkbox', 'color', 'date', 'datetime-local', 'email', 'file', 'hidden', 'image', 'month', 'number', 'password', 'radio', 'range', 'reset', 'search', 'select', 'submit', 'tel', 'text', 'textarea', 'time', 'url', and 'week'.

The presentation can also be expressed as a compact string. Examples: email|confirm and textarea|cols:50|rows:5. The order of the components is not significant, confirm|number is the same as number|confirm.

segments.objects.roles

Roles, which are optional, define the object's visibility for a set of arbitrary roles. If no roles are present, the object will always have write access. If roles are present, there must be a wildcard role ("*") to define the default access level. Any other roles can be defined to use one of the access levels (none, hide, mask, view, or write).

See the Access Control Overview section for more detail.

segments.objects.store

The store defines how the object will be persisted in storage. A store has these properties:

  • size A string indicating the maximum size of the object. This is often but not always an integer.
  • `type' One of 'blob', 'date', 'decimal', 'float', 'int', 'string', or 'text'.

The store can also be expressed as a compact string. Examples: string|size:200 and decimal|size:10,2. The order of the parts of a compact string are not significant.

segments.objects.validation

The validation defines acceptable values for the object. Validation can be a simple string or an object. If labels is a string, it is used as the rules property. The validation properties are:

  • accept For file type presentations, this is an array of acceptable file patterns
  • capture For file types that are image or video, sets the data source.
  • maxLength The maximum length of text based inputs.
  • maxValue The maximum value of numeric based inputs.
  • minLength The minimum length of text based inputs.
  • minValue The minimum value of numeric based inputs.
  • multiple A flag that is true if an object can accept multiple values. Default false.
  • pattern A Javascript regular expression that defines acceptable text values.
  • required A flag indicating that a value is required for this object. Default false.
  • rules See below for details.
  • step The size of increments/decrements for numeric and range inputs.
  • translatePattern A flag indicating that the pattern should be translated. Default false.

NextForm v1.3.1 introduces a rules property to the validation object that mirrors Laravel's validation syntax. NextForm will extract properties from a rules string and use them for validation. The rules definition is invariant, any rules not recognized by NextForm remain unchanged, so the string can be passed to other validation libraries.

  • array Causes subsequent rules that affect minLength, maxLength, minValue and maxValue to be ignored.
  • date Causes the min and max rules to map to minValue and maxValue respectively.
  • digits Sets minLength and maxLength
  • digits_between Sets minLength and maxLength
  • filled Maps to the required property set true.
  • integer Causes the min and max rules to map to minValue and maxValue respectively.
  • mimes Maps to the accept property. Each extension ext adds *.ext to the accept list.
  • nullable Sets the required property false.
  • numeric Causes the min and max rules to map to minValue and maxValue respectively.
  • regex Maps to the pattern property.
  • regex_translate Maps to the translatePattern property.
  • string Causes the min, max, size and between rules to map to minLength and maxLength respectively.
  • All remaining validation properties (eg. step) have the same name in the rules string.

Note: If you want to keep the rules string compatible with Laravel's validation system, either maintain rules that have no Laravel equivalent as separate properties or extend Laravel to accommodate NextForm rules.

segments.autoLabels

The autolabels property is useful in localized environments that use the object name as part of a translation message key. When provided, an asterisk in the autoLabels definition will be replaced with the object name and applied each object in the segment. If the object already has a label, it will not be overwritten.

Sample autoLabels definition:

    "autoLabels": {
        "heading": "*.head",
        "help": "*.assist",
    }

This would cause a name object to have a heading of "name.head" and a help label of "name.assist".

The Form Data Structure

In the simplest case, a form contains a name, an optional default segment, and a list of objects. This is a valid form definition with three elements:

{
    "name": "accessForm",
    "useSegment": "accessTest",
    "elements": [
        "sysId", "username", "phone"
    ]
}

The form requires a schema that defines each of the field elements.

Forms can also define other element types:

  • button Button functions are submit, reset, and button (linked to some JS)
  • captcha Nextform offers plug-in Captcha interface and a basic native implementation.
  • cell A cell is a single-row container for one or more other elements. A cell cannot contain other cell or section elements.
  • html A HTML element injects content directly into the output stream.
  • section A section is a multi-row container for other elements. Sections can contain cells, but not other sections.
  • static Fixed text, either plain or raw HTML that appears as an element that is part of the form layout.

Elements on a form can override some object properties from the schema, for example the enabled/disabled state, and labels.