DavyJonesLocker / client_side_validations

Client Side Validations made easy for Ruby on Rails
MIT License
2.69k stars 405 forks source link

AJAX forms enableClientSideValidations() not actually working #700

Closed jasonivers closed 7 years ago

jasonivers commented 7 years ago

When I try to use client side validations on a form loaded by AJAX, I get an error any time it tries to actually run validations (i.e. on focus, on blur, etc.), though not on the actual enableClientSideValidation() call:

rails.validations.self-b6cd8d2….js?body=1:231 Uncaught TypeError: Cannot read property 'validators' of undefined
    at HTMLInputElement.focusout.ClientSideValidations (rails.validations.self-b6cd8d2….js?body=1:231)
    at HTMLInputElement.dispatch (jquery2.self-25ca496….js?body=1:4738)
    at HTMLInputElement.elemData.handle (jquery2.self-25ca496….js?body=1:4550)
    at Object.trigger (jquery2.self-25ca496….js?body=1:7808)
    at Object.simulate (jquery2.self-25ca496….js?body=1:7867)
    at HTMLDocument.handler (jquery2.self-25ca496….js?body=1:7926)
focusout.ClientSideValidations @ rails.validations.self-b6cd8d2….js?body=1:231
dispatch @ jquery2.self-25ca496….js?body=1:4738
elemData.handle @ jquery2.self-25ca496….js?body=1:4550
trigger @ jquery2.self-25ca496….js?body=1:7808
simulate @ jquery2.self-25ca496….js?body=1:7867
handler @ jquery2.self-25ca496….js?body=1:7926

I have it running the following code after updating the document:

$(".simple_form[data-validate=true]").each(function() {
  $(this).enableClientSideValidations();
});

I looked through the code in the actual gem, and I pulled some of what enableClientSideValidations() does, and it works if I call it directly:

$(".mdl-card-form .simple_form[data-validate=true]").each(function() {
  // $(this).enableClientSideValidations();
  var settings = this.ClientSideValidations.settings;
  if (typeof settings == 'undefined') {
    var settingsData = $(this).data('clientSideValidations');
    this.ClientSideValidations = {
      settings: settingsData,
      addError: function(element, message) {
        return ClientSideValidations.formBuilders[settingsData.html_settings.type].add(element, settingsData.html_settings, message);
      },
      removeError: function(element) {
        return ClientSideValidations.formBuilders[settingsData.html_settings.type].remove(element, settingsData.html_settings);
      }
    };
  }
});

Doing some debugging, it's pretty clear that $(form).data('clientSideValidations') (this in my code, due to simplification) is failing to read the data when executed via enableClientSideValidations(), as it is undefined, while addError and removeError are defined (but broken, because they rely on information from the settings data). I don't have any further time to invest in debugging this right now, since I have a functional workaround, but if you have specific things you'd like me to test, I can.

