Best Practice for nested templates


#1

I’m writing a fairly large and complex (for me at least) ember app. Currently all of the templates are under the templates directory and it’s starting to get a bit unwieldy (90 templates and growing)

The application will have several modules, each of which will have multiple sub-modules.

The basic idea for each module is something like:

Planning
|
|--- Home
|
|--- Visits
     |
     |--- Visit
          |
          |--- Tasks
          |
          |--- Requirements
          |
          |--- Issues

My router for the same module is currently structured as follows:

    // Planning
    this.resource('planning', {
        path: 'planning'
    }, function() {

        // Planning Dashboard
        this.resource('planning-home', {
            path: 'home'
        });

        // Planning Visits
        this.resource('planning-visits', {
            path: 'visits'
        }, function() {
            this.resource('planning-visit', {
                path: ':visit_id'
            }, function() {
                this.resource('planning-visit-tasks', {
                    path: 'tasks'
                });
                this.resource('planning-visit-requirements', {
                    path: 'requirements'
                });
                this.resource('planning-visit-issues', {
                    path: 'issues'
                });
            });
        });
    });

I’d like to have my templates organized by module/sub-module to keep things easy to navigate. Based on what I’ve found in my Googling the following structure seems to be correct (#1864 being my primary guide) but I’ve been unable to make it work.

templates
|
|--- planning.hbs
|
|--- /planning
     |
     |--- home.hbs
     |
     |--- visits.hbs
     |
     |--- /visits
          |
          |--- visit.hbs
          |
          |--- /visit
               |
               |--- tasks.hbs
               |--- requirements.hbs
               |--- issues.hbs

Does anyone have any practical experiences with a nested structures like the above?


Mobile, Nested Templates, and Transitions
#2

It may be that the parent route templates are occluding the child routes.

When a child route is rendered (e.g. for ‘planning-home’) it will be rendered through the {{outlet}} of the parent template (e.g. ‘planning’) if the parent template is defined.

In your case, if you have a template called ‘planning’ without an {{outlet}}, you could move the contents to a route/template called ‘planning/index’ instead, and leave the ‘planning’ template undefined (or put in some wrapping markup with an outlet for all child routes). The ‘./index’ will be visited/rendered automatically when visiting the parent route directly.


#3

@macu thanks for the response. The planning template contains only two lines:

{{render "planning-header"}}
{{outlet}}

planning-header is a template that contains nothing but the submodule links (Visits, other sub modules). I can reliably get the planning root template and the header to render but the outlet never seems to work. Even though I have an outlet could the occlusion issue still be occuring? Is it a better practice to have a planning/index template that then acts as the “home” for all of the other planning templates?


#4

How are you currently getting the planning-home template to render, or do you mean the /planning route just has the header and nothing else? Convention is to use the ./index route as the home or entry point to a resource, so yes, I recommend that. Otherwise I’m not sure why sub-routes wouldn’t be rendering through the outlet, unless you’ve overridden something like renderTemplate or templateName in the planning route.


#5

I haven’t overridden any of the existing methods.

I’m redirecting to planning-home in the PlanningIndexRoute

App.PlanningIndexRoute = Em.Route.extend({
    redirect: function() {
        this.transitionTo('planning-home');
    }
});

Am I “going around my elbow to get to my arse” here? I guess the big thing is I want the planning-home template/route to have a URL path of http://myapp.com/planning/home and the IndexRoute redirect was how I achieved the desired result.


#6

I think I’ve spotted the issue. For the template at “templates/planning/home.hbs” you’ll need a route called “home” within the “planning” resource (the route name shouldn’t need the “planning-” prefix as it is auto-prefixed by the parent resource name), which Ember will associate with PlanningHomeRoute and PlanningHomeController etc.

this.resource('planning', function() {
  this.route('home');
});

Then, to transition to this route:

this.transitionTo('planning.home');

This applies to your other routes as well. The names of enclosing resources are not prefixed to sub-route names, but to transition to a sub-route use the dot, like ‘planning.visit.tasks’.


#7

I’ll give it a try and let you know how it works. Thanks again for all the help!


#8

@macu what you suggested got me going in the right direction.

I did end up needing the planning. in front of all of my resource routes. After reading through the Getting Started guide for Routes again it was actually right in front of my face the whole time:

Routes nested under a resource take the name of the resource plus their name as their route name. If you want to transition to a route (either via transitionTo or {{#link-to}}), make sure to use the full route name (posts.new, not new).

That doesn’t address nested resources which seem to explicitly require the full path.

Here is what I ended up with in my router:

// Planning
this.resource('planning', function() {

    // Planning Dashboard
    this.route('home');

    // Planning Visits
    this.resource('planning.visits', { path: 'visits' }, function() {
        this.resource('planning.visits.visit', { path: ':visit_id' }, function() {
            this.route('tasks');
            this.route('requirements');
            this.route('issues');
        });
    });
});

As you can see I had to have the full route path as the name for the resources.

For each route I just had to use the full route path without the dots and camel cased. So the planning.visits.visit.tasks route woud be App.PlanningVisitsVisitTasksRoute. It’s a little redundant but it works!

The folder structure for the module ended up exactly as I wanted it in the OP.

Thanks again for all of your help!

EDIT: The docs DO contain a reference to nested resources. It’s just buried near the bottom. I also didn’t notice that the table listing the created routes and attributes was scrollable and showed the template structure in the last column.


#9

I have to say, that I’m not sure that leads to a lot of benefits. We have something similar and we structured everything under modules to keep the number of templates small, but still using just the resource name when they get compiled and added to the templates hash (Ember.TEMPLATES)

It looks something like this, using grunt-ember-templates:

emberTemplates: { compile: { options: { amd: true, templateBasePath: 'app' }, files: { "clinical-templates.js": "templates/*.hbs", "charting-templates.js": "templates/*.hbs", "labs-templates.js": "templates/*.hbs", } } }

Then I simply require all of those files at runtime and it will add all the templates to Ember.TEMPLATES. There’s the potential for name clashing, but usually each domain has it’s own resources and we have simple ways to identify that.