Welcome Guest, Not a member yet? Register   Sign In
best practices on Form Reuse - keeping all forms in a central location with validation
#21

[eluser]chuckleberry13[/eluser]
No I wasn't implying you or anybody should have two separate Controllers processing the same form. I was asking if we agree that the Controller should handle the validation rules then how to share that form across several Controllers without having to re-write the validation rules every time I want to include that particular form.
#22

[eluser]Randy Casburn[/eluser]
I'm with you and your way cool handle chuckle!

a form is a form is a form...it is not locked to a controller or a view or an application or a panel or the left side or middle or top of a page. It may be used in twelve different applications that are similar but with different controllers and views and databases...but the forms might just have the same field sets (or similar).

I'm with you on this.

Randy

p.s. it is true that I'm a little nuts though.
#23

[eluser]chuckleberry13[/eluser]
@randy your killing me with this going insane thing.

The more I think about your idea the more it makes sense. That way if I include the same field but in another form its validation rules/callback function is triggered. I am thinking I can just set a bank of validation rules and test if that field was passed in and if so attach its validation rule to the $rule array. I like the idea of that. I'm going to play around with it and see what becomes of it. Thanks for your help.

@Colin I am still interested in alternative methods if you would care to elaborate.
#24

[eluser]Colin Williams[/eluser]
Quote:That way if I include the same field but in another form its validation rules/callback function is triggered. I am thinking I can just set a bank of validation rules and test if that field was passed in and if so attach its validation rule to the $rule array.

Not a bad idea. You're essentially building a system of form widgets. I think a well thought-out form widget library could be in order for that. The only concern is that it would lock you into strict conventions (although convention over configuration is all the rage, if the convention doesn't extend all the way through the process, your just creating more work somewhere else). Rules are already fairly simple to define, but it's that extra processing that current rule functions can't do by themselves that might be useful.

Quote:a form is a form is a form...it is not locked to a controller or a view or an application or a panel or the left side or middle or top of a page

I don't think I was speaking to the contrary. But I'm not sure I can imagine a single form shared among Controllers. Even if it had the exact same field names and HTML markup, it's going to serve an individual purpose. And what happens when one Controller requires more from the form? You've got to go ahead and make yourself two separate forms anyway. Feel free to prove me wrong by example.
#25

[eluser]chuckleberry13[/eluser]
The situation I'm running into is my user sign up form. I'm finding I need to display that form in several locations because of the nature of the project. Just trying to find the best way to do that and so far wigetizing the forms seems to be the only way I can come up with.

I am open to another way.
#26

[eluser]JoostV[/eluser]
Since setting validation, friendly usernames, etc is a repetitive taks, you can put these tasks in a custom library, forms.php. This will do all validation setup, validation itself, and return prepped input to the controller.