I'm using:

  1. client_side_validations version 9.2.0
  2. rails 5.0.2
  3. validates_presence_of :name
  4. Rails code:
    <%= simple_form_for @task, remote: true, authenticity_token: true, validate: true, html: { id: 'task-form', validate: true, novalidate: false, data: { validate: true } } do |f| %>
    <div class="form-inputs width-full margin-large">
      <%= f.hidden_field :taskable_id %>
      <%= f.hidden_field :taskable_type %>
      <%= f.hidden_field :reload_details, value: true %>
      <%= f.input :name, placeholder: 'Example Task Name' %>
    </div>
    <div class="mdl-card__actions">
      <a href='#' class='mdl-button mdl-js-button mdl-button--deemphasized' onClick='modal.close();return false;'>Cancel</a>
      <button type="submit" class='mdl-button mdl-js-button mdl-button--primary' id='save'><%= @task.new_record? ? 'Create' : 'Update' %> Task</button>
    </div>
    <% end %>
  5. HTML output:
    <form id="task-form" novalidate="novalidate" data-validate="true" class="simple_form edit_task" data-client-side-validations="{&quot;html_settings&quot;:{&quot;type&quot;:&quot;SimpleForm::FormBuilder&quot;,&quot;error_class&quot;:&quot;error&quot;,&quot;error_tag&quot;:&quot;span&quot;,&quot;wrapper_error_class&quot;:&quot;field_with_errors&quot;,&quot;wrapper_tag&quot;:&quot;div&quot;,&quot;wrapper_class&quot;:&quot;input relative&quot;,&quot;wrapper&quot;:&quot;default&quot;},&quot;number_format&quot;:{&quot;separator&quot;:&quot;.&quot;,&quot;delimiter&quot;:&quot;,&quot;},&quot;validators&quot;:{&quot;task[name]&quot;:{&quot;presence&quot;:[{&quot;message&quot;:&quot;can't be blank&quot;}]}}}" action="/tasks/157" accept-charset="UTF-8" data-remote="true" method="post" _lpchecked="1"><input name="utf8" type="hidden" value="✓"><input type="hidden" name="_method" value="patch"><input type="hidden" name="authenticity_token" value="lRGlK4Bc3yFosgUfjLLl3vVeuJupqwfCUpAVcW7CXXIIipD3SLb/PgLBNtRvKk+L+GsAbLPkL3LGkdXxl9nzxA==">
    <div class="form-inputs width-full margin-large">
      <input type="hidden" value="2" name="task[taskable_id]" id="task_taskable_id">
      <input type="hidden" value="Project" name="task[taskable_type]" id="task_taskable_type">
      <input value="true" type="hidden" name="task[reload_details]" id="task_reload_details">
      <div class="input relative string required task_name"><label class="string required" for="task_name">Name <abbr title="required">*</abbr></label><input class="string required" placeholder="Example Task Name" type="text" value="New new task - Updated" name="task[name]" id="task_name" data-validate="true"></div>
    </div>
    <div class="mdl-card__actions">
      <a href="#" class="mdl-button mdl-js-button mdl-button--deemphasized" onclick="modal.close();return false;" data-upgraded=",MaterialButton">Cancel</a>
      <button type="submit" class="mdl-button mdl-js-button mdl-button--primary" id="save" data-upgraded=",MaterialButton">Update Task</button>
    </div>
    </form>
tagliala commented 7 years ago

Hi

Thanks for the detailed explanation

<%= simple_form_for @task, remote: true, authenticity_token: true, validate: true, html: { id: 'task-form', validate: true, novalidate: false, data: { validate: true } } do |f| %>

why are you adding html: { id: 'task-form', validate: true, novalidate: false, data: { validate: true } }? This should not be needed.

Unfortunately I cannot replicate your issue.

Model

class Task
  include ::ActiveModel::Model

  attr_accessor :taskable_id, :taskable_type, :reload_details, :name

  validates :name, presence: true

  def new_record?
    false
  end
end

View

<%= link_to 'Create Task', :new_task, remote: true, class: 'new-task-j' %>
<div id="new-task-container-j"></div>

Task view, loaded remotely

<%= simple_form_for @task, remote: true, authenticity_token: true, validate: true, html: { id: 'task-form' } do |f| %>
  <div class="form-inputs width-full margin-large">
    <%= f.hidden_field :taskable_id %>
    <%= f.hidden_field :taskable_type %>
    <%= f.hidden_field :reload_details, value: true %>
    <%= f.input :name, placeholder: 'Example Task Name' %>
  </div>
  <div class="mdl-card__actions">
    <a href='#' class='mdl-button mdl-js-button mdl-button--deemphasized' onClick='modal.close();return false;'>Cancel</a>
    <button type="submit" class='mdl-button mdl-js-button mdl-button--primary' id='save'><%= @task.new_record? ? 'Create' : 'Update' %> Task</button>
  </div>
<% end %>

Ajax loading:

$(document).on 'ajax:success', '.new-task-j', (e, data, status, xhr) ->
  $('#new-task-container-j').append $(data).find('#task-form')
  $('#task-form').enableClientSideValidations();

Result: image

tagliala commented 7 years ago

Closing here.

I will reopen if you provide a reproducible test case