Asynchronous Form Field Validation with Ember
I've been playing around with Ember.js recently. Here is my approach at implementing synchronous (username length) and asynchronous (username availability) validation, encapsulated in an Ember.js View. The code is a bit long, but it is more declarative and thus predictable than its jQuery spaghetti counterpart. Here's the gist and here's the fiddle.
window.App = Ember.Application.create()
App.Focusable = Ember.Mixin.create
# Don't let events spoil your Kool-Aid!
# Let's get these out of the way by turning them
# into a magical property:
focused: false
focusIn: (event) -> @set 'focused', true
focusOut: (event) -> @set 'focused', false
App.AsyncValidation = Ember.Mixin.create
# Set up an observer that will watch when the field is
# in and out of focus.
valid: (->
if @get 'focused'
@set 'code', ''
@set 'message', ""
else
value = @get 'value'
# Call the specific validation function, passing the
# value to validate and a callback to which the function
# must pass the result of the validation.
@validate value, (code, message) =>
# If everything's okay, we can show this result
# Make sure that the value hasn't changed and that
# the field isn't being edited
if value == @get('value') and not @get('focused')
@set 'code', code
@set 'message', message
).observes 'focused'
# Dummy validation function
validate: (value, next) -> next '', ""
App.TextField = Ember.View.extend App.Focusable, App.AsyncValidation,
classNames: ['control-group']
type: 'text'
# This is the value of the field:
value: ""
# And its placeholder:
placeholder: ""
# Validation status. One of '', 'error' or 'success'
code: ''
classNameBindings: ['code']
# Error or success message
message: ""
template: Ember.Handlebars.compile """
<div class="controls">
{{view Ember.TextField
placeholderBinding = "view.placeholder"
valueBinding = "view.value"
typeBinding = "view.type"
}}
<span class="help-inline">{{view.message}}</span>
</div>
"""
App.EmailField = App.TextField.extend
placeholder: "Email"
validate: (value, status) ->
if value.length == 0
status '', ""
# Might catch the occasional typo
else if value.indexOf('@') == -1 or value.indexOf('.') == -1
status 'error', "Please enter a valid email"
else
# Insert AJAX call here:
setTimeout((->
status 'success', "Email address available"
), 1000)