abivia / nextform
Abivia Next Form Generator
Requires
- php: >=7.4
- abivia/configurable: ^1.0
- doctrine/dbal: ^2.0
- illuminate/translation: ^6.0|^7.0|^8.0
- myclabs/deep-copy: ^1.9
- symfony/yaml: ^5.1@dev
Requires (Dev)
- nunomaduro/phpinsights: ^1.14.0
- phpunit/phpunit: ^9.0-stable
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 exceptconfirm
. 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 patternscapture
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 thepattern
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 affectminLength
,maxLength
,minValue
andmaxValue
to be ignored.date
Causes the min and max rules to map tominValue
andmaxValue
respectively.digits
SetsminLength
andmaxLength
digits_between
SetsminLength
andmaxLength
filled
Maps to therequired
property set true.integer
Causes the min and max rules to map tominValue
andmaxValue
respectively.mimes
Maps to theaccept
property. Each extensionext
adds*.ext
to the accept list.nullable
Sets therequired
property false.numeric
Causes the min and max rules to map tominValue
andmaxValue
respectively.regex
Maps to thepattern
property.regex_translate
Maps to thetranslatePattern
property.string
Causes the min, max, size and between rules to map tominLength
andmaxLength
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.