Working is extremely nice once you have a grasp on the basic concepts. But, one of the coolest promises (some pun intended) is the ability to use the render view helper. At its core, the render object lets you render a template with a view class wrapping it. However, the real power to the render helper is that it also creates a related controller based on the rendered template's name.

For the most part, this means that you have a segmented controller for handling your actions and events. However, some of the most power from having this controller comes from the fact that you now have a way to pull in new data from ther server. Looking around, I took a while to figure out how to how to set the context for your rendered view.

Attempt 1

Set the content parameter on my controller so that content: App.Store.find('model-name'). This is a common answer seen on older stack overflow results. Unfortunately, the standards have changed quite a bit since most of those posts were written. App.Store doesn't have a find function available, I couldn't figure out what was going on and still don't quite know.

Solution 1

With a view, I knew that I could use didInsertElement to set up my views (once the initial render had completed of course). So, being the smart kid that I am, and knowing that I had scratched a hole in my head from playing with controllers, I did one of the worst things I've ever done with Ember. I'll show the code first:

didInsertElement: function() {
    var self = this;
    $.getJSON(@getAJAXUrl(), function (data) {
         self.categories = data
    });
}

I literally set the property by listening for the view to be rendered, updating the guitarPhotos property (used in the template rendering logic by the way), and then sending it back. What makes this just terribly awful, isn't the fact that there were three page reflows (loading, initial render, and render once the data was pulled in), but basic fact that this worked (and worked well for a while).

This gave me the opportunity to get rid of the render helper and fall back on the view helper. This meant I had my parent view's context. I could use my parent model at will, all my actions had to be on the parent controller, and events wouldn't bubble up, they were forced to the surface. I had scope creep into a child view where I could manipulate much more data than I should have had access to.

NEVER… Please let me repeat myself…

NEVER do this! didInsertElement isn't for loading data. This effected the way I wrote my templates (everything referred to view.guitarPhotos), parent views, and more… It was a mess that I'm still cleaning up.

Problem with Solution 1

The issue that I ran into was that ember views don't traditionally access to the Application store, so using Ember data… Well, that was out of the picture without having to dive into using the IoC container. I resorted to using jQuery to load in some json data and I used then to set the model property (I told you this was super hacked together).

Solution 1.1

The problem with using straight up jQuery was the fact that returning the jQuery.toJSON call didn't really give me my result, this is why I had to used the callback in a traditional jQuery function. Well, after a bit of time scratching my head and working with some view events, I remembered that I could use this.$ in my views and have some really cool features that Ember adds on to jQuery. One of which was to return a promise from all AJAX methods instead of the jqXHR object for the request.

This meant I could use a more readable (but not really game changing) syntax:

didInsertElement: function() {
    var self = this;
    $.getJSON(@getAJAXUrl()).then(function (data) {
         self.categories = data;
    });
}

It didn't help much, but it got me to my next little effort into making things better

Solution 2

After a bit of sleep and getting my head on straight, I thought to myself that I could have just used a computed property since Ember.$ was returning a promise instead of an XHR object. So instead of using the didInsertElement method to fire this off, I went ahead and pulled in a computed property categories:

categories: function() {
    $.getJSON(@getURL());
}.property()

Problem with Solution 2

This is actually usable code. If you aren't using Ember Data or any data adapter, this can actually work for what you need. There is one problem with this though. The view exists before the property is computed. This means that any HTML in your template for this flashes before this returns, then it will flash on to the screen. It's another reflow and isn't really ideal. Since this view doesn't really make sense until the categories property is set, it really doesn't help. Of course, this is easily solved by adding an arbitrary, if loop around the entire template which breaks if the API call returns an empty set of results (oh well).

Solution 3

I was using Ember Data and while it really made me mad that I didn't have access to the store, I had to build my API in a really weird way. To understand this I have to describe the problem this widget was trying to solve.

This screen is showing a storefront. A store has some basic data at the top of the screen, but here we are concerned with product categories (not related to the current store) and then dropping in a list of store products (related by store) within each category. The API endpoint to achieve this was built on my using jQuery and the URL looked a bit like this store/:storeId/categories and to get the products within a category it looked more like this: store/:storeId/categories/:categoryId/store-items. It's not bad, but my front-end app was dictating how I had to build my server API.

So after scratching my head, I came up with a solution… Well sort of… Let's make a logical ember model that relates a store to categories. I called the model (drumroll please)… StoreCategory.

Now I added a storeCategories attribute to my Store ember model, but this wasn't enough. See, since storeCategories didn't really exist, I had to fake it by also adding some parameters to the relationship, so a simple hasMany wouldn't really work. So here's what my store model looked like instead:

App.Store = DS.Model.extend({
    // Other stuff
    categoryIds: DS.attr(),
    categories: function() {
        return this.store.find('store-category', {
            store_id: this.get('id')
        });
    }.property('categoryIds')
});

Now instead of working with a weird view.categories property, we can go back to using render and passing on the categories variable in our parent template like this: {{render 'store/categories' categories}}. Honestly, this code still exists in my code base. This is actually really cool. And now we can use our controller to handle actions in our sub-view.

Solution 4

Now, this worked for when I was only getting the limited categories for a store based on what items they have listed as available. But, then we get to the store editing screen. A store owner needs to be able to get see all the categories and activate items when they get in stock. So, we couldn't just use our trick from solution 3. Instead, we need to figure out something else.

For this I started actually really trying to figure out what was going wrong with setting the content for a rendered view and not passing a context. I knew that this should work some way similar to content: *.store.find.... Even though this topic was quite old, I expected the API to be at least somewhat similar to this. I had to figure it out.

Actually, the trick was how to get the content property to work. I knew I could build my own JSON object or pass in a variable. But getting something from the store just didn't click. I knew that the controller should have a this.store why didn't it exist!?

The answer was something I already have touched on. Since pre 1.0 Ember, computed properties have to be explicitly declared as a computed property (it used to be any function accessed as a variable would be cast as a computed property). So when I declared my content property like this:

content: function() {
    this.store.find('category');
}

But when we make it a computed property:

content: function() {
    this.store.find('category');
}.property()

It's suddenly working!

What was going on? Where's

So the question was why did none of our early efforts work? The trick is the changes in API. The store is now auto injected in our application. We could create extra stores or manually inject things, make a global store, etc. But, as Yehuda and Tom like to brag: "if something seems hard to do, you are probably doing something wrong". It seems cocky, but really, it is true more often than not in my experience.

What was going wrong was my own ignorance to how the controller was being initialized. Of course, this.store doesn't exist before the controller is initialized. Until the IoC resolution works its stuff out, the store isn't set. But once we say that content is a computed property, then that function isn't run until after the controller is set up and the store is resolved. Now like magic, we can pull in data from Ember data as our content for rendered views!