formapro / JsFormValidatorBundle

The Javascript validation for Symfony 2, 3 and 4 forms
MIT License
128 stars 56 forks source link

Uncaught TypeError: Cannot read property 'transformers' of undefined fp_js_validator.js:536 #51

Open latysh opened 10 years ago

latysh commented 10 years ago

Symfony 2.4.8

$('form')
    .find('input[type=text], textarea')
    .blur(function(){
        // Run validation for this field
        $(this).jsFormValidator('validate')
    })
    .focus(function() {
        // Reset markers when focus on a field
        $(this).removeClass('error');
        $(this).removeClass('ready');
    })
    .jsFormValidator({
        'showErrors': function(errors) {
            if (errors.length) {
                $(this).removeClass('ready');
                $(this).addClass('error');
            } else {
                $(this).removeClass('error');
                $(this).addClass('ready');
            }
        }
    });

Error appears when I try to validate form on a custom event based on here https://github.com/formapro/JsFormValidatorBundle/blob/master/Resources/doc/3_12.md

Form elements generated manually, tried to generate form elements by {{ form(form) }} still the same error.

Everything works fine when I submit the form. But I want to validate form field when the value is changed to show whether it is correct value or not.

What can be the issue?

CSchulz commented 10 years ago

I had a similar one and found the issue with debugging the javascript in my "constraints".

Can you add your form?

latysh commented 10 years ago

Hi,

My manual form looks like this

<form name="pepperstone_applicationbundle_user" method="post" action="/app_dev.php/registration/step1/check"
      class="form-horizontal formView step1form" novalidate="novalidate">
    <div class="form-group">
        <!-- Text input-->
        <div class="col-md-12 mgnBtm20">
            <label class="control-label required" for="pepperstone_applicationbundle_user_email">username</label>
            <input type="email" id="pepperstone_applicationbundle_user_email"
                   name="pepperstone_applicationbundle_user[email]" required="required" class="form-control input-lg"
                   value="altynbek.usenov@gmail.com"/>
            <span class="help-block"> </span>
        </div>
        <!-- Text input-->
        <div class="col-md-12 mgnBtm20">
            <label class="control-label required" for="pepperstone_applicationbundle_user_change_password_first">enter.new.password</label>
            <input type="password" id="pepperstone_applicationbundle_user_change_password_first"
                   name="pepperstone_applicationbundle_user[change_password][first]" required="required"
                   class="form-control input-lg"/>
            <span class="help-block"> </span>
        </div>
        <div class="col-md-12 mgnBtm20">
            <label class="control-label required" for="pepperstone_applicationbundle_user_change_password_second">confirm.new.password</label>
            <input type="password" id="pepperstone_applicationbundle_user_change_password_second"
                   name="pepperstone_applicationbundle_user[change_password][second]" required="required"
                   class="form-control input-lg"/>
            <span class="help-block"> </span>
        </div>
        <!-- Text input-->
        <div class="col-md-12 mgnBtm20">
            <label class="control-label required" for="pepperstone_applicationbundle_user_first_name">First name</label>
            <input type="text" id="pepperstone_applicationbundle_user_first_name"
                   name="pepperstone_applicationbundle_user[first_name]" required="required" maxlength="255"
                   class="form-control input-lg" value="Altynbek"/>
            <span class="help-block"> </span>
        </div>
        <!-- Text input-->
        <div class="col-md-12 mgnBtm20">
            <label class="control-label required" for="pepperstone_applicationbundle_user_last_name">Last name</label>
            <input type="text" id="pepperstone_applicationbundle_user_last_name"
                   name="pepperstone_applicationbundle_user[last_name]" required="required" maxlength="255"
                   class="form-control input-lg" value="Usenov"/>
            <span class="help-block"> </span>
        </div>
        <!-- Text input-->
        <div class="col-md-12 mgnBtm20">
            <label class="control-label required" for="pepperstone_applicationbundle_user_phone">Phone</label>
            <input type="text" id="pepperstone_applicationbundle_user_phone"
                   name="pepperstone_applicationbundle_user[phone]" required="required" maxlength="255"
                   class="form-control input-lg" value="0404205605"/>
            <span class="help-block"> </span>
        </div>
        <!-- Text input-->
        <div class="col-md-12 mgnBtm20">
            <label class="control-label required" for="pepperstone_applicationbundle_user_address">Address</label>
            <input type="text" id="pepperstone_applicationbundle_user_address"
                   name="pepperstone_applicationbundle_user[address]" required="required" maxlength="255"
                   class="form-control input-lg" value="1/522 South road"/>
            <span class="help-block"> </span>
        </div>
        <!-- Text input-->
        <div class="col-md-12 mgnBtm20">
            <label class="control-label required" for="pepperstone_applicationbundle_user_city">City</label>
            <input type="text" id="pepperstone_applicationbundle_user_city"
                   name="pepperstone_applicationbundle_user[city]" required="required" maxlength="255"
                   class="form-control input-lg" value="Moorabbin"/>
            <span class="help-block"> </span>
        </div>
        <!-- Text input-->
        <div class="col-md-12 mgnBtm20">
            <label class="control-label required" for="pepperstone_applicationbundle_user_state">State</label>
            <input type="text" id="pepperstone_applicationbundle_user_state"
                   name="pepperstone_applicationbundle_user[state]" required="required" maxlength="255"
                   class="form-control input-lg" value="VIC"/>
            <span class="help-block"> </span>
        </div>
        <!-- Text input short-->
        <div class="col-md-3 mgnBtm20">
            <label class="control-label required" for="pepperstone_applicationbundle_user_zip_code">Zip code</label>
            <input type="text" id="pepperstone_applicationbundle_user_zip_code"
                   name="pepperstone_applicationbundle_user[zip_code]" required="required" maxlength="255"
                   class="form-control input-lg " value="3189"/>
            <span class="help-block"> </span>
        </div>
    </div>
    <!-- buttons-->
    <div class="form-group btnWrapper">
        <div class="col-md-6 col-xs-12 pull-right text-right">
            <button type="submit" id="pepperstone_applicationbundle_user_submit"
                    name="pepperstone_applicationbundle_user[submit]" class="btn btn-lg btn-success flexBtn">
                Update.and.proceed
            </button>
        </div>
    </div>
    </div><!--/contentvieport-->
    <ul class="record_actions">
    </ul>
    <input type="hidden" id="pepperstone_applicationbundle_user__token"
           name="pepperstone_applicationbundle_user[_token]" value="8ZAF1uxjybgTL5kQLIJPb7dFDMQD5ghgII6rtJtS_L0"/>
