jazzman/form-html-generator

v1.2.2 2017-09-29 07:48 UTC

This package is auto-updated.

Last update: 2024-04-05 17:58:35 UTC


README

Usage

Namespace import

FormManager is namespaced, but you only need to import a single class into your context:

use FormManager\Builder as F;

Create a field

All HTML5 field types are supported.

//Create an input type="text" element
$name = F::text();

//Use the jQuery syntax to set/get/remove attributes:
$name->attr('name', 'username');

$name->attr([
	'maxlength' => 50,
	'required' => true
]);

$maxlength = $name->attr('maxlength');

$name->removeAttr('required');

//Get/set values
$name->val('MyName');

//Get/set css classes
$name->addClass('cool-input');
$name->removeClass('cool-input');

//Get/set/remove data-* attributes
$name->data('id', 23);
$name->data([
	'name' => 'value',
	'foo' => 'bar'
]);
$foo = $name->data('foo');

$name->removeData('id');

$name->removeData(); //Remove all data

//You can chain various methods:
$email = F::email()->addClass('cool-input')->id('my-email')->val('my@email.com');

//And use the __call() magic method to add attributes:
$email->required(); //same than $email->attr('required', true);
$email->required(false); //same than $email->attr('required', false);

Generate the html code

The html code is created automatically on convert the object into a string:

$name = F::text()->class('my-input')->required();

echo $name; //<input type="text" class="my-input" required>

//print the input with extra attributes
echo $name->addClass('text-input')->placeholder('Your name');

Working with data

Inputs can validate the data depending of the type and other validation attributes:

$url = F::url();

//Set the input as required
$url->required();

//Check if the value is valid
if (!$url->validate()) {
	echo $url->error(); //This value is required
}

//set an invalid url
$url->val('invalid-url');

//check the value and get the error
if (!$url->validate()) {
	echo $url->error(); //This value is not a valid url
}

The inputs can handle custom validators:

$name = F::text();

function isDave($input)
{
	if ($input->val() !== 'dave') {
		throw new FormManager\InvalidValueException('This value must be "dave"');
	}
}

//Add custom validators
$name->addValidator('isDave');

$name->val('tom');

if (!$name->validate()) {
	echo $name->error(); //This value must be "dave"
}

//Remove the validator
$name->removeValidator('isDave');

The load() method is like val() but it handles the data sent by the client:

$name = F::text();

//Add a function to sanitize the data
$name->sanitize(function($value)
{
	return strip_tags($value);
});

//if you use val(), the value remains as is
$name->val('<strong>earl</strong>');
echo $name->val(); //<strong>earl</strong>

//if you use load(), the value will be sanitized
$name->load('<strong>earl</strong>');
echo $name->val(); //earl

Labels

You can use labels with your inputs, just use the property ->label and it will be created automatically. It may also generate an extra label with the error message.

$name = F::text();

//Define a label
$name->label('User name');

//And modify the label using the same syntax than inputs:
$name->label->class('main-label');

//Print all (label + input)
echo $name;

//Print label and inputs separately
echo $name->label . '<br>' . $name->input;

//Use errorLabel to print a secondary label with the validation error:
echo $name->label . '<br>' . $name->input . $name->errorLabel;

Datalist

Datalist are also allowed, just use the datalist() method to set/get values:

$name = F::search();

//Define the datalist values
$name->datalist([
	'female' => 'Female',
	'male' => 'Male'
]);

Custom renders

You can configure how each field must be rendered. First, you need to know all properties of each field:

  • $field->input The input/select/textarea element
  • $field->label The label element
  • $field->errorLabel A secondary label with the validation error
  • $field->datalist An optional datalist instance
  • $field->wrapper A div element containing all the stuff above
$field->render(function ($field) {
	$html = (string) $field->label;
	$html .= '<p>'.$field->input.$field->errorLabel.'</p>';
	$html .= $field->datalist;

	return $html;
});

Special fields

In addition with regular fields (the html5 equivalents: text, textarea, select, datetime, etc...) there are some special fields useful to create more complicate data schemes:

Group

A group is a simple field to store other fields under a name. The following example shows a group of three fields:

$date = F::group([
	'day' => F::number()->min(1)->max(31)->label('Day'),
	'month' => F::number()->min(1)->max(12)->label('Month'),
	'year' => F::number()->min(1900)->max(2013)->label('Year')
]);

//Set values to group
$date->val([
	'day' => 21,
	'month' => 6,
	'year' => 1979
]);

//Get values
$values = $date->val();

//Use array syntax to access to the fields by name
$year = $date['year']->val();

//Add more fields dinamically
$date['hour'] = F::number()->min(0)->max(23)->label('Hour');

//Add other html attributes to the group:
$date->addClass('field-day')->attr(['id' => 'date-field']);

Choose

This field stores other fields with the same name but different values. Useful for radio inputs or to define various submit buttons.

