By now you know the drill…
# app/assets/javascripts/router.js.coffee @resource 'leads', path: '/', -> @route 'new'
// app/assets/javascripts/router.js this.resource('leads', { path: '/' }, function() { this.route('new'); });
New lead will be a route
and not a resource
because it does not need to load an existing model in our system.
We’re going to create a fields
property that will be a plain javascript object. We’ll use this to hold the attributes for the new lead until we’re ready to actually create it.
We set fields
to an empty object in the route so that it’s reset every time we visit the new lead route.
# app/assets/javascripts/routes/leads_new.js.coffee App.LeadsNewRoute = Ember.Route.extend setupController: (controller) -> controller.set 'fields', {}
// app/assets/javascripts/routes/leads_new.js App.LeadsNewRoute = Ember.Route.extend({ setupController: function(controller) { controller.set('fields', {}) } });
And here’s our new lead template. Note that it goes in templates/leads/
because it’s a route
nested under a resource named leads
.
// app/assets/javascripts/templates/leads/new.js.emblem article#lead h1 New Lead form fieldset dl dt: label First Name: dd: view Ember.TextField value=fields.firstName dl dt: label Last Name: dd: view Ember.TextField value=fields.lastName dl dt: label Email: dd: view Ember.TextField value=fields.email dl dt: label Phone: dd: view Ember.TextField value=fields.phone fieldset.actions input type='submit' value='Create Lead' click="createLead"
As you can see, I’ve bound all of the inputs to the fields
property.
The submit has a click action called createLead
, which we’ll deal with now.
Create a controller to handle the createLead
action:
# app/assets/javascripts/controllers/leads_new.js.coffee App.LeadsNewController = Ember.Controller.extend actions: createLead: -> lead = @store.createRecord 'lead', @get('fields') lead.save().then => @transitionToRoute 'lead', lead
// app/assets/javascripts/controllers/leads_new.js App.LeadsNewController = Ember.Controller.extend({ actions: { createLead: function() { var self = this; var lead = this.store.createRecord('lead', this.get('fields')); lead.save().then(function() { self.transitionToRoute('lead', lead); }); } } });
This action first calls createRecord
, which we pass the string name of the model and an object with the attributes we want to give the new model. Since we bound fields
to all the attributes we can just use it as is. If you logged fields
you would see something like { firstName: 'Sam', lastName: 'Smith', email: 'sam@example.com', phone: '123-456-7890' }
.
Once the record is created we save it, then transition to the show lead route.
There’s another common pattern to create new records that I didn’t use. I could have created a new record in the route and bound the inputs to the record. The reason I didn’t do this is because I didn’t want the record to show up in our list of leads on the left until the user pressed “Create Lead”. By keeping the attributes in fields
and waiting until createLead
is called to create a record, the record won’t appear in the list until the user has chosen to create it.
Add a link to our new lead route in the leads
template:
// app/assets/javascripts/templates/leads.js.emblem article#leads h1 | Leads link-to 'leads.new' | New Lead
Now refresh and try it. Everything should work, but it’s not perfect. You can create leads where every attribute is null. We should perform a validation here to prevent this.
Validation libraries for Ember exist and if you want to use one I recommend Dockyard’s Ember Validations library. However, given Ember’s robust object system and the large amount of control it gives you over the client, you don’t necessarily need a library.
I’m going to do these validations by hand. There are any number of different ways to do validations, this is just one.
First let’s define a valid method on the Lead class. Let’s say we just care that a first and last name are present:
# app/assets/javascripts/models/lead.js.coffee App.Lead.reopenClass valid: (fields) -> fields.firstName and fields.lastName
// app/assets/javascripts/models/lead.js App.Lead.reopenClass({ valid: function(fields) { return fields.firstName && fields.lastName } });
We pass this method an object with the attributes we want to assign to a lead, and it tells us if this collection of attributes is valid or not.
Now we need to modify our createLead
method in the new lead controller:
# app/assets/javascripts/controllers/leads_new.js.coffee createLead: -> fields = @get('fields') if App.Lead.valid(fields) lead = @store.createRecord 'lead', fields lead.save().then (lead) => @transitionToRoute 'lead', lead else @set 'showError', true
// app/assets/javascripts/controllers/leads_new.js createLead: function() { var self = this; var fields = this.get('fields') if (App.Lead.valid(fields)) { var lead = this.store.createRecord('lead', fields) lead.save().then(function(lead) { self.transitionToRoute('lead', lead) }); } else { this.set('showError', true) } }
We check to see if these fields are valid. If they are, create the record. If they aren’t, set the showError
property to true. Now we can use the showError
property to display a message to the user:
// app/assets/javascripts/templates/leads/new.js.emblem article#lead h1 New Lead if showError .error Leads must have a first and last name.
One last thing: controller instances remain active, so if you created this error, went to another route, then came back, the showError
property would still be true. We don’t want that, so we need to default it to false in the route on setupController
:
# app/assets/javascripts/routes/leads_new.js.coffee setupController: (controller) -> # etc... controller.set 'showError', false
// app/assets/javascripts/routes/leads_new.js setupController: function(controller) { // etc... controller.set('showError', false) }
Now if you create the error, leave, then come back, the form should be fully reset.
That’s it for adding leads. This chapter feels a bit dry, but creating new records is probably something you’ll do a lot so it’s good to know.
The next chapter is more exciting: we’re going to instantly search leads.