Created
September 6, 2013 23:01
-
-
Save NaszvadiG/6471147 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php if (!defined('BASEPATH')) exit('No direct script access allowed'); | |
/** | |
* CodeIgniter Drupal-inspited Form API plus Bootstrap integration | |
* | |
* NOTE: This library does not provide utilities to protect forms | |
* against CSRF attacks. Make sure that you already turned CI's CSRF | |
* protection ON. | |
* | |
* @package CodeIgniter | |
* @author Sepehr Lajevardi <me@sepehr.ws> | |
* @copyright Copyright (c) 2012 Sepehr Lajevardi. | |
* @license http://codeigniter.com/user_guide/license.html | |
* @link https://github.com/sepehr/ci-form | |
* @version Version 1.0 | |
* @filesource | |
*/ | |
// ------------------------------------------------------------------------ | |
/** | |
* Form API class for CodeIgniter. | |
* | |
* @package CodeIgniter | |
* @subpackage Libraries | |
* @category Forms | |
* @author Sepehr Lajevardi <me@sepehr.ws> | |
* @link https://github.com/sepehr/ci-form | |
* @todo - Form caching | |
* - Debug logs | |
* - Error handling | |
* - Redocument, add usage examples | |
* - Cleanup! It's very alpha, we need a better structure and more unified API | |
* - There are a bunch of templates that could be merged together, redundancy sucks! | |
* - Dependent dropdowns. | |
* - Ajax API, if possible. | |
* - Move templates into config files. | |
* - Make use of set_checkbox(), set_radio() when populating toggles. | |
* - Language class integration. | |
*/ | |
class Form { | |
/** | |
* Stores the superobject. | |
* @var object | |
*/ | |
private static $CI; | |
//-------------------------------------------------------------------- | |
/** | |
* Stores the form default values. | |
* @var object | |
*/ | |
private static $_values = array(); | |
//-------------------------------------------------------------------- | |
// Defaults | |
//-------------------------------------------------------------------- | |
/** | |
* Stores field defaults. | |
* @var string | |
*/ | |
private static $_form_defaults = array( | |
'action' => '', | |
'prefix' => '', | |
'suffix' => '', | |
'errors' => array(), | |
'attributes' => array(), | |
'multipart' => FALSE, | |
'multistep' => FALSE, | |
'multistep_nav' => FALSE, | |
'multistep_manual' => FALSE, | |
); | |
//-------------------------------------------------------------------- | |
/** | |
* Stores fieldset defaults. | |
* @var string | |
*/ | |
private static $_fieldset_defaults = array( | |
'legend' => '', | |
'prefix' => '', | |
'suffix' => '', | |
'active' => FALSE, | |
'attributes' => array(), | |
'accordion' => array(), | |
'type' => 'fieldset', | |
'before' => '', | |
'after' => '', | |
); | |
//-------------------------------------------------------------------- | |
/** | |
* Stores field defaults. | |
* @var string | |
*/ | |
private static $_field_defaults = array( | |
'type' => 'text', | |
'required' => FALSE, | |
'icon' => FALSE, | |
'disabled' => FALSE, | |
'readonly' => FALSE, | |
'form_actions' => FALSE, | |
'inline' => FALSE, | |
'error_type' => 'block', | |
'help_type' => 'block', | |
'class' => 'form-control span2', | |
'required_markup' => ' <strong>*</strong>', | |
'attributes' => array(), | |
'input_append' => '', | |
'render_class' => '', | |
'render_callback' => '', | |
'label' => '', | |
'value' => '', | |
'placeholder' => '', | |
'wrapper_class' => '', | |
'prefix' => '', | |
'suffix' => '', | |
'suffix_inline' => '', | |
'extra' => '', | |
'help' => '', | |
'error' => '', | |
'help_block' => '', | |
'help_inline' => '', | |
'checked' => '', | |
'selected' => '', | |
'options' => '', | |
'popover' => '', | |
'popover_title' => '', | |
'rules' => '', | |
'before' => '', | |
'after' => '', | |
'private' => '', | |
); | |
//-------------------------------------------------------------------- | |
/** | |
* Stores input types. | |
* @var string | |
*/ | |
private static $_input_fields = array( | |
'checkbox', 'datetime-local', | |
'color', 'date', | |
'datetime', 'week', | |
'email', 'file', | |
'hidden', 'image', | |
'month', 'number', | |
'password', 'radio', | |
'range', 'reset', | |
'search', 'submit', | |
'tel', 'text', | |
'time', 'url', | |
); | |
//-------------------------------------------------------------------- | |
// Templates | |
//-------------------------------------------------------------------- | |
/** | |
* Stores common fields template prototype. | |
* @var string | |
*/ | |
private static $_default_template = ' | |
{before} | |
<div id="{wrapper_id}" class="control-group {type}-control-wrapper{wrapper_class}{error}"> | |
<label class="control-label" for="{id}">{label}{required}</label> | |
<div class="controls"> | |
<div class="input-prepend{input_append}"> | |
{icon} | |
{prefix} | |
<{element} id="{id}" class="{class}" placeholder="{placeholder}" name="{name}" type="{type}" value="{value}" {attributes} {disabled}{readonly}{checked}{selected} /> | |
{suffix_inline} | |
{help_inline} | |
{help_block} | |
</div> <!-- /.input-prepend --> | |
{suffix} | |
</div> <!-- /.controls --> | |
</div> <!-- /.control-group --> | |
{after} | |
{private} | |
'; | |
//-------------------------------------------------------------------- | |
/** | |
* Stores common inline fields template prototype. | |
* @var string | |
*/ | |
private static $_default_inline_template = ' | |
{before} | |
<div id="{wrapper_id}" class="input-prepend inline-field"> | |
{icon} | |
<{element} id="{id}" class="{class}" placeholder="{placeholder}" name="{name}" type="{type}" value="{value}" {attributes} {disabled}{readonly}{checked}{selected} /> | |
</div> <!-- /.input-prepend --> | |
{after} | |
{private} | |
'; | |
//-------------------------------------------------------------------- | |
/** | |
* Stores hidden fields template prototype. | |
* @var string | |
*/ | |
private static $_hidden_template = ' | |
<input type="hidden" name="{name}" value="{value}" /> | |
'; | |
//-------------------------------------------------------------------- | |
/** | |
* Stores a field help markup prototype. | |
* @var string | |
*/ | |
private static $_accordion_template = ' | |
<div {attributes}> | |
<div class="accordion-heading"> | |
<a class="accordion-toggle" data-toggle="collapse" data-parent="#form-accordion" href="#{target_id}">{title}</a> | |
</div> <!-- /.accordion-heading --> | |
<div id="{target_id}" class="accordion-body collapse{active}"> | |
<div class="accordion-inner"> | |
{nav_prev} {body} {nav_next} | |
</div> <!-- /.accordion-inner --> | |
</div> <!-- /.accordion-body --> | |
</div> <!-- /.accordion-group --> | |
'; | |
//-------------------------------------------------------------------- | |
/** | |
* Stores a dropdown template prototype. | |
* @var string | |
*/ | |
private static $_dropdown_template = ' | |
{before} | |
<div id="{wrapper_id}" class="control-group {type}-control-wrapper{wrapper_class}{error}"> | |
<label class="control-label" for="{id}">{label}{required}</label> | |
<div class="controls"> | |
<div class="input-prepend{input_append}"> | |
{icon} | |
{prefix} | |
{options} | |
{suffix_inline} | |
{help_inline} | |
{help_block} | |
</div> <!-- /.input-prepend --> | |
{suffix} | |
</div> <!-- /.controls --> | |
</div> <!-- /.control-group --> | |
{after} | |
{private} | |
'; | |
//-------------------------------------------------------------------- | |
/** | |
* Stores an inline dropdown template prototype. | |
* @var string | |
*/ | |
private static $_dropdown_inline_template = ' | |
{before} | |
<div class="input-prepend inline-field"> | |
<label for="{id}">{label}{required}</label> | |
{icon} | |
{options} | |
</div> | |
{after} | |
{private} | |
'; | |
//-------------------------------------------------------------------- | |
/** | |
* Stores a button template prototype. | |
* @var string | |
*/ | |
private static $_button_template = ' | |
{before} | |
<div class="controls{form_actions}"> | |
<{element} id="{id}" class="form-submit btn {class}" {attributes}>{value}</{element}> | |
</div> <!-- /.controls --> | |
{after} | |
{private} | |
'; | |
//-------------------------------------------------------------------- | |
/** | |
* Stores an inline button template prototype. | |
* @var string | |
*/ | |
private static $_button_inline_template = ' | |
{before} | |
<{element} id="{id}" class="form-submit btn {class}" style="float:none" {attributes}>{value}</{element}> | |
{after} | |
{private} | |
'; | |
//-------------------------------------------------------------------- | |
/** | |
* Stores a textarea template prototype. | |
* @var string | |
*/ | |
private static $_textarea_template = ' | |
{before} | |
<div id="{wrapper_id}" class="control-group {type}-control-wrapper{wrapper_class}{error}"> | |
<label class="control-label" for="{id}">{label}{required}</label> | |
<div class="controls"> | |
<div class="input-prepend{input_append}"> | |
{icon} | |
{prefix} | |
<textarea id="{id}" class="{class}" placeholder="{placeholder}" name="{name}" type="{type}" {attributes} {disabled}{readonly}>{value}</textarea> | |
{suffix_inline} | |
{help_inline} | |
{help_block} | |
</div> <!-- /.input-prepend --> | |
{suffix} | |
</div> <!-- /.controls --> | |
</div> <!-- /.control-group --> | |
{after} | |
{private} | |
'; | |
//-------------------------------------------------------------------- | |
/** | |
* Stores checkboxes/radios template prototype. | |
* @var string | |
*/ | |
private static $_toggles_template = ' | |
{before} | |
<div id="{wrapper_id}" class="control-group {type}-control-wrapper{wrapper_class}{error}"> | |
<label class="control-label" for="{id}">{label}{required}</label> | |
<div class="controls"> | |
{prefix} | |
{options} | |
{suffix_inline} | |
</div> <!-- /.controls --> | |
{suffix} | |
</div> <!-- /.control-group --> | |
{after} | |
{private} | |
'; | |
//-------------------------------------------------------------------- | |
/** | |
* Stores an inline checkbox/radio template prototype. | |
* @var string | |
*/ | |
private static $_toggle_template = ' | |
{before} | |
<label class="{toggle} inline"> | |
<input type="{toggle}" name="{name}" value="{value}" {attributes} {checked} /> {label} | |
</label> | |
{after} | |
{private} | |
'; | |
//-------------------------------------------------------------------- | |
/** | |
* Stores a field icon prototype. | |
* @var string | |
*/ | |
private static $_icon_template = '<i class="icon icon-{icon}"></i>'; | |
//-------------------------------------------------------------------- | |
/** | |
* Stores a field help markup prototype. | |
* @var string | |
*/ | |
private static $_help_template = '<span class="help-{type}">{help}</span>'; | |
//-------------------------------------------------------------------- | |
/** | |
* Form API Constructor | |
* | |
* @return void | |
*/ | |
public function __construct() | |
{ | |
// Get CI superobject | |
self::$CI =& get_instance(); | |
// And load the form helper | |
self::$CI->load->helper('form_helper'); | |
} | |
//-------------------------------------------------------------------- | |
// API Functions | |
//-------------------------------------------------------------------- | |
/** | |
* Loads a form definition and returns the rendered output. | |
* | |
* @return string | |
*/ | |
public static function get($form_name, $values = FALSE, $render = TRUE, $flush = FALSE) | |
{ | |
static $cache = array(); | |
// Internally cache the form array, since it might be | |
// used for several times in one request. | |
(isset($cache[$form_name]) AND !$flush) OR $cache[$form_name] = self::load($form_name, $values); | |
// Return the form array or rendered HTML, if requested | |
return $render ? self::render($cache[$form_name]) : $cache[$form_name]; | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Loads a form definition array from a config file. | |
* | |
* @return array | |
*/ | |
public static function load($form_name, $values = FALSE) | |
{ | |
// Load form definition file, | |
// and set it to fail gracefully | |
if (self::$CI->load->config($form_name, FALSE, TRUE)) | |
{ | |
// Get the array | |
$form = self::$CI->config->item(basename($form_name)); | |
// Set default field values to be overriden, if required | |
self::set_defaults($values); | |
// Allow other parties to manipulate the form array | |
class_exists('Events') AND Events::trigger("form_{$form_name}_alter", $form); | |
return $form; | |
} | |
// Failed to load form definition file | |
log_message('error', 'Form definition failed: ' . $form_name); | |
return FALSE; | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Rendered a form definition array/sub-array into its HTML equivalent. | |
* | |
* @return string | |
*/ | |
public static function render($form, $subform = FALSE) | |
{ | |
// Terminate if not an array | |
if ( ! is_array($form)) | |
{ | |
return FALSE; | |
} | |
$output = ''; | |
// Apply form defaults | |
$fields = $form = array_merge(self::$_form_defaults, $form); | |
// Render openning tag | |
$subform OR list($output, $fields) = self::_render_form_open($form, $form['multistep'], $form['multistep_manual']); | |
// Render fields and fieldsets | |
$output .= self::_render_fields($fields, $form['multistep']); | |
// Render closing tag | |
$subform OR $output .= self::_render_form_close($form['suffix'], $form['multistep']); | |
return $output; | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Validates a form against its rules. | |
* | |
* This might be used instead of the $this->form_validation->[set_rules()|run()]; | |
* | |
* @return boolean | |
*/ | |
public static function validate($form_name, $CI = FALSE) | |
{ | |
// Make sure that we have CI form validation class already in place. | |
class_exists('CI_Form_validation') OR self::$CI->load->library('form_validation'); | |
// Workaround the HMVC issue | |
self::validate_init($CI); | |
// Load the form array | |
$form = self::load($form_name); | |
// Set custom callbacks validation errors | |
if (isset($form['errors']) AND is_array($form['errors'])) | |
{ | |
self::set_message($form['errors']); | |
} | |
$rules = self::_validate_rules($form); | |
// Pass it to the CI validation | |
return self::$CI | |
->form_validation | |
->set_rules($rules) | |
->run(); | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Works around form validation HMVC issue. | |
* | |
* @return void | |
*/ | |
public static function validate_init($CI = FALSE) | |
{ | |
is_object($CI) AND self::$CI->form_validation->CI = $CI; | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Extracts validation rules from a form definition. | |
* | |
* @return array | |
*/ | |
public static function validate_rules($form_name) | |
{ | |
if ($form = self::load($form_name)) | |
{ | |
return self::_validate_rules($form); | |
} | |
return FALSE; | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Sets custom callback error message. | |
* | |
* It's identical to CI form validation's set_message(), | |
* it's a wrapper in fact! | |
*/ | |
public static function set_message($callback, $error = FALSE) | |
{ | |
// Check if it's an array of callbacks | |
if (is_array($callback)) | |
{ | |
foreach ($callback as $name => $error) | |
{ | |
self::set_message($name, $error); | |
} | |
} | |
// Make sure that we have CI form validation class already in place. | |
// @TODO: Cache check results. | |
class_exists('CI_Form_validation') OR self::$CI->load->library('form_validation'); | |
self::$CI->form_validation->set_message($callback, $error); | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Flushes form validation field data. | |
* | |
* @return void | |
*/ | |
public static function flush($flush_post = TRUE) | |
{ | |
// Flush $_POST data | |
$flush_post AND $_POST = array(); | |
// Flush form validation data | |
if (class_exists('CI_Form_validation')) | |
{ | |
// Reset form validation field data, here comes the hack! | |
self::$CI->form_validation = class_exists('MY_Form_validation') | |
? new MY_Form_validation() | |
: new CI_Form_validation(); | |
} | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Prepares the value for display in the form. | |
* | |
* @return string | |
*/ | |
public static function value($name, $default = '') | |
{ | |
// $_POST values? | |
if (isset($_POST[$name])) | |
{ | |
return form_prep($_POST[$name]); | |
} | |
// Default values? | |
if (isset(self::$_values[$name])) | |
{ | |
return form_prep(self::$_values[$name]); | |
} | |
return set_value($name, $default); | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Checks checkbox/radio element values. | |
* | |
* @return boolean | |
* @todo REVIEW, FIX | |
*/ | |
public static function value_toggled($name, $value, $checked = FALSE) | |
{ | |
$clean = self::clean($name); | |
// Posted, check $_POST | |
if (isset($_POST[$clean])) | |
{ | |
return is_array($_POST[$clean]) | |
? in_array($value, $_POST[$clean]) | |
: $_POST[$clean]; | |
} | |
// Check if it's posted, but not checked | |
if ( ! empty($_POST) AND isset($_POST[$clean . '__toggle'])) | |
{ | |
// Set it! this will be a lil help for our controllers/models | |
$_POST[$clean] = FALSE; | |
return FALSE; | |
} | |
// Check self::$_values for single booleans | |
if (isset(self::$_values[$clean])) | |
{ | |
return is_array(self::$_values[$clean]) | |
? in_array($value, self::$_values[$clean]) | |
: (bool) self::$_values[$name]; | |
} | |
// Return the default passed | |
return $checked; | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Returns form validation errors. | |
* | |
* @return boolean | |
*/ | |
public static function errors($to_array = FALSE) | |
{ | |
if ($to_array) | |
{ | |
// Remove error delimiters | |
self::$CI->form_validation->set_error_delimiters(FALSE, FALSE); | |
// Return validation errors array | |
return array_filter(explode("\n", validation_errors())); | |
} | |
return validation_errors(); | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Sets form default valurs | |
* | |
* @return void | |
*/ | |
public static function set_defaults($values) | |
{ | |
is_object($values) AND $values = (array) $values; | |
is_array($values) AND self::$_values = $values; | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Cleans string from []s. | |
* | |
* @return string | |
*/ | |
public static function clean($name) | |
{ | |
if (($pos = strpos($name, '[]')) !== FALSE) | |
{ | |
$name = substr($name, 0, $pos); | |
} | |
return str_replace(array('[', ']'), array('-', ''), $name); | |
} | |
//-------------------------------------------------------------------- | |
// Internal Renderrer methods | |
//-------------------------------------------------------------------- | |
/** | |
* Renders an array of fields. | |
* | |
* @return string | |
*/ | |
private static function _render_fields($fields, $multistep = FALSE) | |
{ | |
$output = ''; | |
if ( ! is_array($fields)) | |
{ | |
// Temporary: | |
return 'Form::_render_fields(): $fields is not properly formatted array, get some coffee!'; | |
} | |
// Render each field to HTML by calling its corresponding renderrer callback | |
foreach ($fields as $name => &$data) | |
{ | |
if ( ! isset($data['type'])) | |
{ | |
continue; | |
} | |
// Let submit typed inputs to be rendered as buttons | |
$data['type'] == 'submit' AND $data['type'] = 'button'; | |
// Prepare checkbox/radiobutton elements, @TODO: Review. | |
if ($data['type'] == 'checkbox' OR $data['type'] == 'radio') | |
{ | |
// Preserve element type and make sure that we'll | |
// call _render_toggles() to support renderring | |
// checkbox/radio groups | |
$data['toggle'] = $data['type']; | |
$data['type'] = 'toggles'; | |
} | |
// 1. User custom renderrer: @TODO | |
// 2. Field specific renderrer: | |
if (method_exists(get_class(), '_render_' . $data['type'])) | |
{ | |
$renderer = '_render_' . $data['type']; | |
$output .= self::$renderer($data, $name, $multistep); | |
continue; | |
} | |
// 3. Default field renderrer: | |
$output .= self::_render_field($data, $name); | |
} | |
return $output; | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Renders a single element array into HTML. | |
* | |
* @return string | |
*/ | |
private static function _render_field($field, $name = FALSE, $template = FALSE) | |
{ | |
// Set the correct template | |
$template OR $template = isset($field['inline']) ? self::$_default_inline_template : self::$_default_template; | |
// Setup field name | |
$name OR $name = isset($field['name']) ? $field['name']: 'no_name_' . rand(1, 10); | |
// Merge field defaults | |
$field = array_merge(self::$_field_defaults, $field); | |
// Remove brackets from name attribute, if any | |
$clean = self::clean($name); | |
// Add field extras | |
$field += array( | |
'name' => $name, | |
'id' => "edit-$clean", | |
'wrapper_id' => "$clean-wrapper", | |
); | |
// Field help | |
if ($field['help']) | |
{ | |
$field["help_{$field['help_type']}"] = str_replace(array('{type}', '{help}'), array($field['help_type'], $field['help']), self::$_help_template); | |
} | |
// Field error | |
// @TODO: Errors overwrite help messages, fix this behavior | |
if (function_exists('form_error') AND form_error($field['name'])) | |
{ | |
$field['error'] = ' error'; // The wrapper flag class | |
$field["help_{$field['error_type']}"] = str_replace(array('{type}', '{help}'), array($field['error_type'], form_error($field['name'])), self::$_help_template); | |
} | |
// Field icon | |
$field['icon'] AND $field['icon'] = '<span class="add-on">' . self::_render_icon($field['icon']) . '</span>'; | |
// Field element type | |
isset($field['element']) OR | |
$field['element'] = in_array($field['type'], self::$_input_fields) ? 'input' : $field['type']; | |
// @TODO: Improve this | |
// Prepare field value, exclude buttons, checkboxes and radios | |
if (isset($field['value']) AND $field['type'] != 'button' AND !isset($field['toggle'])) | |
{ | |
$field['value'] = self::value($field['name'], $field['value']); | |
} | |
// Boolean flags | |
$field['readonly'] = $field['readonly'] ? ' readonly' : ''; | |
$field['disabled'] = $field['disabled'] ? ' disabled' : ''; | |
// Mark field as required if it's so | |
$field['required'] = strpos($field['rules'], 'required') === FALSE ? '' : $field['required_markup']; | |
// Class strings | |
// @TODO: Fix form_actions. We should be able to group multiple buttons into one form_actions element | |
$field['form_actions'] AND $field['form_actions'] = ' form-actions'; | |
$field['wrapper_class'] AND $field['wrapper_class'] = ' ' . $field['wrapper_class']; | |
// Popovers | |
$field['popover'] AND $field['attributes'] += array( | |
'rel' => 'popover', | |
'data-content' => $field['popover'], | |
'data-original-title' => $field['popover_title'], | |
); | |
// Parse extra attributes | |
isset($field['attributes']) AND $field['attributes'] = _parse_form_attributes($field['attributes'], array()); | |
// Parse inline fields in suffix | |
if (isset($field['suffix']) AND is_array($field['suffix'])) | |
{ | |
$field['suffix'] = self::_render_fields($field['suffix']); | |
} | |
// As well as in suffix_inline | |
if (isset($field['suffix_inline']) AND is_array($field['suffix_inline'])) | |
{ | |
$field['suffix_inline'] = self::_render_fields($field['suffix_inline']); | |
$field['input_append'] = ' input-append'; | |
} | |
// And as in after | |
isset($field['after']) AND is_array($field['after']) | |
AND $field['after'] = self::_render_fields($field['after']); | |
// Prep field placeholder map | |
foreach ($field as $key => $value) | |
{ | |
$field['{' . $key . '}'] = $value; | |
unset($field[$key]); | |
} | |
return str_replace(array_keys($field), array_values($field), $template); | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Renders a a subform array into HTML. | |
* | |
* @return string | |
*/ | |
private static function _render_subform($field, $name = FALSE, $multistep = FALSE) | |
{ | |
// Load subform array | |
$subform = self::load($field['subform']); | |
return self::_render_fields($subform, FALSE); | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Renders a fieldset element and its embedded fields. | |
* | |
* @return string | |
*/ | |
private static function _render_fieldset($fieldset, $name = FALSE, $multistep = FALSE) | |
{ | |
$output = ''; | |
// Merge fieldset defaults | |
$fieldset = array_merge(self::$_fieldset_defaults, $fieldset); | |
// Render fieldset prefix | |
$fieldset['prefix'] AND $output .= $fieldset['prefix']; | |
$output .= '<fieldset ' . _parse_form_attributes($fieldset['attributes'], array()) . '>'; | |
// Render fieldset legend | |
$fieldset['legend'] AND $output .= '<legend>' . $fieldset['legend'] . '</legend>'; | |
// Unset fieldset attributes | |
$_fieldset = $fieldset; | |
foreach (self::$_fieldset_defaults as $key => $value) | |
{ | |
unset($fieldset[$key]); | |
} | |
// Render embedded fields | |
$output .= self::render($fieldset, TRUE) . '</fieldset>'; | |
// Render fieldset suffix | |
$_fieldset['suffix'] AND $output .= $_fieldset['suffix']; | |
// Render fieldset as a multistep form step, if required | |
if ($multistep) | |
{ | |
// Accordion dynamic defaults | |
// @TODO Integrate language class | |
$accordion_defaults = array( | |
'nav_buttons' => FALSE, | |
'body' => $output, | |
'title' => $_fieldset['legend'], | |
'active' => $_fieldset['active'], | |
'target_id' => $name . '-accordion', | |
'attributes' => array('class' => 'accordion-group', 'id' => $name . '-accordion-wrapper'), | |
'nav_next' => '', | |
'nav_prev' => '', | |
'nav_next_label' => 'Next', | |
'nav_prev_label' => 'Previous', | |
); | |
// Merge accordion defaults | |
$_fieldset['accordion'] = array_merge($accordion_defaults, $_fieldset['accordion']); | |
// Render fieldset as an accordion | |
$output = self::_render_accordion($_fieldset['accordion'], $name); | |
} | |
return $output; | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Renders the passing array into an accordion element. | |
* | |
* @return string | |
*/ | |
private static function _render_accordion($accordion, $name = FALSE) | |
{ | |
// @TODO: Make sure that accordion group class is set | |
// Accordion navigation? | |
if ($accordion['nav_buttons']) | |
{ | |
foreach (array('prev', 'next') as $nav) | |
{ | |
if (isset($accordion["nav_$nav"]) AND $accordion["nav_$nav"]) | |
{ | |
// Render accordion navigation buttons | |
$accordion["nav_$nav"] = self::_render_button(array( | |
'type' => 'button', | |
'element' => 'a', | |
'class' => "btn btn-info pull-right accordion-$nav", | |
'value' => $accordion["nav_{$nav}_label"], | |
'attributes' => array( | |
'data-toggle' => 'collapse', | |
'data-parent' => '#form-accordion', | |
'data-target' => '#' . $accordion["nav_$nav"], | |
), | |
), "nav_button_$nav", FALSE); | |
} | |
} | |
} | |
// Active step? add the class | |
$accordion['active'] AND $accordion['active'] = ' in'; | |
return self::_render_field($accordion, $name, self::$_accordion_template); | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Renders a textarea element. | |
* | |
* @return string | |
*/ | |
private static function _render_textarea($field, $name = FALSE, $multistep = FALSE) | |
{ | |
return self::_render_field($field, $name, self::$_textarea_template); | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Renders a toggle element (checkbox/radio). | |
* | |
* @return string | |
*/ | |
private static function _render_toggle($field, $name = FALSE, $multistep = FALSE) | |
{ | |
// Checkbox/radio value setup | |
if (self::value_toggled($name, $field['value'], $field['checked'])) | |
{ | |
$field['checked'] = 'checked'; | |
} | |
return self::_render_field($field, $name, self::$_toggle_template); | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Renders a group of toggle element (checkbox/radio). | |
* | |
* @return string | |
*/ | |
public static function _render_toggles($field, $name = FALSE, $multistep = FALSE) | |
{ | |
$toggles = ''; | |
// Load checkbox/radio data model if set | |
if (isset($field['data'])) | |
{ | |
$field['options'] = self::_call_data_model($field['data']); | |
} | |
// Render each option as a single checkbox/radio element | |
foreach ($field['options'] as $value => $label) | |
{ | |
$toggles .= self::_render_toggle(array( | |
'type' => 'toggles', | |
'value' => $value, | |
'label' => $label, | |
'toggle' => $field['toggle'], | |
'checked' => isset($field['value']) ? in_array($value, $field['value']) : FALSE, | |
), $name); | |
} | |
// Set rendered elements as options | |
$field['options'] = $toggles; | |
unset($field['value']); | |
// Append a hidden helper field, this will let | |
// us detect unchecked toggles on form submits | |
$field['private'] = self::_render_hidden('1', self::clean($name) . '__toggle'); | |
return self::_render_field($field, $name, self::$_toggles_template); | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Renders a button element. | |
* | |
* @return string | |
* @todo Ensure proper button classes | |
*/ | |
private static function _render_button($button, $name = FALSE, $multistep = FALSE) | |
{ | |
// Rendering A, DIV or SPAN elements as buttons? | |
isset($button['element']) OR $button['element'] = 'button'; | |
// Add type if it's a button | |
$button['element'] == 'button' AND $button['element'] .= ' type="submit"'; | |
// Default icon set? | |
if (isset($button['icon'])) | |
{ | |
$button['icon_prefix'] = $button['icon']; | |
unset($button['icon']); | |
} | |
// Inline icon: prefix | |
isset($button['icon_prefix']) AND | |
$button['value'] = self::_render_icon($button['icon_prefix']) . $button['value']; | |
// Inline icon: suffix | |
isset($button['icon_suffix']) AND | |
$button['value'] .= self::_render_icon($button['icon_suffix']); | |
// Inline element? | |
$template = isset($button['inline']) | |
? self::$_button_inline_template | |
: self::$_button_template; | |
return self::_render_field($button, $name, $template); | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Renders a markup element. | |
* | |
* @return string | |
*/ | |
private static function _render_markup($markup, $name = FALSE, $multistep = FALSE, $element = 'button') | |
{ | |
// Load and return the view if it's set to | |
if (isset($markup['view'])) | |
{ | |
// View data may come in various formats, here they are: | |
if (isset($markup['data']) AND is_array($markup['data'])) | |
{ | |
// 1. Flat arrays: In this case we should fetch values from form defaults | |
foreach ($markup['data'] as $index => $key) | |
{ | |
if (is_numeric($index) AND isset(self::$_values[$key])) | |
{ | |
unset($markup['data'][$index]); | |
$markup['data'][$key] = self::$_values[$key]; | |
} | |
} | |
} | |
// 2. No data set, fetch from defaults if available | |
else | |
{ | |
$markup['data'] = isset(self::$_values[$name]) ? array($name => self::$_values[$name]) : ''; | |
} | |
// 3. Otherwise there should be a key/valued array in data | |
return self::$CI->load->view($markup['view'], $markup['data'], TRUE); | |
} | |
// Otherwise check for raw HTML in 'value' | |
return isset($markup['value']) ? $markup['value'] : ''; | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Renders a select element. | |
* | |
* @return string | |
*/ | |
private static function _render_dropdown($dropdown, $name, $multistep = FALSE) | |
{ | |
// Load dropdown data model if set. | |
isset($dropdown['data']) AND $dropdown['options'] = self::_call_data_model($dropdown['data']); | |
// Prepare value | |
isset($dropdown['value']) OR $dropdown['value'] = ''; | |
$dropdown['value'] = self::value($name, $dropdown['value']); | |
// Handle dropdown attributes | |
isset($dropdown['attributes']) OR $dropdown['attributes'] = array(); | |
$dropdown['attributes'] += array('id' => 'edit-' . self::clean($name)); | |
// Helps overcome dependent dropdowns issue on prepopulated options | |
(isset($dropdown['cached_value']) AND $dropdown['cached_value']) | |
AND $dropdown['attributes'] += array('cached_value' => $dropdown['value']); | |
// Parse attributes into string | |
$attributes = _parse_form_attributes($dropdown['attributes'], array()); | |
// Handle multiselect dropdowns | |
$helper = (isset($dropdown['multiselect']) AND $dropdown['multiselect']) ? 'form_multiselect' : 'form_dropdown'; | |
// Render dropdown select and option elements | |
isset($dropdown['options']) AND | |
$dropdown['options'] = $helper($name, $dropdown['options'], $dropdown['value'], $attributes); | |
// Inline element? | |
$template = isset($dropdown['inline']) | |
? self::$_dropdown_inline_template | |
: self::$_dropdown_template; | |
return self::_render_field($dropdown, $name, $template); | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Renders an icon element. | |
* | |
* @return string | |
*/ | |
public static function _render_icon($icon) | |
{ | |
return str_replace('{icon}', $icon, self::$_icon_template); | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Renders a hidden element. | |
* | |
* @return string | |
*/ | |
public static function _render_hidden($field, $name, $multistep = FALSE) | |
{ | |
// Get field value if it's an array | |
is_array($field) AND $field = $field['value']; | |
$hidden = str_replace('{name}', $name, self::$_hidden_template); | |
return str_replace('{value}', $field, $hidden); | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Renders a form element openning tag. | |
* | |
* @return array | |
*/ | |
private static function _render_form_open($form, $multistep = FALSE, $manual = FALSE) | |
{ | |
// Open form, multipart if required | |
$output = $form['prefix'] . ($form['multipart'] | |
? form_open_multipart($form['action'], $form['attributes']) | |
: form_open($form['action'], $form['attributes'])); | |
// Prepare accordion collapse type | |
$manual = $manual ? 'manual' : 'auto'; | |
// If the form is multistep, append accordion wrapper | |
$multistep AND $output .= '<div class="accordion" id="form-accordion" data-collapse-type="' . $manual . '">'; | |
// Prep form array for field render | |
foreach (self::$_form_defaults as $key => $value) | |
{ | |
unset($form[$key]); | |
} | |
return array($output, $form); | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Renders a form element closing tag. | |
* | |
* @return string | |
*/ | |
private static function _render_form_close($suffix, $multistep = FALSE) | |
{ | |
// If the form is multistep, append accordion wrapper | |
$output = $multistep ? '</div>' : ''; | |
return $output . form_close($suffix); | |
} | |
//-------------------------------------------------------------------- | |
// Misc Helpers | |
//-------------------------------------------------------------------- | |
/** | |
* Builds a validation array out of the form array. | |
* | |
* @return array | |
*/ | |
private static function _validate_rules($form) | |
{ | |
$rules = array(); | |
foreach ($form as $name => $field) | |
{ | |
// Extract rules from fieldsets | |
if (isset($field['type']) AND $field['type'] == 'fieldset') | |
{ | |
$rules = array_merge($rules, self::_validate_rules($field)); | |
continue; | |
} | |
// Extract rules from subforms | |
if (isset($field['type']) AND $field['type'] == 'subform') | |
{ | |
$rules = array_merge($rules, self::_validate_rules(self::load($field['subform']))); | |
continue; | |
} | |
// Skip non-fields, or fields with no | |
if ( ! is_array($field) OR ! isset($field['type']) OR ! isset($field['rules'])) | |
{ | |
continue; | |
} | |
$rules[] = array( | |
'field' => $name, | |
'rules' => $field['rules'], | |
// Field label is optional, field name will be used instead | |
'label' => isset($field['label']) ? $field['label'] : '', | |
); | |
} | |
return $rules; | |
} | |
//-------------------------------------------------------------------- | |
/** | |
* Calls form field data model method. | |
*/ | |
private static function _call_data_model($data) | |
{ | |
// Get model data | |
list($model, $method) = explode('.', $data); | |
$model_name = basename($model); | |
// Get method args, if any | |
preg_match('#\((.*?)\)#', $method, $match); | |
$arg = isset($match[1]) ? $match[1] : FALSE; | |
strpos($method, '(') !== FALSE AND $method = strstr($method, '(', TRUE); | |
// Typecast boolean args | |
if (strtolower($arg) == 'true' OR strtolower($arg) == 'false') | |
{ | |
$arg = (bool) $arg; | |
} | |
// Load model, if not available | |
isset(self::$CI->$model_name) OR self::$CI->load->model($model, $model_name); | |
// Call the data method of provided model | |
// This will overwrite field "options" property | |
return method_exists(self::$CI->$model_name, $method) | |
? self::$CI->$model_name->$method($arg) | |
: array(); | |
} | |
//-------------------------------------------------------------------- | |
} | |
// End of Form class | |
/* End of file Form.php */ | |
/* Location: ./application/libraries/Form.php */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment