Client validation of array

Hello,

in other words - is it possible to use validation rules like this:

first, I am expecting the variable to be something like this:




array(

  '0' => 'value1',

  '152' => 'value2',

  '34' => 'value3',

  ...

)



…then I create my own Client-Side validator:




class ArrayValues extends CValidator

{

    public function clientValidateAttribute($object,$attribute)

    {

        $message=Yii::t('yii','{attribute} must be {value}.');

        if(is_array($attribute))

        {

            foreach($attribute as $item)

            {

                if($item - some validation)

                {

		    $message=strtr($message, array(

  		        '{value}'=>'..',

		        '{attribute}'=>$object->getAttributeLabel($attribute),

		    )); 

                }

            }

        }

        return "messages.push(".CJSON::encode($message).");";

    }

}



I already posted a question about this problem, but the answer did not suits me:

http://www.yiiframework.com/forum/index.php/topic/34256-validate-dynamically-filled-array-attribute-on-client-side/

Please somebody help,

G.Velchev

Can’t you just create a method in your model, that will validate the array. You can use it later on in your controller. If you are worried about JS validation (ActiveForm validation), you could run that method on ajax $_post variables.

It seems to me, that this might be a bit more straightforward approach.

Thank you for your answer!

Of course I can do it with a method inside my model class, but I don’t see what’s the difference if this method is inside the model class or the validator will be a class that extends CValidator class when I’m validating the input on server-side. I just don’t want to do this with ajax, I am searching for a way to perform the validation on JS level without sending any request to the server.

Using client validation, means that you are making server side requests. If you are keen, on not using AJAX, you could do something like this :


//turn all form fields to a JS array

var f = $('form#Your_form').serializeArray();

//Than you can loop trough all form fields

//and validate them

$.each(f, function(n,v){

    //n here is the array index for current field

    //where v is the JS object for current field

    //you can use v.name - to get filed name, and v.value to get field's value

    if(v.name == 'something' && v.value == 'some validation')

        {

            //do something

        }

        else

            {

                //on error do something..

                alert('Something\' is wrong!')

            }

            

})

//use this snippet inside of $('input#field').change(function(){})



Thank you again for trying to help me.

After trying all my guesses I wrote a similar solution with the small difference that I am foreaching the form fields after clicking on the submit button of the form(not on change event). So the problem comes here. When the button is clicked, the jQuery ‘click’ event is raised and the things in my foreach statement are firstable executed. The thing is that client validation errors that comes from Yii validation rules are executed after this happens, so I could not add the errors which my foreach found to the list(see below) with all other errors that are coming after…because this list is still not shown(stays empty and the whole parent div element is hidden) when my errors occur.

So those errors I am adding to another list placed in another div…and that is not good for the user.




<div class="alert alert-block alert-error" id="b1-form_es_" style="">

    <p>Please correct the errors to continue</p>

    <ul>

        <li>err1</li>

        <li>err2</li>

        <li>err3</li>

        ...

    </ul>

</div>



That is why I am searching for that kind of solution that is not away from the framework, but is part of it(fills in the original list of errors).

G.Velchev

Finally noticed the link to your previous thread. And understood what you meant. (Silly me :) ). So sorry about that.

So, create a Validator class that should look something like this :




class SValidator extends CValidator{

   

    /**

     * CValidator is an abstract class, and the method validateAttribute should be implemented

     */

    protected function validateAttribute($object, $attribute) {

        //this would be the value of the input attribute

        $value = $object->$attribute;

        //loop trough the array of values

        foreach($value as $key=>$val){

            //Sanity check here...

            if(strlen($val) < 4)

            {

                //If something goes wrong

                //Add an errror message

                $this->addError($object,$attribute . '['.$key.']',  'izhN ' . $key . ' should be longer than 4 characters!');

            }

            

        }

                

        

    }

    

}



Add rules to your model :