</form>
latysh commented 10 years ago

And this is what generated from {{ form(form, { 'attr': {'class': 'form-horizontal formView step1form', novalidate: 'novalidate'} }) }}

<form name="pepperstone_applicationbundle_user" method="post" action="/app_dev.php/registration/step1/check"
      class="form-horizontal formView step1form" novalidate="novalidate">
    <div id="pepperstone_applicationbundle_user" class="form-horizontal formView step1form" novalidate="novalidate">
        <div><label for="pepperstone_applicationbundle_user_email" class="required">username</label><input type="email"
                                                                                                           id="pepperstone_applicationbundle_user_email"
                                                                                                           name="pepperstone_applicationbundle_user[email]"
                                                                                                           required="required"
                                                                                                           value="altynbek.usenov@gmail.com"/>
        </div>
        <div><label for="pepperstone_applicationbundle_user_change_password_first"
                    class="required">enter.new.password</label> <input type="password"
                                                                       id="pepperstone_applicationbundle_user_change_password_first"
                                                                       name="pepperstone_applicationbundle_user[change_password][first]"
                                                                       required="required"/></div>
        <div><label for="pepperstone_applicationbundle_user_change_password_second" class="required">confirm.new.password</label>
            <input type="password" id="pepperstone_applicationbundle_user_change_password_second"
                   name="pepperstone_applicationbundle_user[change_password][second]" required="required"/></div>
        <div><label for="pepperstone_applicationbundle_user_first_name" class="required">First name</label><input
                type="text" id="pepperstone_applicationbundle_user_first_name"
                name="pepperstone_applicationbundle_user[first_name]" required="required" maxlength="255"
                value="Altynbek"/></div>
        <div><label for="pepperstone_applicationbundle_user_last_name" class="required">Last name</label><input
                type="text" id="pepperstone_applicationbundle_user_last_name"
                name="pepperstone_applicationbundle_user[last_name]" required="required" maxlength="255"
                value="Usenov"/></div>
        <div><label for="pepperstone_applicationbundle_user_phone" class="required">Phone</label><input type="text"
                                                                                                        id="pepperstone_applicationbundle_user_phone"
                                                                                                        name="pepperstone_applicationbundle_user[phone]"
                                                                                                        required="required"
                                                                                                        maxlength="255"
                                                                                                        value="0404205605"/>
        </div>
        <div><label for="pepperstone_applicationbundle_user_address" class="required">Address</label><input type="text"
                                                                                                            id="pepperstone_applicationbundle_user_address"
                                                                                                            name="pepperstone_applicationbundle_user[address]"
                                                                                                            required="required"
                                                                                                            maxlength="255"
                                                                                                            value="1/522 South road"/>
        </div>
        <div><label for="pepperstone_applicationbundle_user_city" class="required">City</label><input type="text"
                                                                                                      id="pepperstone_applicationbundle_user_city"
                                                                                                      name="pepperstone_applicationbundle_user[city]"
                                                                                                      required="required"
                                                                                                      maxlength="255"
                                                                                                      value="Moorabbin"/>
        </div>
        <div><label for="pepperstone_applicationbundle_user_state" class="required">State</label><input type="text"
                                                                                                        id="pepperstone_applicationbundle_user_state"
                                                                                                        name="pepperstone_applicationbundle_user[state]"
                                                                                                        required="required"
                                                                                                        maxlength="255"
                                                                                                        value="VIC"/>
        </div>
        <div><label for="pepperstone_applicationbundle_user_zip_code" class="required">Zip code</label><input
                type="text" id="pepperstone_applicationbundle_user_zip_code"
                name="pepperstone_applicationbundle_user[zip_code]" required="required" maxlength="255" value="3189"/>
        </div>
        <div>
            <button type="submit" id="pepperstone_applicationbundle_user_submit"
                    name="pepperstone_applicationbundle_user[submit]">Submit
            </button>
        </div>
        <input type="hidden" id="pepperstone_applicationbundle_user__token"
               name="pepperstone_applicationbundle_user[_token]" value="8ZAF1uxjybgTL5kQLIJPb7dFDMQD5ghgII6rtJtS_L0"/>
    </div>
