Nested resources revisited


#1

OK after much frustration, I still can’t quite get a handle on this topic. Maybe I am just dull witted. But ember is really making me feel stupid on this particular topic. It is my major point of despair with the Ember framework.

Does anyone have a canonical example of deeply nested resources? Essentially a collection of collections of collections hierarchy at least three levels deep.

Here are two possible router examples

Still not sure how to wire up the rest of this. Maybe I just have a fundamental misunderstanding of how to properly nest resources. And the difference between a resource and a route. Can someone help me with this bag of hurt? It is quite possible I am just being stubborn. But I don’t get it.

The data can be anything but I would love to see the two following routers implemented in functional JSBin examples.

App.Router.map(function() {
    this.resource('libraries', function() {
        this.route('new');

        this.resource('library', {path: ':library_id'}, function() {

            this.resource('books', function() {
                this.route('new');
                this.resource('book', {path: ':book_id'}, function() {

                    this.resource('pages', function() {
                        this.route('new');
                        this.resource('page', {path: ':page_id'}, function() {

                        }); // Page
                    }); // Pages

                }); // Book
            }); // Books

        }); // Library
    }); // Libraries
}); // map

A more abstract example

App.Router.map(function() {
    this.resource('athings', function() {
       this.resource('athing', { path: ':athing_id' }, function() {

            this.resource('bthings', function() {
               this.resource('bthing', { path: ':bthing_id' }, function() {

                    this.resource('cthings', function() {
                       this.resource('cthing', { path: ':cthing_id' }, function() {

                       }); // cthing
                    }); // cthings

               }); // bthing
            }); // bthings

       }); // athing
    }); // athings
});

#2

I’ll take a crack at it. What are you trying to do with these routes? Where do you get stuck?


#3

That would be awesome @tarasm

I would love to see all three collections represented on a single page.

Imagine URLs that look like this

So in the above example the dynamic segments are:

  • “seattle-library”
  • “how-to-use-ember” or “how-to-use-angular”
  • 110 or 111, or 1, or 2, etc

Basically I would like to represent some data about the library, the books, and the pages along with the exact objects all within a single view or page. Think breadcrumb navigation.

Hope that makes sense. I can try creating a nested JSON fixture if that would help. But it doesn’t matter to me too much what the actual data is. Just that there are essentially three array controllers all living on a single page.

Also, would be helpful to see the intermediary index states as well. e.g.

It is quite possible I have the router not properly defined but perhaps the above URLs convey my intent.

Let me know if you have more questions, happy to flesh it out. Just trying to think this one through.


#4

I cloned it and working with the clone so we don’t step all over each other’s code. http://emberjs.jsbin.com/UmesorE/1/edit


#5

I did it. http://emberjs.jsbin.com/UmesorE/6/edit


#6

This is awesome and very helpful! Just the kind of example I was looking for. I will look over it closer. Thanks so much!


#7

Cool, I’m Glad I Was Able To Help

Ps: Discuss On AndOid Only Allows Me To Write In Capital Letters


#8

I’m cross posting it here so it doesn’t get lost.

I added serialize method to LibraryRoute, BookRoute and PageRoute. This is necessary because our object keys are not id, so we have to specify how to serialize that route.

App = Ember.Application.create();

App.Router.map(function() {
    this.resource('libraries', function() {
        this.route('new');

        this.resource('library', {path: ':library_id'}, function() {

            this.resource('books', function() {
                this.route('new');
                this.resource('book', {path: ':book_id'}, function() {

                    this.resource('pages', function() {
                        this.route('new');
                        this.resource('page', {path: ':page_id'}, function() {

                        }); // Page
                    }); // Pages

                }); // Book
            }); // Books

        }); // Library
    }); // Libraries
}); // map

var libraries = [
  Ember.Object.create({ library_id: 'l1', name: 'Seattle Library'}),
  Ember.Object.create({ library_id: 'l2', name: 'Toronto Library'}),
  Ember.Object.create({ library_id: 'l3', name: 'Montreal Library'})      
];

var books =[
  Ember.Object.create({ book_id: 'b1', title: 'War and Peace'}),
  Ember.Object.create({ book_id: 'b2', title: 'Little Prince'}),
  Ember.Object.create({ book_id: 'b3', title: 'The Jungle Book'})  
];

