window.App = Em.Application.create() App.ApplicationAdapter = DS.ActiveModelAdapter.extend() App.ApplicationRoute = Em.Route.extend model: -> @store.createRecord 'user' actions: updateUser: -> @currentModel.save().then -> alert 'User created' , -> # You have to pass an error handler to the save promise, # even if it does nothing, otherwise an exception will # be thrown and the errors won't be populated for you App.User = DS.Model.extend name: DS.attr 'string' email: DS.attr 'string' $.mockjax url: "/users" type: 'POST' status: 422 responseText: errors: name: ["must be present"] email: ["is not an email", "is blank"] the_force: ["is not strong in this one"] base: ["This person is a generally unsavoury character and is not allowed to sign up"] DS.Model.reopen adapterDidInvalidate: (errors) -> recordErrors = @get 'errors' for own key, errorValue of errors recordErrors.add key, errorValue Ember.Handlebars.helper 'titleize', (text) -> text.titleize() String::titleize = -> @underscore().replace(/_/g, " ").capitalize() App.ErrorMessagesComponent = Em.Component.extend errors: Em.computed.filter 'for.errors.content', (error) -> error.attribute != 'base' baseErrors: Em.computed.filter 'for.errors.content', (error) -> error.attribute is 'base' # For a detailed rundown of the object form components below, see: # http://alexspeller.com/simple-forms-with-ember/ App.ObjectFormComponent = Em.Component.extend buttonLabel: "Submit" actions: submit: -> @sendAction() App.FormFieldComponent = Em.Component.extend type: Em.computed 'for', -> if @get('for').match(/password/) "password" else "text" label: Em.computed 'for', -> @get('for').underscore().replace(/_/g, " ").capitalize() fieldId: Em.computed 'object', 'for', -> "#{Em.guidFor @get('object')}-input-#{@get('for')}" object: Em.computed.alias 'parentView.for' hasError: (-> @get('object.errors')?.has @get('for') ).property 'object.errors.[]' errors: (-> return Em.A() unless @get('object.errors') @get('object.errors').errorsFor(@get('for')).mapBy('message').join(', ') ).property 'object.errors.[]' setupBindings: (-> @binding?.disconnect @ # Disconnect old binding if present # Create a binding between the value property of the component, # and the correct field name on the model object. @binding = Em.Binding.from("object.#{@get 'for'}").to('value') # Activate the binding @binding.connect @ ).on('init').observes 'for', 'object' # Ensure the bindings are cleaned up when the component is removed tearDownBindings: (-> @binding?.disconnect @ ).on 'willDestroyElement'
<script type="text/x-handlebars"> <div class="container"> <h2>Create User</h2> {{error-messages for=this}} {{#object-form for=this action="updateUser" buttonLabel="Save"}} {{form-field for="name"}} {{form-field for="email"}} {{/object-form}} </div> </script> <script type="text/x-handlebars" data-template-name="components/error-messages"> {{#if errors.length}} <div class="alert alert-danger"> <h4>There was a problem</h4> <ul> {{#each baseErrors}} <li> {{message}} </li> {{/each}} {{#each errors}} <li> {{titleize attribute}} {{message}} </li> {{/each}} </ul> </div> {{/if}} </script> <script type="text/x-handlebars" data-template-name="components/form-field"> <div class="form-group" {{bind-attr class="hasError:has-error"}}> <label {{bind-attr for=label}}>{{label}}</label> {{#if template}} {{yield}} {{else}} {{input type=type value=value id=label class="form-control"}} {{/if}} {{#if errors.length}} <span class="help-block"> {{errors}} </span> {{/if}} </div> </script> <script type="text/x-handlebars" data-template-name="components/object-form"> <form {{action "submit" on="submit"}}> {{yield}} <div class="form-group"> <button type='submit' class='btn btn-block btn-primary'>{{buttonLabel}}</button> </div> </form> </script>