judgegem / judge

Client-side form validation for Rails
MIT License
256 stars 41 forks source link

Judge not validating email field #37

Closed jeffreyguenther closed 10 years ago

jeffreyguenther commented 10 years ago

I can't seem to get judge to validate my email field. If I turn off validation the field the form validates and can be submit. I have stopped using devise's :validatable module because of some of the custom logic I'm doing around validation. Judge seems to be responding to the uniques query fine. The user doesn't exist.

Started GET "/judge?klass=user&attribute=email&value=bob%40example.com&kind=uniqueness" for 127.0.0.1 at 2014-10-09 20:54:49 -0700
Processing by Judge::ValidationsController#build as JSON
  Parameters: {"klass"=>"user", "attribute"=>"email", "value"=>"bob@example.com", "kind"=>"uniqueness"}
  User Exists (12.1ms)  SELECT  1 AS one FROM "users"  WHERE "users"."email" = 'bob@example.com' LIMIT 1
Completed 200 OK in 17ms (Views: 0.1ms | ActiveRecord: 12.1ms)

Model:

class User < ActiveRecord::Base
  validates :email, presence: true
  validates :email, uniqueness: true
  validates :email, format: { with: /.+@.+/, message: "must contain \"@\""}

  with_options if: :should_validate? do |admin|
    admin.validates :password, presence: true 
    admin.validates :password, confirmation: true
    admin.validates :password, length: { in: 8..128 }
    admin.validates :first_name, presence: true
    admin.validates :last_name, presence: true  
  end

  def should_validate?
    admin? || persisted?
  end
end
controls = {}
    $(".participate-form__form-section").each (i, e) ->
        # console.log "building collection of controls"
        # get the section's inputs
        id = "fs-#{i + 1}"
        # console.log id
        inputs = document.querySelectorAll(".participate-form__form-section##{id} input[type=text], .participate-form__form-section##{id} input[type=email]")
        controls["#{id}"] = inputs

# On click, show the form section referenced in the href
    $(".next-btn").on "click", (e) ->
        e.preventDefault()
        isValid = false

        #  Add logic to manage the validations per sheet
        id = $(this).parent().attr("id")
        isValid = validate_section(controls[id])
        console.log "section is valid: #{isValid}"

        if isValid
            #do some stuff to prompt the user

validate_section = (controls) ->
    console.log controls
    results = []
    for control in controls
        results.push(validate_field(control))
    console.log results
    !(results.indexOf(false) >= 0)

validate_field = (control)->
    # console.log id
    isValid  = false
    judge.validate(control,
        valid: (element) ->
            # console.log "valid"
            isValid = true
        invalid: (element, messages) ->
            id = element.getAttribute("id")
            # console.log "invalid"
            console.log messages
            $("##{id}").popover({ 
                # trigger: "manual"
                # html : true,
                placement: "left",
                content: messages
                }).popover('show')
            $("##{id}").toggleClass("invalid-field", true)
    )
    console.log "#{control.getAttribute("id")} is #{isValid}"
    isValid

The generated HTML is:

<input class="participate-form__input-field" data-validate="[{&quot;kind&quot;:&quot;presence&quot;,&quot;options&quot;:{},&quot;messages&quot;:{&quot;blank&quot;:&quot;can't be blank&quot;}},
{&quot;kind&quot;:&quot;uniqueness&quot;,&quot;options&quot;:{},&quot;messages&quot;:{}},
{&quot;kind&quot;:&quot;format&quot;,&quot;options&quot;:{&quot;with&quot;:&quot;(?-mix:.+@.+)&quot;,&quot;message&quot;:&quot;must contain \&quot;@\&quot;&quot;},&quot;messages&quot;:{&quot;invalid&quot;:&quot;must contain \&quot;@\&quot;&quot;,&quot;blank&quot;:&quot;can't be blank&quot;}}]" 
id="user_email" 
name="user[email]" 
placeholder="jsmith@abc.com" 
type="email" value="bob@example.com">

Any idea what the issue might be?

jeffreyguenther commented 10 years ago

Accidentally closed the issue. I'm still having the issue.

jeffreyguenther commented 10 years ago

Starting to get a little closer to the cause. It appears the callbacks are never being called for uniqueness tests

judge.validate(control,
        valid: (element) ->
            console.log "valid"
        invalid: (element, messages) ->
            console.log "invalid"
    )
jeffreyguenther commented 10 years ago

And the issue was.... judge.validate is an asynchronous method. The code above doesn't take that into account!

grstk commented 9 years ago

@jrguenther how do you take this into account?

jeffreyguenther commented 9 years ago

When judge validates for uniqueness, it makes an AJAX call to determine if the email already exists.

My mistake was to think of the validate as a synchronous method and my code would run in sequence. You'll notice I have a loop above to validate a section of a form. Each section contains several controls. Because validate is called once for every control in the loop, I was assuming the validate calls would return in the same sequence and I would be able to get the result in the loop. Instead, controls were returning invalid when in fact they were valid because they were returning valid after my code had moved on.

To solve the problem, I had to use a jquery deferred to provide a callback.

(".next-btn").on "click", (e) ->
    e.preventDefault()
    isValid = false
        #controls is an object with an array of controls per id. Each id represents a section of the form.
    validate_section(controls[id]).done (result) -> 
        console.log result
        $(".participate-form__content").animate({
             scrollTop: target.position().top * offset
        }, 1000);

validate_section = (controls) ->
    console.log controls
    count  = controls.length
    result = true

    deferred = $.Deferred()

    for control in controls
        validate_field(control).done((isValid) ->

            console.log "Control is validated"
            count--
            result = result && isValid

            console.log "#Result: #{result}"
            console.log "Controls left: #{count}"
            if(count == 0 && result)
                deferred.resolve(result)
                console.log "Section is good"
        )

    deferred.promise()

validate_field = (control)->
    deferred = $.Deferred()

    #  add spinny part
    if control.getAttribute("type") is "email"
        $(".has-feedback").append("<i class=\"form-control-feedback fa fa-circle-o-notch fa-spin\"></i>")

    judge.validate(control,
        valid: (element) ->
            console.log "called valid()"
            id = element.getAttribute("id")
            $("##{id}").toggleClass("invalid-field", false)
            deferred.resolve(true)
        invalid: (element, messages) ->
            id = element.getAttribute("id")

            console.log "called invalid()"
            console.log messages

            # Create html error messages
            html_message = messages.join("<br>")

            # Create a new pop over
            $("##{id}").popover("destroy")
            $("##{id}").popover({ 
                # trigger: "manual"
                html : true,
                placement: "left",
                content: html_message
                }).popover('show')