var pages =[
  Ember.Object.create({ page_id: '1', number: 1}),
  Ember.Object.create({ page_id: '2', number: 2}),
  Ember.Object.create({ page_id: '3', number: 3})  
];

App.IndexRoute = Ember.Route.extend({
  redirect: function() {
    this.transitionTo('libraries');
  }
});

App.LibrariesRoute = Ember.Route.extend({
  model: function() {
    return libraries;
  }
});

App.LibraryRoute = Ember.Route.extend({
  actions: {
    goToBooks: function(library_id) {
      this.transitionTo('books', library_id);
    }
  },
  model: function(params) {
    // add parameters to the injected object
    return libraries.findBy('library_id', params.library_id);
  },
  serialize: function(model) {
    return model.get('library_id');
  }
});

App.BooksRoute = Ember.Route.extend({
  actions: {
    goToBook: function(book_id) {
      // use the saved library_id
      var current = this.router.router.currentParams;
      this.transitionTo('book', current.library_id, book_id);
    }
  },
  model: function(params) {
    return books;
  }  
});

App.BookRoute = Ember.Route.extend({
  actions: {
    showIndex: function() {
      var current = this.router.router.currentParams;
      this.transitionTo('pages', current.library_id, current.book_id);
    }
  },
  model: function(params) {
    return books.findBy('book_id', params.book_id);
  },
  serialize: function(model) {
    return model.get('book_id');
  }  
});

App.PagesRoute = Ember.Route.extend({
  actions: {
    goToPage: function(page_id) {
      var current = this.router.router.currentParams;
      this.transitionTo('page', current.library_id, current.book_id,  page_id);
    }
  },  
  model: function(params) {
    return pages;
  }
});

App.PageRoute = Ember.Route.extend({
  model: function(params) {
    return pages.findBy('page_id', params.page_id);
  },
  serialize: function(model) {
    return model.get('page_id');
  }
});

#9

I changed the code to use transition.params it added more code but its better than using private API

http://emberjs.jsbin.com/UmesorE/24


#10

OK, here is something that is not clear to me. Assume the following relationships

a library -> has many books

a book -> has many pages

etc.

Now when on the specific library how do I make use of the specific library param id when looking for books?

Another way of saying this:

How to retrieve a collection filtered by the parent object’s id?

http://emberjs.jsbin.com/UmesorE/24#/libraries/l1/books

Maybe this is related to the transition.params but it is not very clear how I would do a query that looked liked this:

App.BooksRoute = Ember.Route.extend({
  model: function(params) {
    return this.store.find('book', {library: params.library_id} );
  }  
});

The presumption is that when I return books I only want to see the books in a specific library. Assume for the moment that I can pass a library id to my books endpoint to filter the collection.

Does this make sense? This may be an ember data question more than a nested routes question but that is what is confusing me at the moment.


#11

@eccegordo like this

App.BooksRoute = Ember.Route.extend({
  model: function(params, transition) {
    this.set('params', transition.params);
    return this.store.find('book', {library: transition.params.library_id} );
  }  
});

#13

Thanks @tarasm.

One last question and I think I have this all ironed out.

Inside a controller how can you can tell if a specific route is active?

Here is the scenario when I am at http://example.com/#/libraries/:library_id

I want to display a link that says “show books” and links to

http://example.com/#/libraries/:library_id/books

But once I am at the “books” nested resource I want to hide the “show books” link.

To be more concrete on this page

http://emberjs.jsbin.com/UmesorE/24#/libraries/l1/books

How would you hide the “See list of books” button?


#14

Looks like

App.Router.router.currentHandlerInfos

Has some useful information.


#15

Yeah, that part is a bit annoying. I don’t know a good way to do it other than.

App.BooksRoute = Ember.Route.extend({
  model: function(params, transition) {
    this.set('params', transition.params);
    return this.store.find('book', {library: transition.params.library_id} );
  },
  setupController: function(controller, models) {
    controller.set('content',models);
    controller.set('params', this.get('params'));
  }
});

And then in controller use the params.