We have a layout that needs the current data that is defined in our controller like this:
TripController = RouteController.extend({
layoutTemplate: 'tripLayout',
data: function () {
return Trips.findOne({_id: this.params._id});
}
});
Our problem seems like a data-race:
Most of the times Template.currentData() is null but sometimes (mostly while debugging) it is our data that we defined.
The problem only occurs when we reload the page while we are in the tripLayout, but not when we enter the trip layout from another page.
Note that we thing that the `TripController is loaded, as the controller params is correctly set.
Template.tripLayout.onRendered(function() { // note, this is the layout template
Iron.controller.params._id // correct id
Template.currentData() // null
});
What we are trying to achieve is a split layout where the right side is always the same and the left side is filled by yield (see screenshot)
UPDATE
(THIS UPDATE IS WRONG)
I think I found the error:
waitOn: function () {
return [Meteor.subscribe('public-trips'),
Meteor.subscribe('my-trips')];
}
As soon as I remove the array (so only one subscription), the
data: function () {
return Trips.find({}).fetch();
}
does not return 0 anymore. I will look into it a bit more.
UPDATE2
So my initial assumption was wrong, subscribing to only one does not help. It is really just a race condition.
UPDATE3
I managed it to reproduce it: meteorpad
In the alerts, it shows the number of players it has. First time it is 0 and then 6. But the 0 should not appear, as we 'waitOn' it?!
We managed to solve the 'problem' (It is expected behaviour, but we found a workaround)! Details at https://github.com/iron-meteor/iron-router/issues/1464#issuecomment-158738943:
Alright, here's how we fixed that problem:
The first important thing is to to include the action function like
so:
action: function() {
if (this.ready()) {
this.render();
}
}
That worked for almost everything, but we still had some issues. In
our case, it was about templates that aren't rendered in using yield,
and are thus out of Iron Router's control. Switch these over to
content regions (using {{yield 'contentRegionName'}}) and then
explicitly render them in the action function like so:
action: function() {
if (this.ready()) {
this.render();
this.render('contentRegionTemplate', {to: 'contentRegionName'});
}
}
You need these action functions, since the waitOn only means that the
subs defined there will be added to a waitlist, but not that that
waitlist will be waited on. Essentially, what happens now is that your
action function is called twice, thanks to reactivity, and the
templates are only rendered once the data is ready.
Related
I seem to often end up in a situation where I am rendering a view, but the Model on which that view depends is not yet loaded. Most often, I have just the model's ID taken from the URL, e.g. for a hypothetical market application, a user lands on the app with that URL:
http://example.org/#/products/product0
In my ProductView, I create a ProductModel and set its id, product0 and then I fetch(). I render once with placeholders, and when the fetch completes, I re-render. But I'm hoping there's a better way.
Waiting for the model to load before rendering anything feels unresponsive. Re-rendering causes flickering, and adding "loading... please wait" or spinners everywhere makes the view templates very complicated (esp. if the model fetch fails because the model doesn't exist, or the user isn't authorized to view the page).
So, what is the proper way to render a view when you don't yet have the model?
Do I need to step away
from hashtag-views and use pushState? Can the server give me a push? I'm all ears.
Loading from an already-loaded page:
I feel there's more you can do when there's already a page loaded as opposed to landing straight on the Product page.
If the app renders a link to a Product page, say by rendering a ProductOrder collection, is there something more that can be done?
<ul id="product-order-list">
<li>Ordered 5 days ago. Product 0 (see details)</li>
<li>Ordered 1 month ago. Product 1 (see details)</li>
</ul>
My natural way to handle this link-to-details-page pattern is to define a route which does something along these lines:
routes: {
'products/:productid': 'showProduct'
...
}
showProduct: function (productid) {
var model = new Product({_id: productid});
var view = new ProductView({model: model});
//just jam it in there -- for brevity
$("#main").html(view.render().el);
}
I tend to then call fetch() inside the view's initialize function, and call this.render() from an this.listenTo('change', ...) event listener. This leads to complicated render() cases, and objects appearing and disappearing from view. For instance, my view template for a Product might reserve some screen real-estate for user comments, but if and only if comments are present/enabled on the product -- and that is generally not known before the model is completely fetched from the server.
Now, where/when is it best to do the fetch?
If I load the model before the page transition, it leads to straightforward view code, but introduces delays perceptible to the user. The user would click on an item in the list, and would have to wait (without the page changing) for the model to be returned. Response times are important, and I haven't done a usability study on this, but I think users are used to see pages change immediately as soon as they click a link.
If I load the model inside the ProductView's initialize, with this.model.fetch() and listen for model events, I am forced to render twice, -- once before with empty placeholders (because otherwise you have to stare at a white page), and once after. If an error occurs during loading, then I have to wipe the view (which appears flickery/glitchy) and show some error.
Is there another option I am not seeing, perhaps involving a transitional loading page that can be reused between views? Or is good practice to always make the first call to render() display some spinners/loading indicators?
Edit: Loading via collection.fetch()
One may suggest that because the items are already part of the collection listed (the collection used to render the list of links), they could be fetched before the link is clicked, with collection.fetch(). If the collection was indeed a collection of Product, then it would be easy to render the product view.
The Collection used to generate the list may not be a ProductCollection however. It may be a ProductOrderCollection or something else that simply has a reference to a product id (or some sufficient amount of product information to render a link to it).
Fetching all Product via a collection.fetch() may also be prohibitive if the Product model is big, esp. in the off-chance that one of the product links gets clicked.
The chicken or the egg? The collection.fetch() approach also doesn't really solve the problem for users that navigate directly to a product page... in this case we still need to render a ProductView page that requires a Product model to be fetched from just an id (or whatever's in the product page URL).
Alright, so in my opinion there's a lot of ways that you can fix this. I'll list all that I've thought of and hopefully one will work with you or at the very minimum it will inspire you to find your optimal solution.
I'm not entirely opposed to T J's answer. If you just go ahead and do a collection.fetch() on all the products when the website is loading (users generally expect there to be some load time involved) then you have all of your data and you can just pass that data round like he mentioned. The only difference between what he's suggesting and what I normally do is that I usually have a reference to app in all my views. So, for example in my initialize function in app.js I'll do something like this.
initialize: function() {
var options = {app: this}
var views = {
productsView: new ProductsView(options)
};
this.collections = {
products: new Products()
}
// This session model is just a sandbox object that I use
// to store information about the user's session. I would
// typically store things like currentlySelectedProductId
// or lastViewedProduct or things like that. Then, I just
// hang it off the app for ease of access.
this.models = {
session: new Session()
}
}
Then in my productsView.js initialize function I would do this:
initialize: function(options) {
this.app = options.app;
this.views = {
headerView: new HeaderView(options),
productsListView: new ProductsListView(options),
footerView: new FooterView(options)
};
}
The subviews that I create in the initialize in productsView.js are arbitrary. I was mostly just trying to demonstrate that I continue to pass that options object to subviews of views as well.
What this does is allows every view, whether it be a top level view or deeply nested subview, every view knows about every other view, and every single view has reference to the application data.
These two code samples also introduce the concept of scoping your functionality as precise as you possibly can. Don't try to have a view that does everything. Pass functionality off to other views so that each view has one specific purpose. This will promote reuse of views as well. Especially complex modals.
Now to get back to the actual topic at hand. If you were going to go ahead and load all of the products up front where should you fetch them? Because like you said you don't want a blank page just sitting there in front of your user. So, my advice would be to trick them. Load as much of your page as you possibly can and only block the part that needs the data from loading. That way to the user the page looks like it's loading while you're actually doing work behind the scenes. If you can trick the user into thinking the page is steadily loading then they are much less likely to get impatient with the page load.
So, referencing the initialize from productsView.js, you could go ahead and let the headerView and footerView render. Then, you could do your fetch in the render of the productsListView.
Now, I know what you're thinking. Have I lost my mind. If you do a fetch in the render function then there's no way that the call will have time to return before we hit the line that actually renders the productsViewList template. Well, luckily there's a couple of ways around that. One way would be to use Promises. However, the way I typically do it is to just use the render function as its own callback. Let me show you.
render: function(everythingLoaded) {
var _this = this;
if(!!everythingLoaded) {
this.$el.html(_.template(this.template(this)));
}
else {
// load a spinner template here if you want a spinner
this.app.collection.products.fetch()
.done(function(data) {
_this.render(true);
})
.fail(function(data) {
console.warn('Error: ' + data.status);
});
}
return this;
}
Now, by structuring our render this way the actual template won't load until the data has fully loaded.
While we have a render function here I want to introduce another concept that I use every where. I call it postRender. This is a function where I execute any code that depends on DOM elements being in place once the template has finished loading. If you were just coding a plain .html page then this is code that traditionally goes in the $(document).ready(function() {});. It may be worth noting that I don't use .html files for my templates. I use embedded javascript files (.ejs). Continuing on, the postRender function is a function that I have basically added to my boiler plate code. So, any time I call render for a view in the code base, I immediately chain postRender onto it. I also use postRender as a call back for itself like I did with the render. So, essentially the previous code example would look something like this in my code base.
render: function(everythingLoaded) {
var _this = this;
if(!!everythingLoaded) {
this.$el.html(_.template(this.template(this)));
}
else {
// load a spinner template here if you want a spinner
this.app.collection.products.fetch()
.done(function(data) {
_this.render(true).postRender(true);
})
.fail(function(data) {
console.warn('Error: ' + data.status);
});
}
return this;
},
postRender: function(everythingLoaded) {
if(!!everythingLoaded) {
// do any DOM manipulation to the products list after
// it's loaded and rendered
}
else {
// put code to start spinner
}
return this;
}
By chaining these functions like this we guarantee that they'll run sequentially.
=========================================================================
So, that's one way to tackle the problem. However, you mentioned that you don't want to necessarily load all of the products up front for fear that the request could take too long.
Side Note: You should really consider taking out any information related to the products call that could cause the call to take a considerable amount of time, and make the larger pieces of information a separate request. I have a feeling that users will be more forgiving about data taking a while to load if you can get them the core information really fast and if the thumbnails related to each product takes a little longer to load it shouldn't be then end of the world. That's just my opinion.
The other way to solve this problem is if you just want to go to a specific product page then just implement the render/postRender pattern that I outlined above on the individual productView. However note that your productView.js will probably have to look something like this:
initialize: function(options) {
this.app = options.app;
this.productId = options.productId;
this.views = {
headerView: new HeaderView(options),
productsListView: new ProductsListView(options),
footerView: new FooterView(options)
};
}
render: function(everythingLoaded) {
var _this = this;
if(!!everythingLoaded) {
this.$el.html(_.template(this.template(this)));
}
else {
// load a spinner template here if you want a spinner
this.app.collection.products.get(this.productId).fetch()
.done(function(data) {
_this.render(true).postRender(true);
})
.fail(function(data) {
console.warn('Error: ' + data.status);
});
}
return this;
},
postRender: function(everythingLoaded) {
if(!!everythingLoaded) {
// do any DOM manipulation to the product after it's
// loaded and rendered
}
else {
// put code to start spinner
}
return this;
}
The only difference here is that the productId was passed along in the options object to the initialize and then that's pulled out and used in the .fetch in the render function.
=========================================================================
In conclusion, I hope this helps. I'm not sure I've answered all of your questions, but I think I made a pretty good pass at them. For the sake of this getting too long I'm going to stop here for now and let you digest this and ask any questions that you have. I imagine I'll probably have to do at least 1 update to this post to further flush it out.
You started saying:
I have a listing of items in one Collection view
So what does a collection have..? Models..!
When you do collection.fetch() you retrieve all the models.
When the user selects an item, just pass the corresponding model to the item view, something like:
this.currentView = new ItemView({
model: this.collection.find(id); // where this points to collection view
// and id is the id of clicked model
});
This way, there there won't be any delay/ improper rendering.
What if your collections end point returns huge volume of data..?
Then implement common practices like pagination, lazy loading etc.
I construct a Product model with the given ID
To me that sounds wrong. If you have a collection of products, you shouldn't be constructing such models manually.
Have the collection fetch your models before rendering the list view. This way all the problem you mentioned can be avoided.
I'm currently in the middle of the Iron Yard program and we moved onto Angular this week. We did Backbone last week and so far, I think Angular is leaps and bounds ahead of it.
I've only been working with frameworks for those two weeks though, so the whole MVC concept is still a bit fuzzy to me. Going from being able to freely manipulate elements with jQuery to having to go through a bunch of services and controllers is a big change for me.
This is what my project looks like, it's a very basic store built on the Etsy API:
http://claytonkinder.github.io/Kraftee/#/
Please forgive any layout issues/unoptimized code, we move very quickly so I often have to wait until I have free time before I can come back and shape it up.
My main issue right now is that while adding items to the cart works, a page refresh is required. This happens with the number that's displayed in the cart icon in the header. It also happens whenever you delete an item from the cart on the cart page, which makes it a very frustrating experience.
However, for this thread, I'd like to mainly focus on getting the number in the cart to update in real-time, since I'll probably be able to apply that code to the other features that need it.
This is the view I'm using for my header. It should already be on the line related to the number of items in the cart:
https://github.com/ClaytonKinder/Kraftee/blob/gh-pages/views/header.html#L8
This is the controller related to the Cart:
https://github.com/ClaytonKinder/Kraftee/blob/gh-pages/js/controllers.js#L20
This is the service/factory related to the Cart:
https://github.com/ClaytonKinder/Kraftee/blob/gh-pages/js/services.js#L56
You can see in the controller where I tried $broadcast to fix the issue, but I don't know how to apply that to the view.
If you have any advice/tips for any other portions of code (HTML, CSS, JS, whatever), please feel free to share them. Any improvement at all is a good thing.
The following method
CartService.addToCart(product);
CartService.deleteFromCart(productId);
Both make a HTTP call and hence are Async. so $rootScope.$broadcast('cart-scanned'); is called even before the CartService.addToCart(product); or CartService.deleteFromCart(productId); has returned.
$scope.addToCart = function (product) {
console.log('Item added.');
CartService.addToCart(product);
// called before the http called returns
$rootScope.$broadcast('cart-scanned');
};
$scope.deleteFromCart = function (productId) {
console.log('Item deleted.');
CartService.deleteFromCart(productId);
// called before the http called returns
$rootScope.$broadcast('cart-scanned');
};
Use promises. $q is the service in angular. check out the docs here. Use the following as an example:
In services.js
.factory('CartService', function ($http, $q) {
//....
var addToCart = function (product) {
var deferred = $q.defer();
console.log('Added to cart.');
$http.post(url, product).success(function (resp) {
console.log(resp);
deferred.resolve(resp);
}).error(function (err) {
console.log(err);
deferred.reject(err);
});
return deferred.promise;
};
In controllers.js
$scope.addToCart = function (product) {
console.log('Item added.');
CartService.addToCart(product).then(function () {
$rootScope.$broadcast('cart-scanned');
});
};
$scope.$on('cart-scanned', function () {
CartService.getCart().success(function (cart) {
$scope.cart = cart;
});
});
CartService.addToCart(product) will return a Promise and the callback passed in the then method is called only after the http call completes.
In the template
<div ng-controller="CartController" id="cartNumber">
{{ cart.length }}
</div>
cart refers to the $scope.cart so you have to update that to in order to update the view.
Please let me know if this doesn't work. You can use the same technique for deleteFromCart.
EDIT: Here is the github repo. And you can test the site here.
On the homepage, just open the browser console and you will notice that WaitOn and data are being run twice. When there is no WaitOn, then the data just runs once.
I have setup my pages by extending RouteController and further extending these controllers. For example:
ProfileController = RouteController.extend({
layoutTemplate: 'UserProfileLayout',
yieldTemplates: {
'navBarMain': {to: 'navBarMain'},
'userNav': {to: 'topUserNav'},
'profileNav': {to: 'sideProfileNav'}
},
// Authentication
onBeforeAction: function() {
if(_.isNull(Meteor.user())){
Router.go(Router.path('login'));
} else {
this.next();
}
}
});
ProfileVerificationsController = ProfileController.extend({
waitOn: function() {
console.log("from controller waitOn");
return Meteor.subscribe('userProfileVerification');
},
data: function() {
// If current user has verified email
console.log("from controller data start");
var verifiedEmail = Meteor.user().emails && Meteor.user().emails[0].verified ? Meteor.user().emails[0].address : '';
var verifiedPhoneNumber = Meteor.user().customVerifications.phoneNumber && Meteor.user().customVerifications.phoneNumber.verified ? Meteor.user().customVerifications.phoneNumber.number : '';
var data = {
verifiedEmail: verifiedEmail,
verifiedPhoneNumber: verifiedPhoneNumber
};
console.log("from controller data end");
return data;
}
});
On observing the console in the client, it seems the hooks are being run 2-3 times. And I also get an error on one of the times because the data is not available. The following is the console on just requesting the page once:
from controller waitOn
profileController.js?966260fd6629d154e38c4d5ad2f98af425311b71:44 from controller data start
debug.js:41 Exception from Tracker recompute function: Cannot read property 'phoneNumber' of undefined
TypeError: Cannot read property 'phoneNumber' of undefined
at ProfileController.extend.data (http://localhost:3000/lib/router/profileController.js?966260fd6629d154e38c4d5ad2f98af425311b71:46:62)
at bindData [as _data] (http://localhost:3000/packages/iron_controller.js?b02790701804563eafedb2e68c602154983ade06:226:50)
at DynamicTemplate.data (http://localhost:3000/packages/iron_dynamic-template.js?d425554c9847e4a80567f8ca55719cd6ae3f2722:219:50)
at http://localhost:3000/packages/iron_dynamic-template.js?d425554c9847e4a80567f8ca55719cd6ae3f2722:252:25
at null.<anonymous> (http://localhost:3000/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:2445:26)
at http://localhost:3000/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:1808:16
at Object.Blaze._withCurrentView (http://localhost:3000/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:2043:12)
at viewAutorun (http://localhost:3000/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:1807:18)
at Tracker.Computation._compute (http://localhost:3000/packages/tracker.js?517c8fe8ed6408951a30941e64a5383a7174bcfa:296:36)
at Tracker.Computation._recompute (http://localhost:3000/packages/tracker.js?517c8fe8ed6408951a30941e64a5383a7174bcfa:310:14)
from controller data start
from controller data end
from controller waitOn
from controller data start
from controller data end
Have I not used the controllers properly?
Without being able to see the rest of the code that you have defined that uses these route controllers (such as templates or route definitions), I cannot accurately speak to the reason for the data function being called multiple times. I suspect that you may be using the ProfileVerificationsController with multiple routes, in which case the data definition for this controller would be executed multiple times, one for each route that uses the controller. Since the data definition is reactive, as you browse through your application and data changes, this might be resulting in the code defined to be rerun.
As for your controller definitions, I would suggest making a few modifications to make the code more robust and bulletproof. First, the ProfileController definition:
ProfileController = RouteController.extend({
layoutTemplate: 'UserProfileLayout',
yieldRegions: {
'navBarMain': {to: 'navBarMain'},
'userNav': {to: 'topUserNav'},
'profileNav': {to: 'sideProfileNav'}
},
onBeforeAction: function() {
if(!Meteor.user()) {
Router.go(Router.path('login'));
this.redirect('login'); // Could do this as well
this.render('login'); // And possibly this is necessary
} else {
this.next();
}
}
});
Notice the first thing that I changed, yieldTemplates to yieldRegions. This typo would prevent the regions from your templates using this route controller to be properly filled with the desired subtemplates. Second, in the onBeforeAction definition, I would suggest checking not only whether or not the Meteor.user() object is null using Underscore, but also checking for whether or not it is undefined as well. The modification that I made will allow you to check both states of the Meteor.user() object. Finally, not so much a typo correction as an alternative suggestion for directing the user to the login route, you could use the this.redirect() and this.render() functions instead of the Router.go() function. For additional information on all available options that can be defined for a route/route controller, check this out.
Now for the ProfileVerificationsController definition:
ProfileVerificationsController = ProfileController.extend({
waitOn: function() {
return Meteor.subscribe('userProfileVerification');
},
data: function() {
if(this.ready()) {
var verifiedEmail = Meteor.user().emails && Meteor.user().emails[0].verified ? Meteor.user().emails[0].address : '';
var verifiedPhoneNumber = Meteor.user().customVerifications.phoneNumber && Meteor.user().customVerifications.phoneNumber.verified ? Meteor.user().customVerifications.phoneNumber.number : '';
var data = {
verifiedEmail: verifiedEmail,
verifiedPhoneNumber: verifiedPhoneNumber
};
return data;
}
}
});
Notice the one thing that I changed, which is to wrap all of your code defined in the data option for your controller with a if(this.ready()){}. This is critical when using the waitOn option because the waitOn option adds one or more subscription handles to a wait list for the route and the this.ready() check returns true only when all of the handles in the wait list are ready. Making sure to use this check will prevent any cases of data unexpectedly not being loaded yet when you are building up your data context for the route. For additional information on defining subscriptions for your routes/route controllers, check this out.
As a final suggestion, for your onBeforeAction option definition in your ProfileController, I would suggest moving this out into its own global hook like so:
Router.onBeforeAction(function() {
if(!Meteor.user()) {
Router.go(Router.path('login'));
} else {
this.next();
}
});
Defining this check in the global hook ensures that you don't have to worry about adding your ProfileController to all of your routes just to make sure that this check is run for all of them. The check will be run for every route every time that one is accessed. Just a suggestion, though, as you may have reasons for not doing this. I just wanted to suggest it since I make sure to do it for every Meteor app that I develop for additional security.
Here's my scenario:
I have a layout template that needs to check to see if a User belongs to at least one Team. If not, then display a div across the entire site. A user can only see the teams they belong to, so I created a simple publication that works: (code samples are CoffeeScript)
Meteor.publish 'teams', ->
return null if !#userId
Teams.find {'members._id': #userId}
This works great and Teams.find().fetch() in console gives expected results.
However, if I put this code in, say, the Template.layout.rendered, it doesn't work.
Template.layout.rendered = ->
teams = Teams.find().fetch()
hasTeams = teams.length > 0
if !hasTeams
...do stuff..
Obviously this doesn't work because the Teams find is async and not loaded when it needs to make the decision. With a normal template / page I would just use the IronRouter waitOn() but what do I do on the layout?
I could do a waitOn in my router, but since the data is "global" and going to be used everywhere, and because a user could deep link into the site all over the place, I don't want to add that waitOn to EVERY single route.
So what is the proper pattern? How do I get the meteor client to load global data and wait for the data before running the route?
More thinking and searching found the answer right here on SO: struggling to wait on subscriptions and multiple subscriptions
I changed my Router.configure to this:
Router.configure
layoutTemplate: 'layout'
waitOn: ->
return [
Meteor.subscribe('teams')
]
Multiple subscriptions can be added to the return array, and I believe it will wait on all of them.
I had a similar issue with iron router subscribing to a chat in two different publications with waitOn.
Meteor.subscribe('chats')
Only when I switched positions of the following publish blocks, would it let me see any data on the route. The following is the correct order for the two.
Meteor.publish("chats", function () {
return Chats.find();
});
Meteor.publish("chats", function(id) {
return Chats.find({eventRoom: id});
});
I'm currently evaluating Ember.js and therefore I am building a small sample app. Currently everything went quite smooth so far, but now I don't seem to be able to fix my last little problem.
When I access the app normaly via the route films, everything works as expected. The list of films is displayed. Now when I click onto a film, the details of the film are loaded via setupController hook just below the list of films. That's all just fine.
Here comes my problem: I would like to be able to access the film details directly via url, but somehow in this case another request is fired to grab the film details, with the value of undefinded. As far as I understand that is the model hook.
I can only guess, but I think it is the model hook which is beeing executed.
Can someone point me to the probably obvious mistake I'm making? And on the other hand, is the code i wrote so far "correct"? Or is there a better way of doing this?
(I am aware of the bad way I use to render the film details. I will remove the {{#each}} tag, and change the way I asign the response to the film variable.
Here the link to the sample app: http://jsbin.com/ewiN/1#/films
UPDATE
Ok, now I am getting really confused. I almost have it working, hopefully someone can point it out to me, because it's such a simple task, but it nearly seems impossible to do without knowing ember really well...
When accessing the app via url, it only works when I remove the setupController hook. But I need that hook, to load the FilmDetails on clicking onto the links to properly load the FilmDetails.
http://jsbin.com/ewiN/16#/films/tt0100669
Many thanks for the feedback!
Regards
Reto
Instead of use jQuery.getJSON, use Ember.RSVP.Promise, because internally ember use this promise api instead of jquery. I think that using both, can make inconsistencies.
return new Ember.RSVP.Promise(function(resolve, reject) {
var films = [];
jQuery.getJSON("http://www.omdbapi.com/?s=" + searchTerm, function (response) {
$.each(response.Search, function (index, value) {
films.pushObject(Kitag.Films.create({
title: value.Title,
id: value.imdbID
}));
});
}).fail(reject);
resolve(films);
});
Because we are returning a promise instead of an object, we need to use the model hook, because it is wait until the promise is resolve to render templates.
Kitag.FilmsRoute = Ember.Route.extend({
model: function() {
return Kitag.Films.getMovies('spiderman');
}
});
I have removed the Kitag.FilmRoute, because the expected:
Kitag.FilmRoute = Ember.Route.extend({
model: function(params) {
return Kitag.Film.find(params.id)
},
serialize: function (model) {
return { film_id: model.get("id") };
}
});
is the default.
This is the final result http://jsbin.com/ewiN/15/edit