//Create a choose container
$colors = F::choose();

//Add some fields. The keys are the values
$colors->add([
	'red' => F::radio()->label('Red'),
	'blue' => F::radio()->label('Blue'),
	'green' => F::radio()->label('Green')
]);

//Access to the fields by value
$red_radio = $colors['red'];

//Add more fields dinamically
$colors['yellow'] = F::radio()->label('Yellow');

//Set the value
$colors->val('red');

//Get value
$color_choosen = $colors->val();

Collection

It's like a group, but stores a collection of values:

//Create a collection container
$people = F::collection([
	'name' => F::text()->label('Name'),
	'email' => F::email()->label('email'),
	'age' => F::number()->label('Age')
]);

//Set two values
$people->val([
	[
		'name' => 'Xaquín',
		'email' => 'xaquin@email.com',
		'age' => '24'
	],[
		'name' => 'Uxío',
		'email' => 'uxio@email.com',
		'age' => '37'
	]
]);

//Access to the first group of values:
$group = $people[0];

//Access to any field
echo $people[0]['name']->val(); //returns 'Xaquín'

//Push a new value
$people->pushVal([
	'name' => 'Manoela',
	'email' => 'manoela@email.com',
	'age' => '18'
]);

//Returns the group container used as template for each value inserted.
//useful to use the html template in javascript

$template = $people->getTemplate();

echo '<div class="template">'.$template.'</div>';

CollectionMultiple

If you need different types of values in your collection, CollectionMultiple is the answer:

//Create a collectionMultiple container
$article = F::collectionMultiple([
	'section' => [
        'title' => F::text()->label('Title'),
        'text' => F::textarea()->label('Text')
    ],
    'picture' => [
        'caption' => F::text()->label('Caption'),
        'image' => F::file()->label('Image')
    ],
    'quote' => [
        'text' => F::textarea()->label('Text'),
        'author' => F::text()->label('Author')
    ]
]);

//Set values. Note that we need a "type" value to know the type of each row
$article->val([
	[
		'type' => 'section',
		'title' => 'This is the section title',
        'text' => 'Lorem ipsum...',
	],[
		'type' => 'quote',
		'text' => 'You have to learn the rules of the game. And then you have to play better than anyone else.',
		'author' => 'Albert Einstein'
	]
]);

// Note that a hidden input will be created for you to store the group type
$article[0]['type']->val(); //section
$article[0]['type']->attr('type'); //hidden

//Push more values
$article->pushVal([
	'type' => 'section',
	'title' => 'This is another section',
    'text' => 'The world of dogs are better than the cats because...'
]);

//Add new types
$article->add([
	'video' => [
		'title' => F::text()->label('Title'),
		'video' => F::url()->label('Youtube url')
	]
]);

//Returns an array with all templates used
$templates = $article->getTemplate();

foreach ($templates as $name => $template) {
	echo '<div class="template-'.$name.'">'.$template.'</div>';
}

Loader

The main aim of the loader field is to separate the way to load the data with the way to store and keep this data once it's loaded. Let's say for example, an input type file: if a file is loaded, the value is an array with the same structure than any $_FILES value, but if user doesn't upload anything, the value is empty. So, what is supposed to do in this case? remove the file or keep the previous value? The loader field has two fields: a "loader" field and a "field" field. The first one is responsive to load the new values (for example, it can be an input type file) and the second keeps the previous value (for example, it can be an input type hidden, or text, etc). Let's see an example:

$fileUpload = F::loader([
    'loader' => F::file()->label('Upload a file here'),
    'field' => F::hidden() //this hidden input stores the old value
]);

//Set a value
$fileUpload->val('my-file.png');

//we have this value
echo $fileUpload->val(); //my-file.png

//load empty data
$fileUpload->load(null);

//Nothing was loaded, so we keep the old value
echo $fileUpload->val(); //my-file.png

//load new value
$fileUpload->load($_FILES['file-upload']);

//we have the new value
echo ($fileUpload->val() === $_FILES['file-upload']); //true

Forms

We need a form to put all this things together. The form is just another field, in fact, it's like a Group.

$form = F::form();

//Set the form attributes:
$form->attr([
	'action' => 'test.php',
	'method' => 'post'
]);

//Add some fields and containers
$form->add([
	'name' => F::text()->maxlength(50)->required()->label('Your name'),
	'email' => F::email()->label('Your email'),
	'telephone' => F::tel()->label('Telephone number'),

	'gender' => F::choose([
		'm' => F::radio()->label('Male'),
		'f' => F::radio()->label('Female')
	]),
	
	'born' => F::group([
		'day' => F::number()->min(1)->max(31)->label('Day'),
		'month' => F::number()->min(1)->max(12)->label('Month'),
		'year' => F::number()->min(1900)->max(2013)->label('Year')
	]),

	'language' => F::select()->options(array(
		'gl' => 'Galician',
		'es' => 'Spanish',
		'en' => 'English'
	))->label('Language'),

	'friends' => F::collection([
		'name' => F::text()->label('Name'),
		'email' => F::email()->label('email'),
		'age' => F::number()->label('Age')
	]),

	'action' => F::choose([
		'save' => F::submit()->html('Save changes'),
		'duplicate' => F::submit()->html('Save changes')
	])
]);