public function rules()

	{

		// NOTE: you should only define rules for those attributes that

		// will receive user inputs.

		return array(

			array('firstName, lastName', 'required'),

			array('firstName, lastName', 'length', 'max'=>30),

//This will perform the validation

//Note: if you don't save SValidator class to a folder that is autoloaded by Yii, you have to do the loading by yourself

                        array('izhN', 'SValidator')

		);

                

	}



And finally your form. I presume that it looks like this. (Take a look @ izhN[1], izhN[2], that is how your JS added fileds should look like…)




<div class="row">

<label for="Validate_izhN">

<input type="text" value="" name="Validate[izhN][0]" id="Validate_izhN_0"/>

<div class="errorMessage" id="Validate_izhN_0_em_" style="display:none"/>

</div>

<div class="row">

<label for="Validate_izhN">

<input type="text" value="" name="Validate[izhN][1]" id="Validate_izhN_1"/>

<div class="errorMessage" id="Validate_izhN_1_em_" style="display:none"/>

</div>

<div class="row">

<label for="Validate_izhN">

<input type="text" value="" name="Validate[izhN][2]" id="Validate_izhN_2"/>

<div class="errorMessage" id="Validate_izhN_2_em_" style="display:none"/>

</div>



And that’s it. I hope this will do the job for you.

Thank you very much for your time. This do the job, but only for the PHP validation. When I am implementing this solution in clientValidateAttribute($object,$attribute) it does not.

My explanation of this case:

Lets assume that I disable the clientValidation of the form, then submit it with values that do not observe my validator rules. Than what happens: the information is sent to the server via post method, and the $_POST array is filled in with:




...,

izhNo = array(

    '0'=>'value0',

    '1'=>'value1',

    '5'=>'',

    '12'=>'value12',

    ...

),

...



…then in the controller $model->validate() is called and my custom validator is looping trough the izhNo array and adding errors for all fields which are wrong(in the previous example this is izhNo[5]).

I think the problem is that when I am validating on client side - no information is POSTed and there is still no array which should I loop trough.

Anyone with ideas?

The thing is, you are adding fields with javascript. When you post them, everything is fine (your controller does the validation). Except the fact, that the newly created fields don’t exist in your model. Neither as model vars or DB fields. When you send the post request all form fields are validated against the specified rules. But when Yii renders the output it can not add the JS added fields, because they don’t exist anywhere.

So this was problem Nr 1. Nr 2 is, that clientValidation (ajaxValidation) doesn’t work on JS added fields. This is because $.fn.yiiactiveform instance is already created, and the fields that users can add dynamically, don’t exist yet.

This probably was a long way to say “You’re stuck”. I looked into your problem yesterday, and the only solution, that I could come up with is implementing custom AJAX calls. I think this is not the solution that you are expecting, but it’s something.




//Let's say that all your JS added fields have the class "extra"

$('input.extras').die('change').live('change', function(){

    elem = $(this)

    //get form action

    var u = $(this).parent().parent().parent('form').attr('action')

    frm = $(this).parent().parent().parent('form')

    //serialize form to array

    f = $(this).parent().parent().parent('form').serializeArray()

    //push $_POST['ajax']['your-form-id'] to the form array

    f.push({'name' : 'ajax', 'value':'login-form'})

    $.post(u, f, function(data){

      //Check if there is a response

       if (data != null && typeof data == 'object'){

                $.each(data, function(k, v){

                    //k here represents the id of a field, that has an error

                    //message

                    ///////

                    //you can loop trough all fields and get only the messages

                    //you need (for the elements that you need to be validated)

                    $('#'+k + '').parent().find('div.errorMessage').text(v[0]).fadeIn()

                    //error message has been shown

                });


            }

            else{

                //if no data has been recieved ... do something

            }

      

    }, 'json')

})



This is what I could think about ajaxValidation.

It would be really difficult to validate the "new" fields after form submission(POST). You might be able to do it with page states or browser local storage…

Hope my answer will help you in some way :)