Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ember.js - CRUD scenarios - Specifying View from within a Route

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.

like image 878
MilkyWayJoe Avatar asked Jul 07 '12 18:07

MilkyWayJoe


1 Answers

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.

like image 97
Bradley Priest Avatar answered Oct 04 '22 00:10

Bradley Priest