Best Practice for implementing similar actions across routes

What’s the best practice for duplication same router actions across different resources?

For instance, in our application, when editing or creating a post there are a few separate sub forms we’d like the user to be able to transition to:

  • /posts/new/photo
  • /posts/5/edit/photo

Would you have to create a postsNewPhotoRoute route, controller, view AND a postEditPhotoRoute route, controller, view? I assume you wouldn’t be able to create say a postPhotoRoute and reuse it for both “new” and “edit” actions would you? What about a way to specify a route to use from the router?

this.route("photo", { route: App.PostPhotoFormRoute }) 

A simple option is to create a Route/Controller/View depending on what you need and extend that.

i.e

App.BaseEditRoute = Ember.Route.extend({
  // ...
})

and then

App.postsNewPhotoRoute = App.BaseEditRoute.extend()
App.postEditPhotoRoute = App.BaseEditRoute.extend()

Or if they share only some logic you could probably create a Mixin and use it when you extend the various Ember Objects

This is something I’d like to support more explicitly. We’re thinking about doing route namespaces where you could re-use identically named routes within unique namespaces.

@pwagenet that would be great!

We have a lot of routes that share the same functionality, too. This is how we do it.

Fx we have 3 ways you can edit an invoice:

  • Edit an existing invoice
  • Create a new invoice
  • Credit an existing invoice

This is the relevant portion of our router.js:

Billy.Router.map(function() {
    this.resource('invoices', function() {
        this.resource('invoice', {path: '/:invoice_id'}, function() {
            this.route('edit');
            this.route('credit');
        });
        this.route('new');
    });
});

We declare the invoice.edit route’s resources first:

Billy.InvoiceEditRoute = Em.Route.extend({
    model: function() {
        return this.modelFor('invoice');
    }
});

Billy.InvoiceEditController = Ember.ObjectController.extend({
  save: function() {
    this.get('model').save();
  },
  //...and other computed properties and actions...
});

Billy.InvoiceEditView = Em.View.extend({
    templateName: 'invoice/edit' //We need to set this explicitly, since the other views will inherit from this one
});

Then we declare the invoices.new route to return a suitable model, and extend invoice.edit’s controller and view:

Billy.InvoicesNewRoute = Em.Route.extend({
    model: function() {
        var invoice = Billy.Invoice.createRecord({
            entryDate: moment(),
            currency: Billy.session.get('organization.baseCurrency'),
            state: 'draft'
        });
        Billy.InvoiceLine.createRecord({
            invoice: invoice,
            quantity: 1
        });
        return model;
    }
});

Billy.InvoicesNewController = Billy.InvoiceEditController.extend();

Billy.InvoicesNewView = Billy.InvoiceEditView.extend();

And we would do the same for Billy.InvoiceCreditRoute, -Controller and -View.

What’s in invoice/edit.hbs is not really important. The important thing is that they all share this template since Billy.InvoiceEditView explicitly declares it as its templateName property.

This works pretty well in practice. But it could be a more integrated part of the framework.

@seilund Just a couple of thoughts.

  1. If your view doesn’t hold any logics other than templateName, i guess there is no need to have a separate view instance. Your Billy.InvoiceEditRoute by default will render the invoice/edit template.

  2. You mentioned for Billy.InvoiceEditView

We need to set this explicitly, since the other views will inherit from this one

Just specifying the templateName in renderTemplate as

Billy.InvoicesNewRoute = Em.Route.extend({
  //....
  renderTemplate: function(){
    this.render('invoice/edit');
  }
});

would take InvoiceEditController as its context which eliminates the necessity of having Billy.InvoicesNewController explicity.

The above two cases comes into play when you are trying to use the same template and controller for new and edit cases.

True. Though in our case we do have specific logic on the different controllers for example:

Billy.InvoiceEditController = Ember.ObjectController.extend({
  title: function() {
    return 'Edit invoice #' + this.get('model.invoiceNo');
  }.property('model.invoiceNo')
});

Billy.InvoicesNewController = Billy.InvoiceEditController.extend({
  title: 'Create invoice'
});

For our implementation, we essentially have a (extending the invoice example):

App.InvoiceFormController = Ember.ObjectController.extend(...)
App.InvoiceFormView = Ember.View.extend(...)

App.InvoiceEditController = App.InvoiceFormController.extend(...)
App.InvoiceNewController = App.InvoiceFormController.extend(...)

...

In my original example, however, I included nested actions inside these “new” and “edit” routes:

  • /posts/new/photo
  • /posts/5/edit/photo

Our ‘posts’ have the ability to add/edit different types of media: photos, videos, etc. Originally I was wanting to have these (toggable) media forms available via routes. I don’t remember all of the problems that we were having, but for one, if your router looks like:

App.Router.map(function() {
    this.resource('posts', function() {
        this.resource('post', {path: '/:post_id'}, function() {
            this.resource('edit', function() {
               this.route("photo");
            });
        });
        this.resource('new', function() {
            this.route("photo");
        });
    });
});

Then the naming structure implies that you’d need “newPhotoRoute”, “editPhotoRoute”, etc…this didn’t bode well with me as we have more “things” then just posts. Furthermore, you can’t just {{linkTo “photo”}}, you have to {{linkTo “new.photo”}} or {{linkTo “post.new.photo”}} which is hard to do if you want to have your new and edit views share the same template.

We then tried making these routes resources:

this.resource("edit" function() {
    this.resource("photo");
});

This allowed us to have “photoRoute” and could {{linkTo “photo” }} from within the new and edit views. HOWEVER, ember didn’t handle the dup’d resources with the same name well, and it linked to the first “photo” resource defined in the router, so even though we were in the “post.new” route, {{linkTo “photo”}} would create a link to “post.edit.photo”

No good. Essentially we opted to abandon using routes for these sub-actions, but it would be nice to revisit that in the future.

1 Like