</form>
CSchulz commented 10 years ago

Am I right we are talking here about an user registration with UniqueEntity?

Perhaps using an existing UserBundle like FOSUserBundle?

latysh commented 10 years ago

Yes you are right. We use FOSUserBundle. Email is unique field... Is the problem related to it?

CSchulz commented 10 years ago

Yes it is, I will write later a complete answer, what issue you are encountering and how you can get around it.

CSchulz commented 10 years ago

Okay, so lets go ...

The point why the validation doesn't work is FOSUserBundle stores username and email twice in DB (username and username_canonical; same for email and email_canonical). The UniqueEntity constraints validates against the *_canonical field.

If you have a look on the generated form you will see it contains email, that is the reason why the JsFormValidator can't match them.

Why does it work on the normal way? It uses the validator.initializer in Initializer class:

    public function initialize($object)
    {
        if ($object instanceof UserInterface) {
            $this->userManager->updateCanonicalFields($object);
        }
    }

So the fields are filled before the validation is done (on the normal way).

How I got this working for me: Yes it is like a kind of a hack, but it works for me.

You need to extend the JsFormValidatorFactory and overwrite the method parseConstraints:

protected function parseConstraints(array $constraints)
{
    $result = array();
    foreach ($constraints as $item) {
        // Translate messages if need and add to result
        foreach ($item as $propName => $propValue) {
            if (false !== strpos(strtolower($propName), 'message')) {
                $item->{$propName} = $this->translateMessage($propValue);
            }
        }

        if ($item instanceof \Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity) {
            if (! is_array($item->fields) && strpos($item->fields, 'Canonical') !== false) {
                $item->fields = $item->errorPath;
            }
            $item = new UniqueEntity($item, $this->currentElement->getConfig()->getDataClass());
        }

        $result[get_class($item)][] = $item;
    }

    return $result;
}

You see I overwrite the field name if it contains Canonical with the errorPath (it is the field contained by the form).

In general the form is working, now. But you should also extend the AjaxController to revert the change and validate against the right fields.

Overwrite the method checkUniqueEntityAction:

public function checkUniqueEntityAction(Request $request)
{
    $data = $request->request->all();
    if ($data['entityName'] === '<User entity name with namespace>' && isset($data['errorPath']) && ($data['errorPath'] === 'email' || $data['errorPath'] === 'username')) {
        $data['data'][$data['errorPath'] . 'Canonical'] = $data['data'][$data['errorPath']];
        unset($data['data'][$data['errorPath']]);
        $request->request->replace($data);
    }

    return parent::checkUniqueEntityAction($request);
}

Overwrite the Factory parameter:

parameters:
    fp_js_form_validator.factory.class: <your namespace>\JsFormValidatorFactory

Overwrite the routing:

fp_js_form_validator:
    routing:
        check_unique_entity: <your route>
latysh commented 10 years ago

Thanks mate, Now it works, exactly as I wanted it to work.

The only issue left with repeated form type. It is not caught by showErrors ((

kick-the-bucket commented 9 years ago

I also got this same error when the input for the unique constraint wasn't in the form because of the buggy javascript. I opened a PR #78 to fix it

66Ton99 commented 9 years ago

@CSchulz or @latysh can you recheck it now?

CSchulz commented 9 years ago

I will do it the next days.

I am surprised that it seems to be such a minor change, nevertheless good work @kick-the-bucket .

kick-the-bucket commented 9 years ago

One more PR #79 for the same line - apparantly the name wasn't the right one to search for...