The only 'flaw' is that my forms.php spits out html for every input (not the entire form, but just the <input type="text" name="foo" value="bar" /&gtWink. In theory this has to be done in the view, but that would require a lot more repetitive programming and would also introduce php logic into the view, which is not a good idea either.

In your controller, you simply set up an array containg some metadata about the form and all the fieds that should go in your form:
Code:
// Form metadata
$form['metadata']['frm_id'] = 'login_form';
$form['metadata']['action'] = 'login/form';

// Field 1
$form['form_elements'][] = array(
'attributes' => array('name' => 'lgn', 'maxlength' => '16', 'onclick' => "this.value=''", 'class' => 'input_medium'),
'type' => 'input',
'process' => true,
'friendly_name' => 'Login',
'validation' => 'trim|required|maxlength[16]|xssclean',
'ini_value' => ''
);

// Field 2
$form['form_elements'][] = array(
'attributes' => array('name' => 'pw', 'maxlength' => '16', 'onclick' => "this.value=''", 'class' => 'input_medium'),
'type' => 'password',
'process' => true,
'friendly_name' => 'Password',
'validation' => 'trim|required|maxlength[16]|xssclean',
'ini_value' => ''
);

Now, from your controller, you call the libary and pass your form array:
Code:
// Load form library
$this->load->library('forms');

// Create a new form
$login_form = $this->forms->initiate($formelements);

The libary will return an object to the controller, holding:
1. form metadata, eg:
Code:
$form->metadata['validated'] = false;
2. an array with all inputs for your forms, like
Code:
$formelements[0] = '<input type="text" name ="lg" maxlength="16">';
$formelements[1] = '<input type="password" name ="pw" maxlength="16">';
3. an array containing key => value pairs of all form fields and their values, e.g.
Code:
$form->form_values['lg'] = 'Jopie';
$form->form_values['pw'] = 'verysecret';

Next thing you do is out all the form logic in the library. It's a tedious task, but you will only need to do it once and it will leave you with a generic, reusable library.
To the library, an array of form elments is passed. This array contains info about name, attributes, type, user friendly name, validation an initial value for every field.

In the library forms.php, you write functions that
1. set validation, like
Code:
// Construct validation array
foreach ($formelements as $element) {

        // Set validation rule
        $rules[$element['attributes']['name']] = $element['validation'];

}

// Set the rules, using the array we constructed earlier
$CI->validation->set_rules($rules);

2. Set friendly user names:
Code:
// Construct an array of friendly user names
foreach ($formelements as $element) {

    // Set a user friendly name
    $fields[$element['attributes']['name']] = $element['friendly_name'];

    // Use this user friendly name as label while we're at it
    $this->formelements[$element['attributes']['name']]['label'] = $element['friendly_name'];

}

// Set friendly user names, using the array we constructed earlier
$CI->validation->set_fields($fields);

3. Run validation
Code:
if ($CI->validation->run() == true) { // Validation was successful

    $this->metadata['validated'] = true;

    // Construct an array of key value pairs (fields that need to be processes and their value)
    foreach ($form_elements as $element) {

        // Only process data that need to be processed!
        if (isset($element['process']) && $element['process'] == true) {
            $this->form_results[$element['attributes']['name']] = $CI->validation->$element['attributes']['name'];
        }

    }

}
else { // Validation was not succesful
    
    $this->metadata['validated'] = false;
    $this->validation_error = $CI->$this->validation->error_string;

    // Get the proper value for every field.
    // if the form was posted, then the value is $this->validation->$element['attributes']['name']
    // if there is no post then the value is $element['ini_value'], which we set in the array

    // Construct the html for the input calling a function by $element['type']
    
}

4. For every type of input you need to write a function that will return the html for the field. This is an example:
Code:
function input($element)
{
    $data = $element['attributes'];
    $data['value'] = $this->form_values[$element['attributes']['name']];
    return form_input($data);
}

Summarizing, when you have initiated you form in the controller, you will have an object that contains:
1. metadata about the id of the form, wheteher it was posted, whether it validated or not, etc.
2. html for every input in the form
3. a label for every input in the form
4. validation error string
5. values that you can store to a database, etc if the form was succesfully validated.
#27

[eluser]chuckleberry13[/eluser]
Thanks for sharing that JoostV. I might look into it sometime but last night I setup the controller handling the form to take in an extra argument which will make it a view partial and I think I like that setup the best for now and here is my reason. I can easily include that form anywhere with one line of code in any of my views like this.

Code:
//Parameter 1 is the controller containing the form
//Parameter 2 is where the form will redirect if it passes validation. If it fails validation it re-displays the form with the entered data and validation errors.
//Parameter 3 is the function inside the controller that contains the form/view partial

<?= modules::run('controller', 'account/will_retire/2', 'signup') ?>

This way its one line, and the validation and form processing is handled in one central location with no extra work. If anybody sees how this could be a problem I wouldn't mind discussing it.

The reason I like this over creating a library to handle forms is I don't have to add anything to the library every time I add a form element. Maybe I misunderstood your library but you do have to do that right?
#28

[eluser]Colin Williams[/eluser]
Quote:The reason I like this over creating a library to handle forms is I don’t have to add anything to the library every time I add a form element. Maybe I misunderstood your library but you do have to do that right?

Looks to me like he had this happen in the Controller. Using ME like that, are you running validation in 'controller:Confusedignup' or just setting validation rules there? I'm not sure how that works out, if it's only being called from the view, then how does it then also run on submit?
#29

[eluser]chuckleberry13[/eluser]
Yes its using ME. Sorry if that was a little vagiue.

So here is the controller containing the form.

We will call it Form Controller
Code:
class Form extends Controller {
    
    function Form()
    {
       parent::Controller();
    }


function signup($partial = NULL)
    {    
        $this->load->library('validation');
        
        $this->validation->set_error_delimiters('<div class="error">', '</div>');
        
            $rules['first_name'] = "required|xss_clean|trim";
            $rules['last_name'] = "required|xss_clean|trim";
            $rules['phone'] = "required|xss_clean|trim";
            $rules['username'] = "required|xss_clean|trim|min_length[4]|max_length[25]|callback_check_username";
            $rules['password'] = "required|xss_clean|trim|min_length[4]";
            $rules['rpassword'] = "required|xss_clean|trim|matches[password]";
            $rules['email'] = "required|xss_clean|trim|valid_email|trim|callback__check_email";
            $rules['remail'] = "required|xss_clean|trim|valid_email|matches[email]";
            
        $this->validation->set_rules($rules);

            $fields['first_name'] = 'First Name';
            $fields['last_name'] = 'Last Name';
            $fields['phone'] = 'Phone';
            $fields['username'] = 'Username';
            $fields['password'] = 'Password';
            $fields['rpassword'] = 'Password Confirmation';
            $fields['email'] = 'Email Address';
            $fields['remail'] = 'Email Confirmation';
        
        $this->validation->set_fields($fields);
    
        if($this->validation->run() == FALSE)
        {
            $this->set_title('Account - Create Account!');
            $this->set_header('Create Your PyrBlu Account!','FEBD23');
            
            $this->add_js(array('jquery.maskedinput','jquery.passmeter','forms','create_account','jquery.validate'));
            $subdata['terms'] = $this->load->view('account/terms_txt', '', TRUE);    
            $data['body'] = $this->load->view('account/signup_form', $subdata, TRUE);
        }
        else
        {
            $this->user->signup();
            if($partial)
                redirect($partial);
            else
            {
                $this->set_title('Account - Creation Success!');
                $this->set_header('Account Successfully Created!','#9fd718');
                $subdata['user_data'] = $this->user->data;
                $data['body'] = $this->load->view('account/signup_success', $subdata, TRUE);
            }
        }
        
        if($partial)
            return $data['body'];
        else
            $this->display($data);        
    }

Now here is a view that I want to include this form in but only as a widget and on this view when the form passes validation I want it to redirect to page/newsletter.

Code:
&lt;?= modules::run('form', 'page/newsletter', 'signup') ?&gt;

So my controller 'Form' holds the validation rules, the callback functions, handles the validation as well as serve up the form. It then redirects if the partial variable is present. Since I actually use this form via the URL (forms/signup) you'll notice the controller is set to spit out its own success page if there is not redirect variable present.

Did that make any sense? This seemed to me the easiest way to reuse this form. What do you think?
#30

[eluser]Colin Williams[/eluser]
Ah, that is clear. I forgot that you were embedding this form on many page views and thought you were calling the ME module controller to extend another form or share form widgets among many forms (you can see how I was dumbfounded!) I've been critical of ME recently, but I'm starting to see more usages where it makes certain things, especially this example, extremely simple to implement.

And in other examples of ME Controllers, the way they returned data seemed odd to me, but I like more how your Controller checks the context in which it is called, and either returns data or generates output. It's a smart use of ME for sure, and I suppose I'm just focusing on the ways it can be abused.




Theme © iAndrew 2016 - Forum software by © MyBB