Tuesday, December 18, 2012

CodeIgniter Multi-Stage Form Example

Introduction to Multi-Stage Forms

There are a lot of forms on the web. Many out there have multiple stages. Take for example the forms of a shopping cart at check out. Stage 1 is usually a confirmation of what you wish to order and quantity fields that are enabled and visible as well as a Submit or Next or Continue button typically in the lower right corner of the form. Stage 2 is then shipping address so that shipping costs can be calculated and so your order can be shipped to you. Stage 3 is usually payment information with fields for a credit card number, billing address, etc.

There are two ways - that I'm familiar with - to accomplish this multi-stage form. The first way is to create a separate URL/page for each stage - i.e. cart_page_order.php --> cart_page_shipping_address.php --> cart_page_payment.php --> cart_page_thank_you.php, The second is to create a single page with appropriate if/switch statements. This post is about the second option.

Introduction to Forms in CodeIgniter

All web pages in CodeIgniter consist of a Model->View->Controller set. The Model is optional but the Controller and View are required. The Model and Controller are PHP classes while the View can be HTML or php.

The Controller (stored in ../application/controllers) will hold basic info about the form and all the validation rules. The View (stored in ../application/views) will provide the code necessary for a browser to display the form. A Model (stored in ../application/models) would be where you would store backend functions like your interactions with a DB.

If you aren't familiar with CodeIgniter you will want to become familiar with:
  • Form Helper: Helps you build forms without the tedious task of dealing with the HTML directly
  • Form Validation Class: Useful in validation form field entries

You might also look at the Template Parser Class for a faster way to build your Views.

My own convention...

I tend to make the View as generic as possible. This means that the Controller must contain all the data necessary for creating the form. This does two things for me - 1) there is only 1 file to maintain: the Controller, and 2) speeds up the dev process since once I have the generic View working for a few forms I know the problem must reside in the Controller and the way I passed in the data.

Additionally, I always create a Header, NavLinks, and Footer View for my sites. I put meta data in the Header View, a set of navigation links in the NavLinks View and any copyright, privacy policy, etc in the Footer View.

Generic Views

A View can be generic because you can pass in data to the View when display it. In the Controller class the Views to be displayed are called via:
$this->load->view(...);
So for my Header View I'd use:
$this->load->view('templates/header');
because I store the Header View in subdirectory named templates under the view subdirectory. If I want to pass the page title into the Header View then I'd use something like:
$data = array('title'=>'Multi-Stage Form Example');
$this->load->view('templates/header', $data);
The array keys of $data become variables available in the Header View so then the Header View looks something like this:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional...>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title><?php echo $title; ?></title>
    ...
</head>

<body>
In my full up sites I use the $data array to pass in the page title, meta description, meta keywords, and any CSS files I want included.

Simple Multi-Stage Form in CodeIgniter

