Forms with less pain using CI Drivers

I hate forms, and am always looking for something "just a little bit more clever" to help turn form creation into what I enjoy - programming. In this article, I want to show a little bit about using CodeIgniter's Drivers to let us make a small "toolkit" of sorts so we can build new forms quickly and painlessly.

If you aren't familiar with CI Drivers yet, I suggest Kevin Phillip's great tutorial on them.

We are going to create a simple form that does absolutely nothing, and then add a second one to demonstrate just how quickly this method lets you create them.

Our driver will create all the html for the form in the classes, which will be echoed to our view with a single variable exactly like how captcha or pagination work. We'll start by creating a "master class" that handles the generic html creation and then add a driver that is specific to a single form.

In your libraries directory, create a subdirectory called "Forms". Inside this, another subdirectory called "drivers" and a new file called "Forms.php" (these are case sensitive). Open Forms.php and let's add some code:


class Forms extends CI_Driver_Library 
{
    public $valid_drivers;
    public $CI;

    function __construct()
    {
        $this--->CI =& get_instance();
        $this->CI->load->helper('form_helper');
        
        $this->CI->load->library('form_validation');
        
        $this->valid_drivers = array('forms_welcome');
    }
}

Should be self-explanatory at this point. Notice we need to add "forms_welcome" to our valid drivers for the later step.

Now, the point - in my mind - of using drivers for all of these libraries is that it lets you create that old hierarchy of objects you learned about way back when...you know, a "Vehicle" Class with "Wheeled_Vehicle", "Winged_vehicle", etc. sub-classes? In addition, it really helps to organize your code and make it more modular, and so easier to drop into your next project. Therefore, let's add all the functionality we (may) need on every form into our Form.php, and then build specific forms with drivers. This is a demo, not a dissertation on OOP Design, so bear with the idea of the code: (apologies - I think my css is picking up the tags in my code and making spaghetti -- substitute the {} where appropriate)

function _build_table_form($table_info,$elements)
{
    $html ='';
    
    $html .= '{table id="'.$table_info['id'].'" name="'.$table_info['name'].'" class="'.$table_info['class'].'"}' ;

    foreach ($elements as $row_number=>$row)
    {
        $html .= '{tr}';
        foreach ($row as $col_number=>$col)
        {
            $html .= '{td}';
            $html .= $col;
            $html .= '{/td}';
        }

        $html .= '{/tr}';
    }

    $html .= '{/table}';
    return $html;
}

This will take a multi-dimensional array and turn it into a table. (And yes, we could have used the CI HTML Table class). the important thing here is, we are separating our layout from our data. If we want to we can add _build_tableless_form() and mark it up differently, or any other variations. Let's also add

function validate_form()
{
    return true;
}

just to show that we expect forms to have a validation function. In real life, this will be nice for our controller classes, but we won't build on that idea here. Okay, this is just our master class - let's make a real form.


class Forms_welcome extends CI_Driver
{
	function welcome_form($data)
	{
		$html = '';
		$html .= form_open('welcome/'.$data['id']);
		
		$table_info = array('name'=-->'welcome_form', 'id'=>'welcome_form','class'=>'welcome_form',);
		
		$table_array = array();
		
		$row_array[1]= form_label('Title', 'title');
		$row_array[2]= form_dropdown(
              'title',
			  $this->_titles_array(),
			  $data['title'],	
              'title'
            );   
        array_push($table_array, $row_array);           

        $row_array[1]= form_label('First Name', 'first_name');  
		$row_array[2]= form_input(array(
              'name'        => 'first_name',
              'id'          => 'first_name',
              'size'        => '50',
			  'value'		=> $data['first_name'],
            ));
        array_push($table_array, $row_array);                  
            
        $row_array[1]= form_label('Last Name', 'last_name');  
		$row_array[2]= form_input(array(
              'name'        => 'last_name',
              'id'          => 'last_name',
              'size'        => '50',
			  'value'		=> $data['last_name'],
            ));
        array_push($table_array, $row_array);
                
        $row_array[1]= ' ';  
		$row_array[2]= form_submit(false, 'Submit');              
        array_push($table_array, $row_array);
                    
        $html .= $this->_build_table_form($table_info, $table_array);   
       
        $html .= form_hidden('id', $data['id']);
        $html .= form_hidden('group_id', $data['group_id']);
            
	$html .= form_close();
	return $html;
	}
}

Nothing too complicated here. We are building the form tags and elements, using the CI form_helper, and putting them in an array for our parent class (which we call with $this->_build_table_form($table_info, $table_array);). Notice I'm using array_push() to add them; this let's me rearrange at will. Also, notice we are merely creating html for each table cell; we can put anything we want in there, it is far more flexible than you might think at first glance.

function _titles_array()
{
    return array(
        1=>'Mr.',
        2=>'Ms.',
        3=>'Majesty, ',
    );
}

This is for our dropdown. You might want to put this type of info in your Form.php, if you feel it could be reused in other forms.

function validate_form()
{
    $this->CI->form_validation->set_rules('first_name', 'First Name', 'trim|required|max_length[64]'); 
    $this->CI->form_validation->set_rules('last_name', 'Last Name', 'trim|required|max_length[64]'); 
    
    return $this->CI->form_validation->run();
}

I like putting the form validation with the form it is belongs to, but this is not a discussion on whether it should go here or on your model (or even controller), just showing it being used.

Now, one thing I detest about making forms is dealing with repopulating the fields in the view, which I think is hideous code and an inappropriate place for it. (I mean that value= (isset($var)) nonsense, or whatever your variation is.) I'm experimenting a lot these days with passing objects around CI, and I'll share some ideas in future articles. To let me demonstrate, here is a "Mock Prototype Object":

 

function build_user()
{
    $data['id'] 		= false;
    $data['group_id']		= false;
    $data['title'] 		= false;
    $data['first_name'] 	= false;
    $data['last_name'] 		= false;
    
    return $data;
}

When we shift to the controller, you see how this will work:


public function index()
{
    $this->load->driver('forms');

    $user = $this->forms->welcome->build_user();

    if ($_POST)
    {
        ... we will populate $user here with $_POST or db data
    }
		
    $this->data['form'] = $this->forms->welcome->welcome_form($user);
	
	$this->load->view('welcome_message', $this->data);
}

Not strictly correct OOP Design, but the idea here is to define the data the form is expecting ahead of time, as part of the form. I'm still building on this idea, but you can see where it is going - down the road, tell this form to expect a User object, for example.

So, I said we would demonstrate how nice this all is by quickly creating a second form. I lied. This is getting rather long, and it should be pretty obvious how to expand on what we have. So, your homework: add another driver for a a login form; username, password, and submit button. Copy/paste is encouraged!

Contact me