I've asked a question previously in which I wanted to bind a collection residing in the controller to the list scenario view, however, I've added details and edit templates and views to my structure producing a couple of extra sub-routes:
root.contacts.details -> /contacts/:contact_id
root.contacts.edit -> /contacts/:contact_id/edit
In my details
scenarios I first started calling the connectOutlets
as follows
[...]
connectOutlets: function (router, contact) {
router.get('contactController').set('contact', contact);
router.get('applicationController').connectOutlet('contacts');
},[...]
This would change the route in the browser navigation bar, but it would load the same view, then I changed the .connectOutlet
to contact instead of contacts to the following
[...]
connectOutlets: function (router, contact) {
router.get('contactController').set('contact', contact);
router.get('applicationController').connectOutlet('contact');
},[...]
Because of this, I had to create a new controller as Ember couldn't find a controller named contactController
, so I ended up with a contactController
and a contactsController
and I think I'm breaking the MVC pattern doing this, as well as creating an extra class to maintain, possible problems with syncronization (when editing a contact I'd have to manually sync with the collection in the contactsController
). Also when I navigate to /#/contacts/2/edit
it loads the details view since I'm using the same name in .connectOutlet('contact')
. So what I'm doing can't be right. I don't want to create controller per scenario. I'm sure this is not how it's done.
I also tried setting the view (in my case App.EditContactView
) instead of the resource name in the connectOutlets
but I got an error saying I can pass "a name or a viewClass but not both" but I was not passing through viewClass
and rather as an argument of connectOutlet
.
I have also tried to set a view or an instance of my view to the route itself and I would either break my JavaScript or in some cases I'd get an error saying that "App.EditContactView does not have a method CharAt".
Then again, I got a little lost. I have seen other questions at SO and else where but the ones I've found were either using ember-routermanager
by Gordon Hempton (which seems good, but I'm interested in using built-in only right now), Ember.StateManager
or not using state/route at all. Documentation isn't explaining too much about these things yet.
Question: What would be the ideal approach to deal with all CRUD scenarios with Ember.Router
?
I want my contactsController
to be able to list all, find one, edit one, add one and delete one contact. Right now I have one contactsController
with findAll
and one contactController
with find
, edit
, remove
, add
because of naming problems.
I am currently not using ember-data so I would be more interested in examples without references to ember-data (I am doing the baby steps without any plug-in for now).
Here's the current version of my router:
JS
App.Router = Ember.Router.extend({
enableLogging: true,
location: 'hash',
root: Ember.Route.extend({
// EVENTS
gotoHome: Ember.Route.transitionTo('home'),
gotoContacts: Ember.Route.transitionTo('contacts.index'),
// STATES
home: Ember.Route.extend({
route: '/',
connectOutlets: function (router, context) {
router.get('applicationController').connectOutlet('home');
}
}),
contacts: Ember.Route.extend({
route: '/contacts',
index: Ember.Route.extend({
route: '/',
contactDetails: function (router, context) {
var contact = context.context;
router.transitionTo('details', contact);
},
contactEdit: function (router, context) {
var contact = context.context;
router.transitionTo('edit', contact);
},
connectOutlets: function (router, context) {
router.get('contactsController').findAll();
router.get('applicationController').connectOutlet('contacts', router.get('contactsController').content);
}
}),
details: Ember.Route.extend({
route: '/:contact_id',
view: App.ContactView,
connectOutlets: function (router, contact) {
router.get('contactController').set('contact', contact);
router.get('applicationController').connectOutlet('contact');
},
serialize: function (router, contact) {
return { "contact_id": contact.get('id') }
},
deserialize: function (router, params) {
return router.get('contactController').find(params["contact_id"]);
}
}),
edit: Ember.Route.extend({
route: '/:contact_id/edit',
viewClass: App.EditContactView,
connectOutlets: function (router, contact) {
router.get('contactController').set('contact', contact);
router.get('applicationController').connectOutlet('contact');
},
serialize: function (router, contact) {
return { "contact_id": contact.get('id') }
},
deserialize: function (router, params) {
return router.get('contactController').find(params["contact_id"]);
}
})
})
})
});
App.initialize();
Relevant templates
<script type="text/x-handlebars" data-template-name="contact-details">
{{#if controller.isLoaded}}
<img {{bindAttr src="contact.imageUrl" alt="contact.fullName" title="contact.fullName"}} width="210" height="240" /><br />
<strong>{{contact.fullName}}</strong><br />
<strong>{{contact.alias}}</strong>
{{else}}
<img src="images/l.gif" alt="" /> Loading...
{{/if}}
</script>
<script type="text/x-handlebars" data-template-name="contact-edit">
<strong>Edit contact</strong><br />
First Name: <input type="text" id="txtFirstName" {{bindAttr value="contact.firstName"}}<br />
Lasst Name: <input type="text" id="txtLastName" {{bindAttr value="contact.lastName"}}<br />
Email: <input type="text" id="txtEmail" {{bindAttr value="contact.email"}}<br />
</script>
<script type="text/x-handlebars" data-template-name="contact-table-row">
<tr>
<td>
<img {{bindAttr src="contact.imageUrl" alt="contact.fullName" title="contact.fullName"}} width="50" height="50" /><br />{{contact.fullName}}
</td>
<td>
Twitter: {{#if contact.twitter}}<a {{bindAttr href="contact.twitter"}} target='_blank'>Follow on Twitter</a>{{else}}-{{/if}}<br />
</td>
<td>
<a href="#" {{action contactDetails context="contact"}}>Details</a> |
<a href="#" {{action contactEdit context="contact"}}>Edit</a>
</td>
</tr>
</script>
Note: If there's anything unclear, please ask in the comment section and I can edit this with more details
Edit: I've added this project to GitHub even tho it's nowhere near what I'd like to expose as a learning sample. The goal is to progress on top of this and create a CRUD template in a near future. Currently using MS Web API, but might add a Rails version soon.
There's a few things going on here, I'll try and answer them, but if I miss anything feel free to leave a comment. You seem to be reinventing a lot of stuff Ember already does for you.
Firstly, if you want to pass a view to the connectOutlet
method you need to pass in a hash as the one and only argument.
router.get('applicationController').connectOutlet({
viewClass: App.EditContactView,
controller: router.get('contactsController'),
context: context
})
Secondly, having two contact controllers is not frowned upon, in fact I'd recommend it. A singular ContactController
that inherits from ObjectController
and a ContactsController
that inherits from ArrayController
, this means you can easily take advantage of the content proxies built in.
Thirdly, if you add find
and findAll
class methods to your models you will make life much easier for yourself.
You won't need to define the serialize/deserialize methods you have defined, by default Ember will look for a model with the name deduced from the route so :contact_id will automatically look for App.Contact.find(:contact_id).
You will also be able to change your index connectOutlets to: router.get('applicationController').connectOutlet('contacts', App.Contact.findAll())
One more tip, currently your details and edit routes are almost completely identical. I would create a single route called company
and then make child details and edit views inside of it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With