The example code below creates a simple multi-stage form. There is no validation of the fields and the form is ugly. You can do whatever you like with CSS to make it pretty (add the CSS file name via the css portion of the Controller's header_data private class variable).

Controller

<?php

    /* --- Multi-Stage Form Example ---
    *        License: Use however you like but please leave the Source URL intact.
    *        Source URL: http://webtechnote.blogspot.com/2012/12/codeigniter-multi-stage-form-example.html
    *        Created: 12/18/12
    *       
    *        Page URL to grab will be based on the controller file name. In this case
    *        multistage_form_example.
    */

    class Multistage_form_example extends CI_Controller {
        /* --- CONSTANTS --- */
        const FORM_DATA_DIR = '/var/www/dev/CodeIgniter_2.1.3/application/form_data/';

        /* --- Public Props --- */


        /* --- Private Props --- */
        private $header_data = array('title'=>'Multi-Stage Form Example',
                                     'meta_desc'=>'Multi-Stage Form Example',
                                     'meta_kw'=>'codeigniter, form, multistage form, example',
                                     'css'=>array());
                                    
        private $footer_data = array('copyright'=>'Nobody, LLC.');
                                    
        private $form_data = array('me'=>'',
                                                             'uniqID'=>'',
                                                             'form_attr'=>array('id'=>'example_form',
                                                                                                    'class'=>'multistage_form',
                                                                                                    'style'=>'width:50%; float:left;'),
                                                             'controls'=>array());
       
        private $uniqID = '';
        private $form_start = TRUE;
        private $all_form_data = array();

        /* --- Public Methods --- */

        // Instantiate the class
        public function __construct() {

            // In CodeIgniter you must have this line whenever
            //    your class has it's own constructor method and
            //    is an extension of the Controller class
            parent::__construct();
           
            // Enable session data
            $this->load->library('session');
           
            // Set the Unique ID
            $this->uniqID = $this->input->post('uniqID', TRUE);
            if ($this->uniqID === FALSE) {
                $this->uniqID = $this->session->userdata('uniqID');
                if ($this->uniqID === FALSE) {
                    $this->uniqID = md5(microtime());
                    $this->session->set_userdata('uniqID', $this->uniqID);
                } // if ($this->uniqID === FALSE) {
            } else {
                $this->form_start = FALSE;
            } // if ($this->uniqID === FALSE)
           
           
            // Set the me variable in form_data
            $this->form_data['me'] = str_replace('.php', '', basename(__FILE__));
           
            // Set the UniqueID
            $this->form_data['uniqID'] = $this->uniqID;
        } // function __construct

       
        // This is the portion of the class that displays content
        public function index() {

            // Load Header
            $this->load->view('templates/header', $this->header_data);

            // Load Helper functions for Forms and URLs
            $this->load->helper(array('form', 'url'));
           
            $isLast = FALSE;
            if ($this->form_start === TRUE) { $this->create_page1_controls(); }
            else {
                // Save the Form Data to a text file (JSON)
                $this->save_form_data();
               
                $page = $this->input->post('page', TRUE);
               
                if ($page == 'first') { $this->create_page2_controls(); }
                elseif ($page == 'second') { $this->create_page3_controls(); }
                elseif ($page == 'last') { $isLast = TRUE; }
            } // if ($this->form_start === TRUE)
           
            if ($isLast === FALSE) {
                $this->load->view('multistage_form_example', $this->form_data);
            } else {
                $this->load->view('multistage_form_example_thank_you', array('data'=>$this->all_form_data));
            } // if ($isLast === FALSE)

            // Load Footer
            $this->load->view('templates/footer', $this->footer_data);
        } // function index
       
       
       
       
       
        /* --- Private Methods --- */
       
        // Create a Submit Button
        private function create_submit_button($page, $name = NULL, $value = NULL, $id = NULL) {
            $page = strtolower($page);
           
            // The first and last pages get special button values and ids
            $isFirst = FALSE;
            $isLast = FALSE;
            if ($page == 'first') { $isFirst = TRUE; }
            if ($page == 'last') { $isLast = TRUE; }
           
            // Default Name for the button
            if (is_null($name)) {
                $name = 'submit_' . $page;
            } // if (is_null($name))
           
            // Default Value - this allows for button names that
            //    make some sense given the current stage of the form
            if (is_null($value)) {
                if ($isFirst) { $value = 'Continue'; }
                elseif ($isLast) { $value = 'Complete'; }
                else { $value = 'Next'; }
            } // if (is_null($value))
           
            // Default id - this allows for unique CSS to be defined
            //    for the first page of the form, the last page, and then
            //    everything in between gets the same CSS. In other words
            //    there are 3 unique ids provided so that 3 unique sets
            //    of CSS can be created if desired.
            if (is_null($id)) {
                if ($isFirst) { $id = 'submit_begin'; }
                elseif ($isLast) { $id = 'submit_complete'; }
                else { $id = 'submit'; }
            } // if (is_null($id))
           
            $attrs = array('type'=>'submit',
                                         'name'=>$name,
                                         'id'=>$id,
                                         'value'=>$value,
                                         'style'=>'float:right;');
                                       
            return $attrs;
        } // function create_submit_button
       
       
        // Create hidden input to hold page
        private function create_page_field($page) {
            $attrs = array('type'=>'hidden',
                                         'name'=>'page',
                                         'value'=>$page);
                                       
            return $attrs;
        } // function create_page_field
       
       
        // Save the form data to a text file in JSON format
        private function save_form_data() {
            // Get all the current post data
            $curr_form_data = $this->input->post(NULL, TRUE);
           
            // Remove the submit button POST values
            //    The name of the submit button is passed along as a POST value
            //    which is useful for if-then statements but isn't something we
            //    need to save to the form data file or display on the thank you
            //    View.
            //
            //    The page is also sent as a POST value and while we don't want
            //    to display it on the thank you View we do want to save it so
            //    that in post-processing of the form data we can easily check
            //    how far along in the form process the user got.
            foreach ($curr_form_data AS $controlName=>$value) {
                if (strpos($controlName, 'submit') !== FALSE) { unset($curr_form_data[$controlName]); }
            } // foreach ($curr_form_data AS $controlName=>$value)
           
            // Retrieve the form's UniqueID
            $uniqID = $curr_form_data['uniqID'];
           
            // Create the form data file if is doesn't exist
            $fname = self::FORM_DATA_DIR . $uniqID . '.txt';
            if (!file_exists($fname)) { file_put_contents($fname, json_encode(array())); }
           
            // Get the previous form input data file
            $prev_form_data = json_decode(file_get_contents($fname), TRUE);
           
            // Combine the previous pages' data and the current page's data
            //    The order of $curr_form_data + $prev_form_data is important
            //    if you want the page parameter to be updated with the lasest
            //    value instead of always being set to 'first'.
            $all_form_data = $curr_form_data + $prev_form_data;
            $this->all_form_data = $all_form_data + array('fname'=>$fname);
           
            // Update the form data file
            file_put_contents($fname, json_encode($all_form_data));
        } // function save_form_data
       
       
       
        /* --- Form Specific Methods --- */
       
       
        // Set Controls for Page 1
        private function create_page1_controls() {
            $page = 'first';
           
            $cntls = array();
           
            // First Name
            $cntls[] = array('type'=>'input',
                                             'label'=>'First Name: ',
                                             'name'=>'first',
                                             'id'=>'first_name',
                                             'value'=>'John',
                                             'maxlength'=>'100',
                                             'size'=>'50',
                                             'style'=>'width:80%; margin:5px; padding:10px;');
                                           
            // Last Name
            $cntls[] = array('type'=>'input',
                                             'label'=>'Last Name: ',
                                             'name'=>'last',
                                             'id'=>'last_name',
                                             'value'=>'Doe',
                                             'maxlength'=>'100',
                                             'size'=>'50',
                                             'style'=>'width:80%; margin:5px; padding:10px;');
                                           
            // Hidden Field for Page
            $cntls[] = $this->create_page_field($page);
                                           
            // Submit Button
            $cntls[] = $this->create_submit_button($page);
                                           
           
            $this->form_data['controls'] = $cntls;
           
            return TRUE;
        } // function create_page1_controls
       
       
        // Set Controls for Page 2
        private function create_page2_controls() {
            $page = 'second';
           
            $cntls = array();
           
            // Username
            $cntls[] = array('type'=>'input',
                                             'label'=>'Username: ',
                                             'name'=>'user',
                                             'id'=>'username',
                                             'value'=>'',
                                             'maxlength'=>'20',
                                             'size'=>'25',
                                             'style'=>'width:60%; margin:5px; padding:10px;');
                                           
            // Password
            $cntls[] = array('type'=>'password',
                                             'label'=>'Password: ',
                                             'name'=>'pwd',
                                             'id'=>'pwd',
                                             'value'=>'',
                                             'maxlength'=>'20',
                                             'size'=>'25',
                                             'style'=>'width:60%; margin:5px; padding:10px;');
                                           
            // Password Confirmation
            $cntls[] = array('type'=>'password',
                                             'label'=>'Confirm Password: ',
                                             'name'=>'pwd_conf',
                                             'id'=>'pwd',
                                             'value'=>'',
                                             'maxlength'=>'20',
                                             'size'=>'25',
                                             'style'=>'width:60%; margin:5px; padding:10px;');
           
            // Hidden Field for Page
            $cntls[] = $this->create_page_field($page);
                                           
            // Submit Button
            $cntls[] = $this->create_submit_button($page);                               
           
            $this->form_data['controls'] = $cntls;
           
            return TRUE;
        } // function create_page2_controls
       
       
        // Set Controls for Page 3
        private function create_page3_controls() {
            $page = 'last';
           
            $cntls = array();
           
            // Email
            $cntls[] = array('type'=>'input',
                                             'label'=>'Email: ',
                                             'name'=>'email',
                                             'id'=>'email',
                                             'value'=>'john.doe@example.com',
                                             'maxlength'=>'100',
                                             'size'=>'50',
                                             'style'=>'width:60%; margin:5px; padding:10px;');
                                           
            // Hidden Field for Page
            $cntls[] = $this->create_page_field($page);
                                           
            // Submit Button
            $cntls[] = $this->create_submit_button($page);
           
            $this->form_data['controls'] = $cntls;
           
            return TRUE;
        } // function create_page3_controls

    } // class Multistage_form_example
?>

Views

header

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title><?php echo $title . " | " . str_replace('Tool', '', TOOL_NAME); ?> Tool</title>
   
    <meta name="description" content="<?php if (isset($meta_dec)) { echo $meta_desc; } ?>" />
    <meta name="keywords" content="<?php if (isset($meta_kw)) { echo $meta_kw; } ?>" />
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />

    <!-- CSS Sheets -->
    <?php
        if (isset($css) && !empty($css) && !is_null($css)) {       
            $str = '';
            $linkBeginStr = '<link rel="stylesheet" href="' . BASE_URL;
            $linkEndStr = '" type="text/css" media="screen" />';
            foreach ($css as $key=>$sht) {
                $str .= $linkBeginStr . $sht . $linkEndStr . "\n\r";
            } // foreach ($css as $key=>$sht)
            echo $str . "\n\r";
        } // if (isset($css) && !empty($css) && !is_null($css))
    ?>
</head>
    <body>

footer

                <div id="footer_content">
                    <table border="0" style="width: 100%">
                        <caption></caption>
                        <col />
                        <col />
                        <tbody>
                            <tr>
                                <td><div id="copyright"><?php echo $copyright; ?> &copy; 2012</div></td>
                                <td style="text-align:right;margin-left:auto;margin-right:0;"><a
                                    href="welcome">Home</a> - <a
                                    href="aboutus" rel="NOFOLLOW">About Us</a> - <a href="privacy" rel="NOFOLLOW">Privacy
                                    Policy</a> - <a href="terms" rel="NOFOLLOW">Terms of Service</a></td>
                            </tr>
                        </tbody>
                    </table>
                </div>
          
            </div>
        </div>
    </body>
</html>

multistage_form_example_view

<?php

    /* --- Multi-Stage Form Example ---
    *        License: Use however you like but please leave the Source URL intact.
    *        Source URL: http://webtechnote.blogspot.com/2012/12/codeigniter-multi-stage-form-example.html
    *        Created: 12/18/12
    */
   

    /* --- This is a generic form generator ---
    *        This View can be used with any Controller that provides the
    *        correct info.
    *
    *        The Controller must provide the details of each control in the
    *        desired form. The following attributes are required for each
    *        control:
    *            type:    input/password/submit/hidden/textarea/dropdown/etc
    *            name: this will become the name attribute for whatever control
    *                            you are describing, the name attribute is 'first' in this example:
    *                            <input type="text" name="first" value="John" id="first_name" maxlength="100" size="50" style="width:80%; margin:5px; padding:10px;"  />
    *
    *        Notes:
    *            The label and hidden form controls get special attention by the code in order
    *            to be HTML coded correctly. All file upload controls have been ignored for this
    *            example.
    */
   

    /* --- Form Helper is Required ---
    *        The Form Helper was loaded in the Controller and allows for quick
    *        coding of the form.
    */
   
    // These constants are nice for keeping the HTML code generated looking
    //    nice and clean and human readable.
    if (!defined('NEWLINE')) { define('NEWLINE', "\n"); }
    if (!defined('TAB')) { define('TAB', "\n"); }
   
   
    // Create the form HTML: <form method="..." action="..." ...>
    echo form_open($me, $form_attr) . NEWLINE;
   
    // Loop thru all the control
    //    $type will be hidden, input, password, etc.
    //    $attrs will be an array of attributes that will be passed to the
    //        appropriate function
    //
    //    For the sake of this example we are ignoring form_open_multipart() and form_upload().
    //    They pertain to uploading a file via the form.
    foreach ($controls AS $key=>$attrs) {
        // Extract the control type
        if (isset($attrs['type'])) {
            $type = $attrs['type'];
            unset($attrs['type']);
        } else {
            // Error
            die('Control (key = ' . $key . ') has no type attribute');
        } // if (isset($attrs['type']))
       
        // Make sure the control has a name attribute
        if (!isset($attrs['name'])) {
            // Error
            die('Control (key = ' . $key . ') has no name attribute');
        } // if (!isset($attrs['name']))
       
        // Check the $attrs array for a label and extract it if present
        if (isset($attrs['label'])) {
            echo TAB . form_label($attrs['label'], $attrs['name']) . NEWLINE;
            unset($attrs['label']);
        } // if (isset($attrs['label']))
       
        // We use an eval statement here to allow us the greatest amount of freedom
        //    for future use. If we wished to do so, we could create some form of
        //    form control attribute validation. For example, check that an input control
        //    has a name and value in the attribute array and that both non-empty and non-NULL.
        if ($type != 'hidden') {
            $str = 'echo TAB . form_' . strtolower($type) . '($attrs) . NEWLINE;';
        } else {
            $str = 'echo TAB . form_hidden("' . $attrs['name'] . '", "' . $attrs['value'] . '") . NEWLINE;';
        } // if ($type != 'hidden')
       
        eval($str);
    } // foreach ($controls AS $type=>$attrs)
   
   
    // Create a hidden field for the UniqueID so that data can be properly saved
    echo TAB . form_hidden('uniqID', $uniqID) . NEWLINE;
   
   
    // Close the form
    echo form_close();


/* End of File
*        This is the CodeIgniter preferred method of ending the file
*        ../views/multistage_form_example.php
*/

multistage_form_example_thank_you

 <?php

    /* --- Multi-Stage Form Example ---
    *        License: Use however you like but please leave the Source URL intact.
    *        Source URL: http://webtechnote.blogspot.com/2012/12/codeigniter-multi-stage-form-example.html
    *        Created: 12/18/12
    */
   
   
    /* --- This is a generic thank you View to go along with the form generator ---
    *       
    *
    *        Notes:
    *            Only the user entered form data is passed to this screen.
    */
   
    // These constants are nice for keeping the HTML code generated looking
    //    nice and clean and human readable.
    if (!defined('NEWLINE')) { define('NEWLINE', "\n"); }
    if (!defined('TAB')) { define('TAB', "\n"); }
   
    // Use the UniqueID as a Confirmation #
    //    Obviously, this confirmation number could be shortened, modified to
    //    to put dashes every 4 or 6 characters, etc.
    echo '<h1>Confirmation Number: ' . $data['uniqID'] . '</h2>' . NEWLINE;
    unset($data['uniqID']);
   
    foreach ($data AS $controlName=>$value) {
        // No need to display the last page the user got to back to
        //    the user.
        if ($controlName == 'page') { continue; }
       
        // No need to display the confirmed password back to the user.
        if ($controlName == 'pwd_conf') { continue; }
       
        // No need to tell the user where the form data is being stored
        if ($controlName == 'fname') { continue; }
       
        // Obviously the if statements above could be combined into 1 but I wanted
        //    them to be explicit for this example and individually commented.
       
        // If we adopt naming conventions in the Controller for certain very common
        //    form fields then we can dress them up a little here.
        switch (strtolower($controlName)) {
            case 'first':
            case 'last':
                $controlName = ucwords($controlName . ' Name');
                break;
               
            case 'user':
                $controlName = 'Username';
                break;
               
            case 'pwd':
                $controlName = 'Password';
                break;
               
            default:
                $controlName = ucwords(str_replace('_', ' ', $controlName));
               
        } // switch (strtolower($controlName))
       
        // Display everything else
        echo TAB . $controlName . ': ' . $value . '<br>' . NEWLINE;
    } // foreach ($data AS $controlName=>$value)
   
    // Put some space between the data and the footer
    echo '<br><br><br>';
   


/* End of File
*        This is the CodeIgniter preferred method of ending the file
*        ../views/multistage_form_example_thank_you.php
*/

Multi-Stage Form in CodeIgniter with Form Field Validation

At each stage there are some fields that are required while others are not. Let's go back to the example form at the beginning with the shipping address. The Name, Address Line 1, City, State and Zip Code are all likely required while the Address Line 2 is optional. Some common form validation rules on these fields would be to require them and that the string provided be alphabetical characters, alpha-numeric characters, or numeric characters. You might also want to limit the Zip Code to being an integer with no more than 5 digits and the State be a 2 character alphabetical string from a list of state abbreviations.

Dealing with multistage forms in CodeIgniter is a bit of a hack as the MVC doesn't come with a native means of handling the validation across multiple form stages all with the same file name. I've seen some suggestions that CodeIgniter isn't well designed for multistage forms and that it is better to do multistage forms in manner where each stage has it's own Controller and View.

Having a separate Controller for each stage is certainly one way to accomplish the task and somewhat less complicated to code. However, I like to minimize the number of files I need to maintain. In my setup the View is reusable by any number of forms and simple enough to need very little maintenance going forward. I don't have a Model for this example but it seems that should eaily be reusable between form stages. Leaving me with only one Controller file in my current architecture.

Notes on the inclusion of Multi-Stage Form Validation in the Controller

First you have to add the Form Validation library and I like to add delimiters - again so I can control how the errors look (make them RED and BOLD) via CSS.
// Load the Form Validation library
$this->load->library('form_validation');
$this->form_validation->set_error_delimiters('<div class="error">', '</div>');
Then you need to add validation rules like the ones below:
$this->form_validation->set_rules('first', 'First Name', 'trim|required|alpha|xss_clean');
$this->form_validation->set_rules('last', 'Last Name', 'trim|required|alpha|xss_clean');
Then you have to run the form validation like so:
$this->form_validation->run()
Typically you would use an if statement like:
if ($this->form_validation->run() == FALSE)    { ... }
This allows you to determine if the validation failed (i.e. returned FALSE). If it fails you don't want to progress to the next stage you want the user to fix the problem and then move on to the next stage. So you end up with different View logic tree branches in the Controller depending on whether or not the validation passed or failed. Something like the following:
              if ($this->form_start === FALSE && $this->form_validation->run() == FALSE)    {
                // Validation Failed
               
                // Save the Form Data to a text file (JSON)
                $this->save_form_data();
               
                // Determine where we are in the form process
                $isLast = FALSE;
                if ($page == 'first') {
                    $this->create_page1_controls();
                }    elseif ($page == 'second') {
                    $this->create_page2_controls();
                } elseif ($page == 'last') {
                    $this->create_page3_controls();
                } // if ($page == 'first')
            } else {
                // All Validation Checks Passed
               
                // Determine where we are in the form process
                $isLast = FALSE;
                if ($this->form_start === TRUE) { $this->create_page1_controls(); }
                else {
                    // Save the Form Data to a text file (JSON)
                    $this->save_form_data();
                   
                    if ($page == 'first') {
                        $this->create_page2_controls();                        
                    }    elseif ($page == 'second') {
                        $this->create_page3_controls();
                    } elseif ($page == 'last') {
                        $isLast = TRUE;
                    } //
                } // if ($this->form_start === TRUE)
            } // if ($this->form_validation->run() == FALSE)
However, before you can run the form validation you have to set the rules or it will always return FALSE. These rule MUST come before the form validation is run. And you CANNOT have the validation rules for fields not in the current stage run or it will always return FALSE.

Those two wrinkles have caused me a lot of headaches in the past. Below are the updated files for our simple example.

Controller

The controller doesn't change dramatically but 
<?php

    /* --- Multi-Stage Form Example ---
    *        License: Use however you like but please leave the Source URL intact.
    *        Source URL: http://webtechnote.blogspot.com/2012/12/codeigniter-multi-stage-form-example.html
    *        Created: 12/18/12
    *       
    *        Page URL to grab will be based on the controller file name. In this case
    *        multistage_form_example.
    */

    class Multistage_form_example extends CI_Controller {
        /* --- CONSTANTS --- */
        const FORM_DATA_DIR = '/var/www/dev/CodeIgniter_2.1.3/application/form_data/';

        /* --- Public Props --- */


        /* --- Private Props --- */
        private $header_data = array('title'=>'Multi-Stage Form Example',
                                     'meta_desc'=>'Multi-Stage Form Example',
                                     'meta_kw'=>'codeigniter, form, multistage form, example',
                                     'css'=>array());
                                    
        private $footer_data = array('copyright'=>'Nobody, LLC.');
                                    
        private $form_data = array('me'=>'',
                                                             'uniqID'=>'',
                                                             'form_attr'=>array('id'=>'example_form',
                                                                                                    'class'=>'multistage_form',
                                                                                                    'style'=>'width:50%; float:left;'),
                                                             'controls'=>array());
       
        private $uniqID = '';
        private $form_start = TRUE;
        private $all_form_data = array();

        /* --- Public Methods --- */

        // Instantiate the class
        public function __construct($uniqID = NULL) {

            // In CodeIgniter you must have this line whenever
            //    your class has it's own constructor method and
            //    is an extension of the Controller class
            parent::__construct();
           
            // Enable session data
            $this->load->library('session');
           
            // Set the Unique ID
            $this->uniqID = $this->input->post('uniqID', TRUE);
            if ($this->uniqID === FALSE) {
                $this->uniqID = $this->session->userdata('uniqID');
                if ($this->uniqID === FALSE) {
                    $this->uniqID = md5(microtime());
                    $this->session->set_userdata('uniqID', $this->uniqID);
                } // if ($this->uniqID === FALSE) {
            } else {
                $this->form_start = FALSE;
            } // if ($this->uniqID === FALSE)
           
           
            // Set the me variable in form_data
            $this->form_data['me'] = str_replace('.php', '', basename(__FILE__));
           
            // Set the UniqueID
            $this->form_data['uniqID'] = $this->uniqID;
        } // function __construct

       
        // This is the portion of the class that displays content
        public function index() {

            // Load Header
            $this->load->view('templates/header', $this->header_data);

            // Load Helper functions for Forms and URLs
            $this->load->helper(array('form', 'url'));
           
            // Load the Form Validation library
            $this->load->library('form_validation');
            $this->form_validation->set_error_delimiters('<div class="error">', '</div>');
           
            // Get the current page
            $page = $this->input->post('page', TRUE);
           
            // Form Field Validation
            if ($page == 'first') {
                $this->form_validation->set_rules('first', 'First Name', 'trim|required|alpha|xss_clean');
                $this->form_validation->set_rules('last', 'Last Name', 'trim|required|alpha|xss_clean');
            } elseif ($page == 'second') {
                $this->form_validation->set_rules('user', 'Username', 'trim|required|alpha_dash|xss_clean|callback_user_chk');
                $this->form_validation->set_rules('pwd', 'Password', 'trim|required|alpha_numeric|xss_clean|matches[pwd_conf]');
            } elseif ($page == 'last') {
                $this->form_validation->set_rules('email', 'Email', 'trim|valid_email|xss_clean');
            } // if ($page == 'first')
           
           
            // Check to see if the form was filled in correctly
            if ($this->form_start === FALSE && $this->form_validation->run() == FALSE)    {
                // Validation Failed
               
                // Save the Form Data to a text file (JSON)
                $this->save_form_data();
               
                // Determine where we are in the form process
                $isLast = FALSE;
                if ($page == 'first') {
                    $this->create_page1_controls();
                }    elseif ($page == 'second') {
                    $this->create_page2_controls();
                } elseif ($page == 'last') {
                    $this->create_page3_controls();
                } // if ($page == 'first')
            } else {
                // All Validation Checks Passed
               
                // Determine where we are in the form process
                $isLast = FALSE;
                if ($this->form_start === TRUE) { $this->create_page1_controls(); }
                else {
                    // Save the Form Data to a text file (JSON)
                    $this->save_form_data();
                   
                    if ($page == 'first') {
                        $this->create_page2_controls();                        
                    }    elseif ($page == 'second') {
                        $this->create_page3_controls();
                    } elseif ($page == 'last') {
                        $isLast = TRUE;
                    } //
                } // if ($this->form_start === TRUE)
            } // if ($this->form_validation->run() == FALSE)
           
           
            if ($isLast === FALSE) {
                $this->load->view('multistage_form_example', $this->form_data);
            } else {
                $this->load->view('multistage_form_example_thank_you', array('data'=>$this->all_form_data));
            } // if ($isLast === FALSE)

            // Load Footer
            $this->load->view('templates/footer', $this->footer_data);
        } // function index
       
       
       
        /* --- Form Validation Methods --- */
       
        // This corresponds to the callback_user_chk validation requirement above
        public function user_chk($str) {
            // Since it is already being checked for being alpha-numeric (allowed to include dashes
            //    and underscores). We only need to check that there isn't already a user by this name
            //    in our DB.
           
            // Since I haven't set up a DB or broader tool for this example there is no check of the
            //    DB and therefore the check should always return TRUE
            return TRUE;
           
           
            // However, were there a check and it failed then the following 2 lines would execute
            //    providing the user with a explanation of why the form data could not be accepted
            //    as is.
            $this->form_validation->set_message(fetchAfter("::", __METHOD__), 'Username ' . $str . ' aleady exists in the system. Please try another username.');
            return FALSE;
        } // function user_chk
       
       
       
        /* --- Private Methods --- */
       
        // Create a Submit Button
        private function create_submit_button($page, $name = NULL, $value = NULL, $id = NULL) {
            $page = strtolower($page);
           
            // The first and last pages get special button values and ids
            $isFirst = FALSE;
            $isLast = FALSE;
            if ($page == 'first') { $isFirst = TRUE; }
            if ($page == 'last') { $isLast = TRUE; }
           
            // Default Name for the button
            if (is_null($name)) {
                $name = 'submit_' . $page;
            } // if (is_null($name))
           
            // Default Value - this allows for button names that
            //    make some sense given the current stage of the form
            if (is_null($value)) {
                if ($isFirst) { $value = 'Continue'; }
                elseif ($isLast) { $value = 'Complete'; }
                else { $value = 'Next'; }
            } // if (is_null($value))
           
            // Default id - this allows for unique CSS to be defined
            //    for the first page of the form, the last page, and then
            //    everything in between gets the same CSS. In other words
            //    there are 3 unique ids provided so that 3 unique sets
            //    of CSS can be created if desired.
            if (is_null($id)) {
                if ($isFirst) { $id = 'submit_begin'; }
                elseif ($isLast) { $id = 'submit_complete'; }
                else { $id = 'submit'; }
            } // if (is_null($id))
           
            $attrs = array('type'=>'submit',
                                         'name'=>$name,
                                         'id'=>$id,
                                         'value'=>$value,
                                         'style'=>'float:right;');
                                       
            return $attrs;
        } // function create_submit_button
       
       
        // Create hidden input to hold page
        private function create_page_field($page) {
            $attrs = array('type'=>'hidden',
                                         'name'=>'page',
                                         'value'=>$page);
                                       
            return $attrs;
        } // function create_page_field
       
       
        // Save the form data to a text file in JSON format
        private function save_form_data() {
            // Get all the current post data
            $curr_form_data = $this->input->post(NULL, TRUE);
           
            // Remove the submit button POST values
            //    The name of the submit button is passed along as a POST value
            //    which is useful for if-then statements but isn't something we
            //    need to save to the form data file or display on the thank you
            //    View.
            //
            //    The page is also sent as a POST value and while we don't want
            //    to display it on the thank you View we do want to save it so
            //    that in post-processing of the form data we can easily check
            //    how far along in the form process the user got.
            foreach ($curr_form_data AS $controlName=>$value) {
                if (strpos($controlName, 'submit') !== FALSE) { unset($curr_form_data[$controlName]); }
            } // foreach ($curr_form_data AS $controlName=>$value)
           
            // Retrieve the form's UniqueID
            $uniqID = $curr_form_data['uniqID'];
           
            // Create the form data file if is doesn't exist
            $fname = self::FORM_DATA_DIR . $uniqID . '.txt';
            if (!file_exists($fname)) { file_put_contents($fname, json_encode(array())); }
           
            // Get the previous form input data file
            $prev_form_data = json_decode(file_get_contents($fname), TRUE);
           
            // Combine the previous pages' data and the current page's data
            //    The order of $curr_form_data + $prev_form_data is important
            //    if you want the page parameter to be updated with the lasest
            //    value instead of always being set to 'first'.
            $all_form_data = $curr_form_data + $prev_form_data;
            $this->all_form_data = $all_form_data + array('fname'=>$fname);
           
            // Update the form data file
            file_put_contents($fname, json_encode($all_form_data));
        } // function save_form_data
       
       
       
        /* --- Form Specific Methods --- */
       
       
        // Set Controls for Page 1
        private function create_page1_controls() {
            $page = 'first';
           
            $cntls = array();
           
            // First Name
            $cntls[] = array('type'=>'input',
                                             'label'=>'First Name: ',
                                             'name'=>'first',
                                             'id'=>'first_name',
                                             'value'=>'John',
                                             'maxlength'=>'100',
                                             'size'=>'50',
                                             'style'=>'width:80%; margin:5px; padding:10px;');
                                           
            // Last Name
            $cntls[] = array('type'=>'input',
                                             'label'=>'Last Name: ',
                                             'name'=>'last',
                                             'id'=>'last_name',
                                             'value'=>'Doe',
                                             'maxlength'=>'100',
                                             'size'=>'50',
                                             'style'=>'width:80%; margin:5px; padding:10px;');
                                           
            // Hidden Field for Page
            $cntls[] = $this->create_page_field($page);
                                           
            // Submit Button
            $cntls[] = $this->create_submit_button($page);
                                           
           
            $this->form_data['controls'] = $cntls;
           
            return TRUE;
        } // function create_page1_controls
       
       
        // Set Controls for Page 2
        private function create_page2_controls() {
            $page = 'second';
                       
            $cntls = array();
           
            // Username
            $cntls[] = array('type'=>'input',
                                             'label'=>'Username: ',
                                             'name'=>'user',
                                             'id'=>'username',
                                             'value'=>'',
                                             'maxlength'=>'20',
                                             'size'=>'25',
                                             'style'=>'width:60%; margin:5px; padding:10px;');
                                           
            // Password
            $cntls[] = array('type'=>'password',
                                             'label'=>'Password: ',
                                             'name'=>'pwd',
                                             'id'=>'pwd',
                                             'value'=>'',
                                             'maxlength'=>'20',
                                             'size'=>'25',
                                             'style'=>'width:60%; margin:5px; padding:10px;');
                                           
            // Password Confirmation
            $cntls[] = array('type'=>'password',
                                             'label'=>'Confirm Password: ',
                                             'name'=>'pwd_conf',
                                             'id'=>'pwd',
                                             'value'=>'',
                                             'maxlength'=>'20',
                                             'size'=>'25',
                                             'style'=>'width:60%; margin:5px; padding:10px;');
           
            // Hidden Field for Page
            $cntls[] = $this->create_page_field($page);
                                           
            // Submit Button
            $cntls[] = $this->create_submit_button($page);                               
           
            $this->form_data['controls'] = $cntls;
           
            return TRUE;
        } // function create_page2_controls
       
       
        // Set Controls for Page 3
        private function create_page3_controls() {
            $page = 'last';
                       
            $cntls = array();
           
            // Email
            $cntls[] = array('type'=>'input',
                                             'label'=>'Email: ',
                                             'name'=>'email',
                                             'id'=>'email',
                                             'value'=>'john.doe@example.com',
                                             'maxlength'=>'100',
                                             'size'=>'50',
                                             'style'=>'width:60%; margin:5px; padding:10px;');
                                           
            // Hidden Field for Page
            $cntls[] = $this->create_page_field($page);
                                           
            // Submit Button
            $cntls[] = $this->create_submit_button($page);
           
            $this->form_data['controls'] = $cntls;
           
            return TRUE;
        } // function create_page3_controls

    } // class Multistage_form_example
?>

View

This is the only part of the multistage_form_example View that changes - we add the echo validation_errors(); line just under the form_open line.
    // Create the form HTML: <form method="..." action="..." ...>
    echo form_open($me, $form_attr) . NEWLINE;
    echo validation_errors();

Downloads

None yet. Blogger won't let me attach anything that isn't an image or video. Once I get around to creating a DropBox or similar account I zip the files up and put them up there and then provide a link here (assuming I don't forget). But all the code is already in the post so you should be able to just copy and paste.

2 comments:

  1. Nice Tutorial...i got useful information from this tutorial,here is a way to findsimple registration form using codeigniter

    ReplyDelete
  2. what codeigniter version can this code be used?..i use php 5.3..error

    ReplyDelete