Routes unknown until runtime, dependant on API response


#1

Hi

I’m building an ember app where the routes are not known until runtime, and do not follow a pattern.

The routes are returned from a server as URLs, the routing to the resource is created server side, thus an index page will list a bunch of resources, and each resource’s url will be returned as part of the API response.

In order to know what controller to load, and thus view, etc, the response from the server contains a “template” property that determines which template the content should be rendered into.

How might one go about achieving this in an ember context?

Thanks

Oli


#2

My hunch is that you’ll still have need to have predictable routes. If there’s content at a certain URL that will be used in the route, that URL could be set as a property on the controller, and a function could do an AJAX call out to whatever URL is in the property. Have an observer watch the “template” property that you’re pulling back, and render a view dynamically with that name.

NOTE: I’ve never been able to get views to render dynamically myself, but I’ve heard tell that it’s possible.

Bottom line is that I think you’re better having static routes and dynamic views than the other way around.


#3

Interesting.

I’m currently loading the data in using ember-data from a REST API, so the object representations exist in memory already. I was thinking that I could possibly generate the routes on the fly whilst the data is being loaded, would that be a bad idea?

Alternatively, I notice you can do wildcard catch all routes, might this also be an option?


#4

Well, my train of thought is that Ember routes are meant to be coupled with layout, not anything domain-specific. Assuming that the content from each of these pages are presented in the same part of the application each time, that part of the application is the route- not the underlying data. If that content is laid out differently depending on what it is, it seems to me that this problem should be handled from within the route.

It could totally be possible to do with route wildcards and stuff, and it might even be easier. That feels more hacky than canonical to me though.


#5

What I find odd is that there isn’t an existing use case for this. Take a blog that lists all blog posts on the root domain, eg:

myblog.com/my-article

myblog.com/another-article

But also has tags:

myblog.com/tag-1

myblog.com/tag-2

You can’t possibly add a route for each article manually if they’re DB driven, so how would one cater for this?


#6

With dynamic segments.

I would expect that a list of all of these posts would come from some kind of some kind of service (eg, “GET myblog.com/articles”), which would return some kind of JSON string like this:

{
  "blogArticles": [
    {"id": "1",
    "slug": "my-article",
    "tags": ["tag-1", "tag-2"]
    },{
      ...
    }
  ]
}

In which case a route that lists all of the articles would probably look something like “emberapp.com/blogArticles” or “emberapp.com/articles?blog=myBlog”. A route that displays a specific article (parent-child style) would be something like “emberapp.com/blogArticles/34”.

The space you’ve allocated to display a bunch of blog articles is really the only route. What’s being displayed in it is the model, which can be as dynamic as you need it to be.


#7

Perhaps I wasn’t clear.

Let me suggest the following:

myblog.com/articles.json returns something like the following

{
    data: [
        {
            id: 1,
            url: '/my-article.html',
            title: 'My Article',
            type: 'article',
        }
    ]
}

Now, I can make the index page fine, but say, for example, that I can’t request an article by ID, but it HAS to be by the url property of the response. At this point, I can’t define any routes within my application, I can only know about the route from the response.

From the above response, when /my-article.html is hit, I would want to load the “article” controller, view, and template. If this article isn’t already loaded (via a deep link for example), then an ajax request could be made to /my-article.json to retrieve the response above, and thus inspect the “type” property to determine which controller/view/template to load.


#8

Here is one way you can go about it using an initializer http://jsfiddle.net/NQKvy/1052/

I don’t know anyone who has tried this before (maybe zendesk?) but this is one way that came to my head. There are probably a few approaches.

Edit: Edited to support dynamic views also


#9

Thanks, that’s an interesting solution, however it doesn’t quite cover all use cases.

What happens if a user clicks a link within the body of an article? As the content of the destination URL won’t have been loaded, I can’t know what the destination for the route is until I’ve done the ajax request. Same goes for if the user refreshes the page and clicks the back button.

Still working on this…


#10

I guess a key point thats missing is when will you have the data thats suppose to dictate the route, controller, and view type? I am guessing in some model hook is where you’ll be loading this metadata that registers the routes, controllers, and views.

If so,then afterModel can be the place to create this route generation logic. So, by the time the template renders with it will have the paths defined in the route “type”.


#11

So I managed to solve this using the following:

App.Router.map(function() {
    this.route('catchAll', { path: '*:' });
});

App.CatchAllRoute = Ember.Route.extend({

    model: function(params, transition)
    {
        //Get the URL and convert to route name
        var url = transition.intent.url;
        var route_name = Ember.String.classify(url.replace(/[\.|\-|\/]+/g,'_'));

        //Check if route already exists, if so, transition to it
        var route = this.container.resolve('route:'+route_name);
        if(route) return this.transitionTo(route_name);

        //Get the custom loader and load the data for the destination url
        var loader = this.container.lookup('loader:entityresolver');
        return loader.loadEntitiesForUrl(url).then(function(data){

            //Get the type from the template name
            var type = data.content.templateName;

            //Add a new route for the url in question
            App.Router.map(function(){
                this.resource(route_name, {path: url});
            });

            //Register a new route, manually setting the controller,template and view names
            this.container.register('route:'+route_name, Ember.Route.extend({
                controllerName: type,
                viewName: type,
                templateName: type,

                model: function(){
                    return type == 'panels' ? this.store.findAll('panel') : this.store.find( 'article', url );
                }
            }));

            //Transition to new route
            return this.transitionTo(route_name);

        }.bind(this), function(data){
            //Force a manual page change
            document.location.href = url;
        });
    }
});

#12

We’re doing something similar, with a slightly different scenario that might still be relevant.

The application grew at the point that we need to split, we load login and a few other screens along with the code related to load it. Once it’s there and rendered, we start loading the rest in the background. Once the code is loaded we simply call App.Router.map a second time to add the new routes.

It’s interesting what you’re doing. In our case we also load code and that contains the route definition instead of being dynamic based on the entity.

Something that you might want to consider doing different is to abort the transition and then retry it once you load everything.