Mojolicious::Plugin::FormFieldsFromJSON - create form fields based on a definition in a JSON file
version 1.01
# Mojolicious
$self->plugin('FormFieldsFromJSON');
# Mojolicious::Lite
plugin 'FormFieldsFromJSON';
Mojolicious::Plugin::FormFieldsFromJSON is a Mojolicious plugin.
Mojolicious::Plugin::FormFieldsFromJSON - create form fields based on a definition in a JSON file
version 0.32
You can configure some settings for the plugin:
-
dir
The directory where the json files for form field configuration are located
$self->plugin( 'FormFieldsFromJSON' => { dir => '/home/mojo/fields', });
You can also pass an arrayreference with directory names. This will help when you store the JSON files where your templates are...
$self->plugin( 'FormFieldsFromJSON' => { dir => [ '/home/mojo/templates/admin/json', '/home/mojo/templates/author/json', '/home/mojo/templates/guest/json', ] });
-
template
With template you can define a template for the form fields.
$self->plugin( 'FormFieldsFromJSON' => { template => '<label for="<%= $id %>"><%= $label %>:</label><div><%= $field %></div>', });
See Templates.
-
templates
With template you can define type specific templates for the form fields.
plugin 'FormFieldsFromJSON' => { templates => { text => '<%= $label %>: <%= $field %>', }, };
See Templates.
-
global_attributes
With global_attributes, you can define attributes that should be set for every field (except hidden fields)
plugin 'FormFieldsFromJSON' => { global_attributes => { class => 'important-field', }, };
So with this configuration
[ { "label" : "Name", "type" : "text", "name" : "name" }, { "label" : "Background", "type" : "text", "name" : "background" } ]
You get
<input class="important-field" id="name" name="name" type="text" value="" /> <input class="important-field" id="background" name="background" type="text" value="" />
-
alias
Using aliases can help you a lot. Given you want to have several forms where the user can define a color (e.g. by using bootstrap-colorpicker), you don't want to define the special templates in each form. Instead you can define those fiels as type "color" and use an alias:
plugin 'FormFieldsFromJSON' => { template => '<%= $label %>: <%= $field %>', templates => { color => '<%= $label %> (color): <%= $field %>', }, alias => { color => 'text', }, };
The alias defines that "color" fields are "text" fields.
So with this configuration
[ { "label" : "Name", "type" : "text", "name" : "name" }, { "label" : "Background", "type" : "color", "name" : "background" } ]
You get
<label for="name">Name:</label><div><input id="name" name="name" type="text" value="" /></div> <label for="background">Background (color):</label><div><input id="background" name="background" type="text" value="" /></div>
-
translate_labels
If translate_labels is true, the labels for the templates are translated. You have to provide a translation_method|Mojolicious::Plugin::FormFieldsFromJSON/Translation_method, too.
plugin 'FormFieldsFromJSON' => { template => '<%= $label %>: <%= $field %>', translate_labels => 1, translation_method => \&loc, };
For more details see Translation|Mojolicious::Plugin::FormFieldsFromJSON/Translation.
-
translation_method
If translate_labels is true, the labels for the templates are translated. You have to provide a translation_method|Mojolicious::Plugin::FormFieldsFromJSON/Translation_method, too.
plugin 'FormFieldsFromJSON' => { template => '<%= $label %>: <%= $field %>', translate_labels => 1, translation_method => \&loc, };
For more details see Translation|Mojolicious::Plugin::FormFieldsFromJSON/Translation.
-
types
If you have written a plugin that implements a new "type" of input field, you can allow this type by passing types when you load the plugin.
plugin 'FormFieldsFromJSON' => { types => { 'testfield' => 1, }, };
Now you can use
[ { "label" : "Name", "type" : "testfield", "name" : "name" } ]
For more details see Additional Types.
form_fields
returns a string with all configured fields "translated" to HTML.
$controller->form_fields( 'formname' );
Given this configuration:
[
{
"label" : "Name",
"type" : "text",
"name" : "name"
},
{
"label" : "City",
"type" : "text",
"name" : "city"
}
]
You'll get
<input id="name" name="name" type="text" value="" />
<input id="city" name="city" type="text" value="" />
Instead of a formname, you can pass a config:
$controller->form_fields(
[
{
"label" : "Name",
"type" : "testfield",
"name" : "name"
}
]
);
This way, you can build your forms dynamically (e.g. based on database entries).
This helper validates the input. It uses the Mojolicious::Validator::Validation and it validates all fields defined in the configuration file.
For more details see Validation.
This method returns a list of forms. That means the filenames of all .json files in the configured directory.
my @forms = $controller->forms;
The filenames are returned without the file suffix .json.
fields()
returns a list of fields (label or name).
my @fieldnames = $controller->fields('formname');
If your configuration looks like
[
{
"label" : "Email",
"name" : "email",
"type" : "text"
},
{
"name" : "password",
"type" : "password"
}
]
You get
(
Email,
password
)
This plugin supports several form fields:
- text
- checkbox
- radio
- select
- textarea
- password
- hidden
Those fields have the following definition items in common:
-
name
The name of the field. If you do not pass an id for the field in the attributes-field, the name is also taken for the field id.
-
label
If a template is used, this value is passed for
$label
. If the translation feature is used, the label is translated. -
type
One of the above mentioned types. Please note, that you can add own types.
-
data
For text, textarea, password and hidden this is the value for the field. This can be set in various ways:
-
-
Data passed in the code like
$c->form_fields( 'form', fieldname => { data => 'test' } );
-
-
-
Data passed via stash
$c->stash( fieldname => 'test' );
-
-
- Data in the request
-
- Data defined in the field configuration
-
-
Data passed via stash - part two
$c->stash( any_name => { fieldname => 'test' } ); $c->form_fields( 'form', from_stash => 'any_name' );
-
For select, checkbox and radio fields, data contains the possible values.
-
-
attributes
Attributes of the field like "class":
attributes => { class => 'button' }
If global_attributes are defined, then the values are added, so that
plugin( 'FormFieldsFromJSON' => { global_attributes => { class => 'button-danger', } });
and the attributes field as shown, then the field has two classes: button and button-danger. In the field the classes mentioned in field config come first.
<button class="button button-danger" ...>
The following sections should give you an idea what's possible with this plugin
With type text you get a simple text input field.
This is the configuration for a simple text field:
[
{
"label" : "Name",
"type" : "text",
"name" : "name"
}
]
And the generated form field looks like
<input id="name" name="name" type="text" value="" />
If you want to set a CSS class, you can use the attributes
field:
[
{
"label" : "Name",
"type" : "text",
"name" : "name",
"attributes" : {
"class" : "W75px"
}
}
]
And the generated form field looks like
<input class="W75px" id="name" name="name" type="text" value="" />
Sometimes, you want to predefine a value shown in the text field. Then you can
use the data
field:
[
{
"label" : "Name",
"type" : "text",
"name" : "name",
"data" : "default value"
}
]
This will generate this input field:
<input id="name" name="name" type="text" value="default value" />
When you have a list of values for a select field, you can define an array reference:
[
{
"type" : "select",
"name" : "language",
"data" : [
"de",
"en"
]
}
]
This creates the following select field:
<select id="language" name="language">
<option value="de">de</option>
<option value="en">en</option>
</select>
You can define
[
{
"type" : "select",
"name" : "language",
"data" : [
"de",
"en"
],
"selected" : "en"
}
]
This creates the following select field:
<select id="language" name="language">
<option value="de">de</option>
<option value="en" selected="selected">en</option>
</select>
If a key named as the select exists in the stash, those values are preselected (this overrides the value defined in the .json):
$c->stash( language => 'en' );
and
[
{
"type" : "select",
"name" : "language",
"data" : [
"de",
"en"
]
}
]
This creates the following select field:
<select id="language" name="language">
<option value="de">de</option>
<option value="en" selected="selected">en</option>
</select>
[
{
"type" : "select",
"name" : "languages",
"data" : [
"de",
"en",
"cn",
"jp"
],
"multiple" : 1,
"size" : 3
}
]
This creates the following select field:
<select id="languages" name="languages" multiple="multiple" size="3">
<option value="cn">cn</option>
<option value="de">de</option>
<option value="en">en</option>
<option value="jp">jp</option>
</select>
[
{
"type" : "select",
"name" : "languages",
"data" : [
"de",
"en",
"cn",
"jp"
],
"multiple" : 1,
"selected" : [ "en", "de" ]
}
]
This creates the following select field:
<select id="language" name="language">
<option value="cn">cn</option>
<option value="de" selected="selected">de</option>
<option value="en" selected="selected">en</option>
<option value="jp">jp</option>
</select>
[
{
"type" : "select",
"name" : "language",
"data" : {
"de" : "German",
"en" : "English"
}
}
]
This creates the following select field:
<select id="language" name="language">
<option value="en">English</option>
<option value="de">German</option>
</select>
[
{
"type" : "select",
"name" : "language",
"data" : {
"EU" : {
"de" : "German",
"en" : "English"
},
"Asia" : {
"cn" : "Chinese",
"jp" : "Japanese"
}
}
}
]
This creates the following select field:
<select id="language" name="language">
<option value="en">English</option>
<option value="de">German</option>
</select>
[
{
"type" : "select",
"name" : "languages",
"data" : [
"de",
"en",
"cn",
"jp"
],
"multiple" : 1,
"disabled" : [ "en", "de" ]
}
]
This creates the following select field:
<select id="language" name="language">
<option value="cn">cn</option>
<option value="de" disabled="disabled">de</option>
<option value="en" disabled="disabled">en</option>
<option value="jp">jp</option>
</select>
For radiobuttons, you can use two ways: You can either configure
form fields for each value or you can define a list of values in
the data
field. With the first way, you can create radiobuttons
where the template (if any defined) is applied to each radiobutton.
With the second way, the radiobuttons are handled as one single
field in the template.
Given the configuration
[
{
"label" : "Name",
"type" : "radio",
"name" : "type",
"data" : "internal"
}
]
You get
With the configuration
[
{
"label" : "Name",
"type" : "radio",
"name" : "type",
"data" : "internal"
},
{
"label" : "Name",
"type" : "radio",
"name" : "type",
"data" : "external"
}
]
You get
And with
[
{
"label" : "Name",
"type" : "radio",
"name" : "type",
"data" : ["internal", "external" ]
}
]
You get
Define template:
plugin 'FormFieldsFromJSON' => {
dir => './conf',
template => '<%= $label %>: <%= $form %>';
};
Config:
[
{
"label" : "Name",
"type" : "radio",
"name" : "type",
"data" : "internal"
},
{
"label" : "Name",
"type" : "radio",
"name" : "type",
"data" : "external"
}
]
Fields:
Name: <input id="type" name="type" type="radio" value="internal" />
Name: <input id="type" name="type" type="radio" value="external" />
Same template definition as above, but given this field config:
[
{
"label" : "Name",
"type" : "radio",
"name" : "type",
"data" : ["internal", "external" ]
}
]
You get this:
Name: <input id="type" name="type" type="radio" value="internal" />
<input id="type" name="type" type="radio" value="external" />
Config:
[
{
"label" : "Name",
"type" : "radio",
"name" : "type",
"data" : ["internal", "external" ],
"selected" : ["internal"]
}
]
Field:
<input checked="checked" id="type" name="type" type="radio" value="internal" />
<input id="type" name="type" type="radio" value="external" />
When you want to add some HTML code after every element - e.g. a <br />
-
you can use after_element
[
{
"label" : "Name",
"type" : "radio",
"name" : "type",
"after_element" : "<br />",
"data" : ["internal", "external" ]
}
]
Fields:
<input id="type" name="type" type="radio" value="internal" />
<br /><input id="type" name="type" type="radio" value="external" />
<br />
When you want to show the value as a label, you can use show_value.
[
{
"label" : "Name",
"type" : "radio",
"name" : "type",
"show_value" : 1,
"data" : ["internal", "external" ]
}
]
Creates
<input id="type" name="type" type="radio" value="internal" /> internal
<input id="type" name="type" type="radio" value="external" /> external
If you want to show the "sublabels" and want them to be translated, you can use translate_sublabels
[
{
"label" : "Name",
"type" : "radio",
"name" : "type",
"show_value" : 1,
"translate_sublabels" : 1,
"data" : ["internal", "external" ]
}
]
Given this plugin is used this way:
plugin 'FormFieldsFromJSON' => {
dir => File::Spec->catdir( dirname( __FILE__ ) || '.', 'conf' ),
translation_method => \&loc,
};
sub loc {
my ($c, $value) = @_;
my %translation = ( internal => 'intern', external => 'extern' );
return $translation{$value} // $value;
};
You'll get
<input id="type" name="type" type="radio" value="internal" /> intern
<input id="type" name="type" type="radio" value="external" /> extern
For checkboxes, you can use two ways: You can either configure
form fields for each value or you can define a list of values in
the data
field. With the first way, you can create checkboxes
where the template (if any defined) is applied to each checkbox.
With the second way, the checkboxes are handled as one single
field in the template.
Given the configuration
[
{
"label" : "Name",
"type" : "checkbox",
"name" : "type",
"data" : "internal"
}
]
You get
With the configuration
[
{
"label" : "Name",
"type" : "checkbox",
"name" : "type",
"data" : "internal"
},
{
"label" : "Name",
"type" : "checkbox",
"name" : "type",
"data" : "external"
}
]
You get
And with
[
{
"label" : "Name",
"type" : "checkbox",
"name" : "type",
"data" : ["internal", "external" ]
}
]
You get
Define template:
plugin 'FormFieldsFromJSON' => {
dir => './conf',
template => '<%= $label %>: <%= $form %>';
};
Config:
[
{
"label" : "Name",
"type" : "checkbox",
"name" : "type",
"data" : "internal"
},
{
"label" : "Name",
"type" : "checkbox",
"name" : "type",
"data" : "external"
}
]
Fields:
Name: <input id="type" name="type" type="checkbox" value="internal" />
Name: <input id="type" name="type" type="checkbox" value="external" />
Same template definition as above, but given this field config:
[
{
"label" : "Name",
"type" : "checkbox",
"name" : "type",
"data" : ["internal", "external" ]
}
]
You get this:
Name: <input id="type" name="type" type="checkbox" value="internal" />
<input id="type" name="type" type="checkbox" value="external" />
Config:
[
{
"label" : "Name",
"type" : "checkbox",
"name" : "type",
"data" : ["internal", "external" ],
"selected" : ["internal"]
}
]
Field:
<input checked="checked" id="type" name="type" type="checkbox" value="internal" />
<input id="type" name="type" type="checkbox" value="external" />
When you want to add some HTML code after every element - e.g. a <br />
-
you can use after_element
[
{
"label" : "Name",
"type" : "checkbox",
"name" : "type",
"after_element" : "<br />",
"data" : ["internal", "external", "unknown" ]
}
]
Fields:
<input id="type" name="type" type="checkbox" value="internal" />
<br /><input id="type" name="type" type="checkbox" value="external" />
<br /><input id="type" name="type" type="checkbox" value="unknown" />
<br />
When you want to show the value as a label, you can use show_value.
[
{
"label" : "Name",
"type" : "checkbox",
"name" : "type",
"show_value" : 1,
"data" : ["internal", "external" ]
}
]
Creates
<input id="type" name="type" type="checkbox" value="internal" /> internal
<input id="type" name="type" type="checkbox" value="external" /> external
If you want to show the "sublabels" and want them to be translated, you can use translate_sublabels
[
{
"label" : "Name",
"type" : "checkbox",
"name" : "type",
"show_value" : 1,
"translate_sublabels" : 1,
"data" : ["internal", "external" ]
}
]
Given this plugin is used this way:
plugin 'FormFieldsFromJSON' => {
dir => File::Spec->catdir( dirname( __FILE__ ) || '.', 'conf' ),
translation_method => \&loc,
};
sub loc {
my ($c, $value) = @_;
my %translation = ( internal => 'intern', external => 'extern' );
return $translation{$value} // $value;
};
You'll get
<input id="type" name="type" type="checkbox" value="internal" /> intern
<input id="type" name="type" type="checkbox" value="external" /> extern
This type is very similar to text.
This is the configuration for a simple text field:
[
{
"type" : "textarea",
"name" : "message",
"data" : "Current message"
}
]
And the generated form field looks like
<textarea id="message" name="message">Current message</textarea>
This is the configuration for a simple text field:
[
{
"type" : "textarea",
"name" : "message",
"data" : "Current message",
"attributes" : {
"cols" : 80,
"rows" : 10
}
}
]
And the generated textarea looks like
<textarea cols="80" id="message" name="message" rows="10">Current message</textarea>
This type is very similar to text. You can use the very same settings as for text fields, so we show only a simple example here:
This is the configuration for a simple text field:
[
{
"type" : "password",
"name" : "user_password"
}
]
And the generated form field looks like
<input id="user_password" name="password" type="password" value="" />
Especially when you work with frameworks like Bootstrap, you want to
your form fields to look nice. For that the form fields are within
div
s or other HTML elements.
To make your life easier, you can define templates. Either a "global" one, a type specific template or a template for one field.
For hidden fields, no template is applied!
When you load the plugin this way
$self->plugin( 'FormFieldsFromJSON' => {
template => '<label for="<%= $id %>"><%= $label %>:</label><div><%= $field %></div>',
});
and have a configuration that looks like
You get
<label for="name">Name:</label><div><input id="name" name="name" type="text" value="" /></div>
<label for="password">Password:</label><div><input id="password" name="password" type="text" value="" /></div>
When you want to use a different template for select fields, you can use a different template for that kind of fields:
plugin 'FormFieldsFromJSON' => {
dir => File::Spec->catdir( dirname( __FILE__ ) || '.', 'conf' ),
template => '<label for="<%= $id %>"><%= $label %>:</label><div><%= $field %></div>',
templates => {
select => '<%= $label %>: <%= $field %>',
},
};
With a configuration file like
[
{
"label" : "Name",
"type" : "text",
"name" : "name"
}
{
"label" : "Country",
"type" : "select",
"name" : "country",
"data" : [ "au" ]
}
]
You get
<label for="name">Name:</label><div><input id="name" name="name" type="text" value="" /></div>
Country: <select id="country" name="country"><option value="au">au</option></select>
When you want to use a different template for a specific field, you can use the
template
field in the configuration file.
plugin 'FormFieldsFromJSON' => {
dir => File::Spec->catdir( dirname( __FILE__ ) || '.', 'conf' ),
template => '<label for="<%= $id %>"><%= $label %>:</label><div><%= $field %></div>',
};
With a configuration file like
[
{
"label" : "Name",
"type" : "text",
"name" : "name"
}
{
"label" : "Country",
"type" : "select",
"name" : "country",
"data" : [ "au" ],
"template" : "<%= $label %>: <%= $field %>"
}
]
You get
<label for="name">Name:</label><div><input id="name" name="name" type="text" value="" /></div>
Country: <select id="country" name="country"><option value="au">au</option></select>
You get three template variables for free:
-
$label
If a label is defined in the field configuration
-
$field
The form field (HTML)
-
$id
The id for the field. If no id is defined, the name of the field is set.
You can define some validation rules in your config file. And when you call validate_form_fields
, the
fields defined in the configuration file are validated.
Mojolicious::Validator::Validation is shipped with some basic validation checks:
- in
- size
- like
- equal_to
There is Mojolicious::Plugin::AdditionalValidationChecks with some more basic checks. And you can also define your own checks.
The validation field is a hashref where the name of the check is the key and the parameters for the check can be defined in the value:
"validation" : {
"size" : [ 2, 5 ]
},
This will call ->size(2,5)
. If you want to pass a single parameter,
you can set a scalar:
"validation" : {
"equal_to" : "foo"
},
Validation checks are done in asciibetical order.
This is a simple check for the length of a string
[
{
"label" : "Name",
"type" : "text",
"validation" : {
"size" : [ 2, 5 ]
},
"name" : "name"
}
]
Then you can call validate_form_fields
:
my %errors = $c->validate_form_fields( $config_name );
In the returned hash, you get the fieldnames as keys where a validation check fails.
If you have mandatory fields, you can define them as required
[
{
"label" : "Name",
"type" : "text",
"validation" : {
"required" : "name"
},
"name" : "name"
}
]
With the simple configuration seen above, the %error
hash contains the value "1" for
each invalid field. If you want to get a better error message, you can define a hash
in the validation config
[
{
"label" : "Name",
"type" : "text",
"validation" : {
"like" : { "args" : [ "es" ], "msg" : "text must contain 'es'" },
"size" : { "args" : [ 2, 5 ], "msg" : "length must be between 2 and 5 chars" }
},
"name" : "name"
}
]
Examples:
text | error
-------+---------------------------------
test |
t | text must contain 'es'
tester | length must be between 2 and 5 chars
Most webapplications nowadays are internationalized, therefor this module provides some support for translations.
If translate_labels is set to a true value, a template is used and translation_method is given, the labels are translated.
translation_method has to be a reference to a subroutine.
Load and configure the plugin:
plugin 'FormFieldsFromJSON' => {
dir => File::Spec->catdir( dirname( __FILE__ ) || '.', 'conf' ),
template => '<label for="<%= $id %>"><%= $label %>:</label><div><%= $field %></div>',
translate_labels => 1,
translation_method => \&loc,
};
The translation method gets two parameters:
-
the controller object
-
the label
sub loc { my ($c, $value) = @_;
my %translation = ( Address => 'Adresse' ); return $translation{$value} // $value;
};
This can be a more complex subroutine that makes use of any translation framework.
Given this field configuration file:
[
{
"label" : "Address",
"type" : "text",
"name" : "name"
}
]
You'll get
<label for="name">Adresse:</label><div><input id="name" name="name" type="text" value="" /></div>
There is more about internationalization (i18n) than just translation. There are dates, ranges, order of characters etc. But that can't be covered within this single module. There are more Mojolicious plugins that provide more features about i18n:
- Mojolicious::Plugin::I18N
- Mojolicious::Plugin::TagHelpersI18N
- Mojolicious::Plugin::I18NUtils
- Mojolicious::Plugin::CountryDropDown
You can combine these plugins with this plugin. An example is available at the code repository.
The field types supported by this plugin might not enough for you. Then you can create your own plugin and add new types. For example, dates in OTRS are shown as three dropdowns: one for the day, one for the month and finally one for the year.
Wouldn't it be nice to define only one field in your config and the rest is DWIM (Do what I mean)? It would.
So you can write your own Mojolicious plugin where the register subroutine does nothing. And you define
a subroutine called Mojolicious::Plugin::FormFieldsFromJSON::_date
where those dropdowns are created.
Then just do:
plugin 'WhateverYouHaveChosen';
plugin 'FormFieldsFromJSON' => {
types => {
'date' => 1,
},
};
Now you can use
[
{
"label" : "Release date",
"type" : "date",
"name" : "release"
}
]
The subroutine gets these parameters:
-
The plugin object (Mojolicious::Plugin::FormFieldsFromJSON object)
So you can use the methods defined in this plugin, for example to create dropdowns, textfields, ...
-
The controller object (Whatever controller called
form_fields
method)So you can use all the Mojolicious power!
-
The field config
Whatever you defined in you .json config file for that field
-
A params hash
Whatever is passed as parameters to the
form_fields
method.
As an example, you can see Mojolicious::Plugin::FormFieldsFromJSON::Date.
Mojolicious, Mojolicious::Guides, http://mojolicio.us.
The distribution is contained in a Git repository, so simply clone the repository
$ git clone http://github.com/reneeb/Mojolicious-Plugin-FormFieldsFromJSON.git
and change into the newly-created directory.
$ cd Mojolicious-Plugin-FormFieldsFromJSON
The project uses Dist::Zilla
to
build the distribution, hence this will need to be installed before
continuing:
$ cpanm Dist::Zilla
To install the required prequisite packages, run the following set of commands:
$ dzil authordeps --missing | cpanm
$ dzil listdeps --author --missing | cpanm
The distribution can be tested like so:
$ dzil test
To run the full set of tests (including author and release-process tests),
add the --author
and --release
options:
$ dzil test --author --release
Renee Baecker [email protected]
This software is Copyright (c) 2016 by Renee Baecker.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
Renee Baecker [email protected]
This software is Copyright (c) 2018 by Renee Baecker.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)