//You can also add new fields using the array syntax (the key will be the input name):
$form['new-input'] = F::range()->min(0)->max(100)->val(50);

//Print the form
echo $form;

//Access to the fields using key names
echo $form['website'];

//Or fields inside fields
echo $form['born']['day'];

//Set the values to all fields:
$form->val([
	'name' => 'Oscar',
	'email' => 'oom@oscarotero.com',
	'gender' => 'm',
	'born' => [
		'day' => 21,
		'month' => 6,
		'year' => 1979
	],
	'language' => 'gl',
	'friends' => [
		[
			'name' => 'Friend 1',
			'email' => 'friend1@email.com',
			'age' => 25,
		],[
			'name' => 'Friend 2',
			'email' => 'friend2@email.com',
			'age' => 30,
		],[
			'name' => 'Friend 3',
			'email' => 'friend3@email.com',
			'age' => 35,
		]
	],
	'action' => 'save'
]);

//Get the values
$values = $form->val();

//To load the raw values from globals $_GET, $_POST and $_FILES:
$form->loadFromGlobals();

//Or specify your own globals
$form->loadFromGlobals($_my_GET, $_my_POST, $_my_FILES);

//Check the errors
if (!$form->validate()) {
	echo 'there are errors in the form';
}

Builder

The Builder class is used to ease the creation of fields and containers. For example, instead of this:

use FormManager\Containers\Form;
use FormManager\Inputs\Text;
use FormManager\Inputs\Textarea;

$form = new Form([
	'name' => new Text(),
	'bio' => new Textarea(),
]);

You can do simply this:

use FormManager\Builder as F;

$form = F::form([
	'name' => F::text(),
	'bio' => F::textarea()
]);

The FormManager\Builder handles the instantation of all theses classes for you using factories. By default, it contains the FormManager\Factory, responsible of instantation of all fields and containers.

But you can add your owns factories, creating classes implementing FormManager\FactoryInterface.

This is useful for a lot of things. For example, to create custom fields:

use FormManager\Builder as F;
use FormManager\FactoryInterface;

class CustomFields implements FactoryInterface
{
	/**
	 * Method required by the interface
	 */
	public function get($name, array $arguments)
	{
		if (method_exists($this, $name)) {
			return $this->$name();
		}
	}

	public function Year()
	{
		return F::number()->min(1900)->max(date('Y'));
	}
}

Use it in your app:

use FormManager\Builder as F;

F::addFactory(new CustomFields());

$date = F::form([
	'name' => F::text(),
	'born-year' => F::year(),
	'dead-year' => F::year(),
]);

Other usage is to save all forms of your app under a namespace:

namespace MyApp\Forms;

use FormManager\Builder as F;
use FormManager\Fields\Form;

class EditUserForm extends Form
{
	public function __construct()
	{

		$this->add([
			'name' => F::text()->maxlength(200)->label('Name'),
			'email' => F::email()->label('Email'),
			'password' => F::password()->label('Password'),
			'repeat_password' => F::password()->label('Repeat password')
		]);

		//Add a validator to check the password
		$this->addValidator('password-check', function($form)
		{
			$password1 = $form['password']->val();
			$password2 = $form['repeat_password']->val();

			if ($password1 !== $password2) {
				throw new FormManager\InvalidValueException('The passwords does not match');
			}
		});
	}
}

And create a factory

use FormManager\FactoryInterface;

class MyForms implements FactoryInterface
{
	public function get($name, array $arguments)
	{
		$class = 'MyApp\\Forms\\'.ucfirst($name);

		if (class_exists($class)) {
			return new $class();
		}
	}
}

Use it:

use FormManager\Builder as F;

F::addFactory(new MyForms());

$editUser = F::editUserForm();

Note: Each time you register a new factory, it will be prepended to the already registered ones, so if you register fields/containers called "Form", "Textarea", etc, they will be used instead the default. This allows extend them.

Builder instances

The static builder is fine for most cases, but sometimes you need to combine differents builders with different factories. So you can create instances:

use FormManager\Builder;
use FormManager\Factory;

//Create a builder instance adding the default FormManager factory:
$b1 = new Builder(new Factory());

//Create another builder instance with your custom factory:
$b2 = new Builder();
$b2->add(new MyCustomFactory());

//Now, you're ready to combine them:
$form = $b1->form([
	'name' => $b1->text(),
	'description' => $b2->textarea(),
	'email' => Builder::email()
]);