Incredible, dynamic form templates.

Formo blueprints give you a powerful way to create subform templates that can be reused any number of times. These are especially useful for one to many relationships. The basic idea behind a Formo blueprint is that you create a subform and turn it into a blueprint to be reused any number of times. Like a group full of any number of groups within itself. Blueprints require you to set `namespaces` to `TRUE` in your Formo config or at least for the parent form containing the blueprint.
To create a blueprint, just set a subform's `blueprint` property to `TRUE`. In the example below, we are creating an blueprint named 'address', padding it with two empty records. ``` $blueprint = Formo::form(['alias' => 'address', 'blueprint' => true]) ->add('city') ->add('street') ->add_rules_fields([ 'city' => [ ['not_empty'], ], 'state' => [ ['not_empty'] ], ]) ->pad_blueprint(2); $form = Formo::form(['alias' => 'parent_form']) ->add('name', 'input', null, ['attr.placeholder' => 'John Doe']) ->add('email', 'input|email', null, ['attr.placeholder' => '']) ->add($blueprint) ->add('submit', 'button', null, ['html' => 'Submit']); ``` Notice that when you look at the form's value, the blueprint group only shows values for the blueprint copies made with `Formo::pad_blueprint()`. The `city` and `state` fields do not render and do not show in the group's value. `$form->val()` looks like this from the above example: ``` array(4) ( "name" => NULL "email" => NULL "address" => array(2) ( 0 => array(2) ( "city" => NULL "street" => NULL ) 1 => array(2) ( "city" => NULL "street" => NULL ) ) "submit" => NULL ) ``` ### Blueprint Template You can specify a blueprint template to be used just for blueprint copies. The variable name for this is `blueprint_template`. At creation: ``` $blueprint = Formo::form([ 'alias' = 'address', 'blueprint' => true, 'blueprint_template' => 'bar']); ``` Using `Formo::set()`: ``` $blueprint->set('blueprint_template', 'foo'); ```
You can pass an argument to `Formo::pad_blueprint()` that represents either the number of empty copies to pad or the actual values to create copies from. ### Pad 5 empty copies ``` $classes = ORM::factory('Class') ->get_form() ->set([ 'alias' => 'classes', 'blueprint' => TRUE, ]) ->pad_blueprint(5); ``` ### Pad from Database Result ``` $class_result_set = ORM::factory($school->classes->find_all()); $classes = ORM::factory('Class') ->get_form() ->set([ 'alias' => 'classes', 'blueprint' => TRUE, ]) ->pad_blueprint($class_result_set); ``` ### Pad from single ORM Model ``` $class = ORM::factory('Class', 10); $classes = ORM::factory('Class') ->get_form() ->set([ 'alias' => 'classes', 'blueprint' => TRUE, ]) ->pad_blueprint($class); ```
Blueprints are great for dynamic forms because they watch for additional fields to add to the form and validate against when the form is posted. In the example above, Formo generates city and state fields with names like `parent_form[address][0][city]` and `parent_form[address][0][state]`. The form above also has `parent_form[address][1][city]` and `parent_form[address][1][city]`. If wanted to add another city and state group to the form, all you need to do is match the naming structure. In this case, you could add inputs named `parent_form[address][2][city]` and `parent_form[address][2][city]`. When you post the form after adding these fields, Formo's address blueprint looks for the user input and adds another blueprint copy to itself, then loads the user posted data into that blueprint copy, and validates against it using the blueprint's rules. Formo's default group driver adds the attribute `data-blueprintKey` to the `formo_group` div container so it's easy to determine the next key for the next blueprint set.
This is an example of using a Formo blueprint on a form that has a user who can have multiple phone numbers. In this example a phone record looks like this: 1. Id (input|hidden) 1. Name (input) 2. Type (select) 3. Number (input) First, the PHP. ``` $phones = $user->phones->find_all(); // Create the phone blueprint $phone = ORM::factory('Phone') ->set([ 'alias' => 'phone', 'blueprint' => TRUE, 'blueprint_template' => 'Phone record', // We will use this data attribute to know what the primary key field is '' => 'id', ]) // Add a 'deleted' field for keeping track of records that have been deleted ->add('deleted', 'input|hidden', '0') // Pad the blueprint with the user's phone records ->pad_blueprint($phones); $form = $user->get_edit_user_form ->add($phone) ->add('submit', 'button', null, ['html' => 'Save User']); ``` Next, some simple jQuery for adding new new phone records. Sure this example is quick and dirty, but it gets the job done and it is easy to follow ``` // First some reusable boilerplate $.fn.extend({ formoClone: function() { var that = this; // Clone the last group var groupCopy = that.find('.formo-group:last').clone(); console.log(; // Increase the blueprint key var key = parseInt(; key++; = key; $(groupCopy).find('input,select').each(function(k, element){ // Set the values to empty $(element).val(''); // set the name var name = $(element).attr('name'); // Replace the [i] with [j] in the element names var newName = name.replace(/\[[0-9]+\]/, '['+key+']') $(element).attr('name', newName); }); // Append the clone to the page that.append(groupCopy); }, formoDelete: function() { if (pk = $(this).data('pk')) { if ( ! $(this).find('[name="'+pk+'"]')) { // Go ahead and delete the group altogether if there isn't a primary key $(this).remove(); } } // Set the deleted value to 1 $(this).find('input[name="deleted"]').val(1); $(this).addClass('deleted'); }, formoUndelete: function() { $(this).find('input[name="deleted"]').val(0); $(this).removeClass('deleted'); } }); // Now attach some simple click events $('a.add-address').click(function(e){ $('#formo-container-phone').formoClone(); }); $('a.delete-address').click(function(e){ $(this).closest('.formo-group').formoDelete(); }); $('a.undelete-address').click(function(e){ $(this).closest('.formo-group').formoUndelete(); }); ``` Now back to the PHP ``` if ($form->load()->validate()) { foreach ($user->phone->as_array() as $field) { if ($field->deleted->val() == 1 AND $field->id->val()) { // Delete the record $phone = ORM::factory('Phone', $field->id->val()); $phone->delete(); } elseif ( ! $field->id->val()) { // Create a record $phone = ORM::factory('Phone'); ->values($field->val()); $phone->save(); } } } ```
When working with the Formo ORM and blueprints, it's very easy to determine which blueprint copies have been deleted from your form and which have been added dynamically via post. When you use `Formo::pad_blueprint()` with an ORM result set as the value, Formo keeps track of the primary keys involved with those fields. You can easily see if those primary keys have been removed dynamically using `Formo::blueprint_deleted()`. Just make sure you do it after loading the post values: ``` if ($form->load()->validate()) { $deleted_pimary_keys = $form->assignments->blueprint_deleted(); // Or you can pass the field name as the argument $deleted_pimary_keys = $form->blueprint_deleted('assignments'); } ``` Likewise, you can determine fields that have ben dynamically added using `Formo::blueprint_dynamic`: ``` if ($form->load()->validate()) { $new_fields = $form->assignments->blueprint_dynamic(); // Or you can pass the field name as the argument $new_fields = $form->blueprint_dynamic('assignments'); } ```
comments powered by Disqus