Skip to content

Instantly share code, notes, and snippets.

@DVG
Created July 12, 2013 12:30
Show Gist options
  • Save DVG/5984104 to your computer and use it in GitHub Desktop.
Save DVG/5984104 to your computer and use it in GitHub Desktop.

It's important to make sure your server-side app is secure when using a client-side application like Ember, since any user can alter your javascript at runtime to make it do whatever you want. Therefore you want to make sure you still use server-side validations to make sure that your persistant records are valid.

However, Ember doesn't include any built-in way of displaying these errors on a form like you might be used to coming from a Rails world. This is how I do it.

Let's take this login form as an example (using twitter bootstrap css):

<form class="form-horizontal" {{action sendRegistration on="submit"}}>
  <div class="control-group">
    <label class="control-label" for="email">Email</label>
    <div class="controls">
      {{input type="text" id="email" placeholder="Email" value=email}}
    </div>
  </div>
  <div class="control-group">
    <label class="control-label" for="password">Password</label>
    <div class="controls">
      {{input type="password" id="password" placeholder="Password" value=password}}
    </div>
  </div>
  <div class="control-group">
    <label class="control-label" for="password_confirmation">Password Confirmation</label>
    <div class="controls">
      {{input type="password" id="password_confirmation" placeholder="Password Confirmation" value=passwordConfirmation}}
    </div>
  </div>
  <div class="control-group">
    <div class="controls">
      <button type="submit" class="btn btn-inverse">Sign Up</button>
    </div>
  </div>
</form>

And let's assume we have the following JSON that comes back from the server when a validation error occurs:

{
  errors:
  {
    email: ["can't be blank"],
    password: ["doesn't match confirmation"]
  }
}

And we need properties for the errors arrays:

App.RegistrationController = Ember.Controller.extend
  email: null
  password: null
  passwordConfirmation: null
  emailErrors: []
  passwordErrors: []

We need to do three basic things to display the errors:

  1. Bind the control-group divs to a computed property to add the error css class to them when errors are present
  2. Add some markup and bind content for displaying the actual errors
  3. When the post request fails, we need to populate the Errors arrays on the controller

For one, we just need to use a bindAttr binding:

<div {{bindAttr class=":control-group hasEmailErrors:error"}}>
  <label class="control-label" for="email">Email</label>
  <div class="controls">
    {{input type="text" id="email" placeholder="Email" value=email}}
  </div>
</div>

:control-group in this case is a static class that will always be present. hasEmailErrors:error binds the error class to the truty or falsiness of the hasEmailErrors property.

We can use a Computed Property Macro for the computer property in this case. So in our controller we just add:

hasEmailErrors: Ember.computed.notEmpty('emailErrors')

So now, whenever there is something in our emailErrors array, our form fields will be automatically styled as having an error.

Now for #2. Add this after the email input field:

<span class="help-inline">{{emailErrorOutput}}</span>

`emailErrorOutput will be another computed property, albeit the classical kind

emailErrorOutput: (->
  output = "Email "
  for error in @emailErrors
    output = "#{output} #{error}"
).property('emailErrors')

Now we just need to set the errors property after our request fails:

sendRegistration: () ->
  self = @
  self.set('emailErrors', null)
  self.set('passwordErrors', null)
  $.post('/users', 
    user:
      email: @email,
      password: @password,
      password_confirmation: @passwordConfirmation)
  .done (response) ->
    App.Auth.createSession(response)
  .fail (response) ->
    errors = response.responseJSON.errors
    if errors.email
      self.set('emailErrors', errors.email)
    if errors.password
      self.set('passwordErrors', errors.password)

This function will set the errors arrays as necessary, and the bindings will take care of the rest.

The complete template and controller:

App.RegistrationController = Ember.Controller.extend
  email: null
  password: null
  passwordConfirmation: null
  emailErrors: []
  passwordErrors: []

  hasEmailErrors: Ember.computed.notEmpty('emailErrors')
  hasPasswordErrors: Ember.computed.notEmpty('passwordErrors')

  emailErrorOutput: (->
    output = "Email "
    for error in @emailErrors
      output = "#{output} #{error}"
  ).property('emailErrors')

  passwordErrorOutput: (->
    output = "Password "
    for error in @passwordErrors
      output = "#{output} #{error}"
  ).property('passwordErrors')

  sendRegistration: () ->
    self = @
    self.set('emailErrors', null)
    self.set('passwordErrors', null)
    $.post('/users', 
      user:
        email: @email,
        password: @password,
        password_confirmation: @passwordConfirmation)
    .done (response) ->
      App.Auth.createSession(response)
    .fail (response) ->
      errors = response.responseJSON.errors
      if errors.email
        self.set('emailErrors', errors.email)
      if errors.password
        self.set('passwordErrors', errors.password)

<div class="row">
  <div class="span2"></div>
  <div class="span8">
    <div class="page-header">
      <h1>Sign Up</h1>
    </div>
    <form class="form-horizontal" {{action sendRegistration on="submit"}}>
      <div {{bindAttr class=":control-group hasEmailErrors:error"}}>
        <label class="control-label" for="email">Email</label>
        <div class="controls">
          {{input type="text" id="email" placeholder="Email" value=email}}
          <span class="help-inline">{{emailErrorOutput}}</span>
        </div>
      </div>
      <div {{bindAttr class=":control-group hasPasswordErrors:error"}}>
        <label class="control-label" for="password">Password</label>
        <div class="controls">
          {{input type="password" id="password" placeholder="Password" value=password}}
          <span class="help-inline">{{passwordErrors}}</span>
        </div>
      </div>
      <div class="control-group">
        <label class="control-label" for="password_confirmation">Password Confirmation</label>
        <div class="controls">
          {{input type="password" id="password_confirmation" placeholder="Password Confirmation" value=passwordConfirmation}}
          <span class="help-inline">{{passwordConfirmationErrors}}</span>
        </div>
      </div>
      <div class="control-group">
        <div class="controls">
          <button type="submit" class="btn btn-inverse">Sign Up</button>
        </div>
      </div>
    </form>
  </div>
  <div class="span2"></div>
</div>

Thanks! -DVG

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment