EDIT: I think I have found a solution. As I say in my question, the variable profiles is a promise so I've tried the following and it works:
...
setupController: function(controller, model) {
controller.set('model', model);
var profiles = App.Profile.findAllByMaster(model.get('id'));
profiles.then(function(data) {
controller.set('profiles', data);
});
}
...
END EDIT
I'm having the error: Assertion failed: an Ember.CollectionView's content must implement Ember.Array. You passed [object Object] when I try to get data from another model in the setupController hook.
The route is MastersMaster which associated model is Master and I try to get the Profiles models that belong to the current Master.
I'm not using Ember Data or something similar. It's just pure jQuery with $.ajax calls.
It's difficult to explain so here is the code excerpt:
App.MastersMasterRoute = Ember.Route.extend({
model: function(params) {
return App.Master.find(params.master_id);
},
setupController: function(controller, model) {
controller.set('model', model);
// if I comment these two lines it works but I don't get the profiles (obviously)
var profiles = App.Profile.findAllByMaster(model.get('id'));
controller.set('profiles', profiles);
}
});
App.Profile = Ember.Object.extend({
id: null,
name: '',
master_id: null
});
App.Profile.reopenClass({
findAllByMaster: function(master_id) {
var profiles = Ember.A();
return $.ajax({
url: 'ajax/get.profiles.php',
type: 'GET',
dataType: 'json',
data: { master_id: master_id }
}).then(function(response) {
$.each(response, function(i, item) {
profiles.pushObject(App.Profile.create(item));
});
return profiles;
});
}
});
If I console.log the variable profiles before doing the controller.set I see that it's a promise and not the array of Profile objects expected. I suppose I have to resolve the promise before but I don't have any idea.
P.S.: sorry my english :(
As I've said in the edit, the problem was that the findAllByMaster method returns a promise so it has to be resolved before assigning it to the controller's property.
I suppose there is a more elegant or efficient way of solving it so another solutions are welcome.
Related
I have a component which adds some functionality to a <select> tag. I want to initialise some javascript after the <select> has fully rendered including all <option> tags. The data used to populate the <option> tags is an array of objects provided from an ajax request.
I'm using ember-data and finding this works when the data is provided from the store, meaning it is an instance of DS.RecordArray which has helpful properties like isLoaded. However, when the data is provided from a jQuery ajax call and is just plain JSON, it appears as though the component tries to render everything before the promise returned by jQuery is fulfilled.
I feel the issue is with how I'm handling promises as the issue seems to be related to things initialising before they should (ie promises have resolved properly). I tried wrapping the ajax call in an RSVP.Promise object but not luck, (I'm using Ember-CLI). Below is a simplified version of what I have so far. Any help would be appreciated.
// My route
export default Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller, model);
var hash = Ember.RSVP.hash({
// myOptions: $.getJSON('/api/options')
myOptions: new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
url: '/api/options',
type: 'get',
dataType: 'json',
success: function(result) {
Ember.run(null, resolve, result);
},
error: function(result) {
Ember.run(null, reject, result);
}
});
})
});
return hash.then(function(result) {
controller.set('optionsForSelect', result.myOptions);
});
}
});
// My component
export default Ember.Select.extend({
didInsertElement: function() {
// Is called before ajax request has finished
this.$().mySelectPlugin({
});
}
});
// Handlebars template
{{my-select-plugin content=optionsForSelect optionValuPath="content.id" optionLabelPath="content.name"}}
To me, this seems like something that should be handled in the controller, not the route. Here's what I did for a similar situation.
App.LessonsShowIndexController = Ember.ObjectController.extend({
author: function() {
return this.get('model').get('author');
}.property('author'),
fullName: function() {
return this.get('author').get('firstName') + ' ' + this.get('author').get('lastName');
}.property('author.isFulfilled'),
});
Using the isFulfilled property allows the controller to wait for the promise to resolve before using the data. In your case, you could have a property that returns a promise and another that waits for it to be fulfilled.
I am trying to implement a controller needing another (CampaignsNew needing AppsIndex), which looks like
App.CampaignsNewController = Ember.Controller.extend({
needs: ['appsIndex']
});
And in my CampaignsNew template I am showing it via
{{#if controllers.appsIndex.content.isUpdating}}
{{view App.SpinnerView}}
{{else}}
{{#each controllers.appsIndex.content}}
{{name}}
{{/each}}
{{/if}}
However controllers.appsIndex.content.isUpdating is never true. I.e. it attempts to show the data before it has been loaded.
My AppsIndex route has the model overridden:
App.AppsIndexRoute = Ember.Route.extend({
model: function(controller) {
var store = this.get('store').findAll('app');
}
...
});
I can get it to work if I put the same code within my CampaignsNew route and modify the template to each through controller.content. Which says to me that needs is not using the route? It also works if I go to the /apps page and it loads the data, and then navigate to the /campaigns/new page.
How do I get this to work? Thanks!
Edit:
As requested, the relevant parts of my router:
App.Router.map(function() {
this.resource('apps', function() {
...
});
this.resource('campaigns', function() {
this.route('new');
});
});
And the AppsIndex is accessed at /apps and CampaignsNew is at /campaigns/new
Edit2:
After implementing the suggestion by #kingpin2k, I've found that Ember is throwing an error. Below are the updated files and the error received.
App.CampaignsNewController = Ember.ObjectController.extend({
pageTitle: 'New Campaign'
});
App.CampaignsNewRoute = Ember.Route.extend({
model: function(controller) {
return Ember.RSVP.hash({
campaign: this.store.createRecord('campaign'),
apps: this.store.find('app')
});
// return this.store.createRecord('campaign');
},
setupController: function(controller, model){
controller.set('apps', model.apps);
this._super(controller, model.campaign);
}
});
Ember throws this error:
Error while loading route: Error: Assertion Failed: Cannot delegate set('apps', <DS.RecordArray:ember689>) to the 'content' property of object proxy <App.CampaignsNewController:ember756>: its 'content' is undefined.
I read online that this is because the content object doesn't exist. If I set it like so:
App.CampaignsNewController = Ember.ObjectController.extend({
content: Ember.Object.create(),
...
});
Then the page loads without error, and when inspecting the Ember Chrome extension, I can see the data has loaded. But it doesn't show on the page. Which I suppose happened because the content object existed and so Ember didn't wait for the model's promise to fulfill before rendering the template. Seems odd that you should have to define content in such a way though. Any insight on how to handle this?
Edit3: Question answered for me in another thread
Based on your router, apps isn't a parent of campaigns/new.
This means someone could hit #/campaigns/new and Ember would hit ApplicationRoute, CampaignsRoute, and CampaignsNewRoute to populate the necessary information for the url requested. Using needs as a way of communicating between controllers really only makes sense in an ancestral pattern (aka communicating with your parents, grandparents etc).
Just as another quick note, AppsIndex is a route of Apps, it won't be hit when your url includes a child. e.g.
Router
this.resource('apps', function() {
this.resource('chocolate', function(){
.....
});
});
Url being hit
#/apps/chocolate
Routes that will be hit
ApplicationRoute
AppsRoute
ChocolateRoute
ChocolateIndexRoute
The index route is only hit when you don't specify a route of a resource, and you are hitting that exact resource (aka nothing past that resource).
Update
You can return multiple models from a particular hook:
App.FooRoute = Em.Route.extend({
model: function(){
return Em.RSVP.hash({
cows: this.store.find('cows'),
dogs: this.store.find('dogs')
});
}
});
If you want the main model to still be cows, you could switch this up at the setupController level.
App.FooRoute = Em.Route.extend({
model: function(){
return Em.RSVP.hash({
cows: this.store.find('cows'),
dogs: this.store.find('dogs')
});
},
setupController: function(controller, model){
controller.set('dogs', model.dogs); // there is a property on the controller called dogs with the dogs
this._super(controller, model.cows); // the model backing the controller is cows
}
});
Check out the second answer here, EmberJS: How to load multiple models on the same route? (the first is correct as well, just doesn't mention the gotchas of returning multiple models from the model hook).
You can also just set the property during the setupController, though this means it won't be available when the page has loaded, but asynchronously later.
Which controller?
Use Controller if you aren't going to back your controller with a model.
App.FooRoute = Em.Route.extend({
model: function(){
return undefined;
}
});
Use ObjectController, if you are going to set the model of the controller as something, that isn't a collection.
App.FooRoute = Em.Route.extend({
model: function(){
return Em.RSVP.hash({
cows: this.store.find('cows'),
dogs: this.store.find('dogs')
});
}
});
Use ArrayController if that something is going to be a collection of some sort.
App.FooRoute = Em.Route.extend({
model: function(){
return ['asdf','fdsasfd'];
}
});
Note
If you override the setupController, it won't set the model of the controller unless you explicitly tell it to, or use this._super.
App.FooRoute = Em.Route.extend({
model: function(){
return Em.RSVP.hash({
cows: this.store.find('cows'),
dogs: this.store.find('dogs')
});
},
setupController: function(controller, model){
controller.set('cows', model.cows);
controller.set('dogs', model.dogs);
// uh oh, model isn't set on the controller, it should just be Controller
// or you should define one of them as the model
// controller.set('model', model.cows); or
// this._super(controller, model.cows); this does the default setupController method
// in this particular case, ArrayController
}
});
Ember seems to be removing the query string from the URL.
I've stepped through the code, and I know for sure that I'm setting the flag correctly:
<script>
ENV = {FEATURES: {'query-params-new': true}};
</script>
<script src="js/libs/ember.prod-1.6.0beta+canary.js"></script>
But when my route loads, the query string is being removed, and I can't access the queryParams.
Here's my router:
App.Router.map(function () {
this.resource('simpleSearch', {path: 'simplesearch'}, function () {
this.resource('simpleSearchOption', {path: ':simpleSearchOption_id'});
this.resource('simpleSearchResults', {path: 'results'});
});
});
When I attempt the following url (which is based on the URL from the guide), the query string is stripped: [webserver]/#/simplesearch/0?simplesearch[height]=10
When the model is first initialized by the route, it builds out what the query parameters will be, and the controller's queryParams property is set by the route:
App.SimpleSearchRoute = Ember.Route.extend({
model: function () {
var optionsForSimpleSearchModel = [];
for (var i = 0; i < App.SimpleSearchOptions.length; i++) {
optionsForSimpleSearchModel[i] = App.SimpleSearchOption.create(App.SimpleSearchOptions[i]);
}
return App.SimpleSearch.create({
'simpleSearchOptions': optionsForSimpleSearchModel,
'numOfOptions': App.SimpleSearchOptions.length
});
},
setupController: function (controller, model) {
console.log(model.get('queryParams'));
controller.set('queryParams', model.get('queryParams'));
controller.set('model', model);
}
});
BUT, I've also tried explicitly setting the queryParams in the controller:
App.SimpleSearchController = Ember.ObjectController.extend({
height: null,
queryParams: ['height'],
...
I'm not sure what else I'm missing...
How does this thing really work?
It seems that I'm a silly dude.
I needed to add the params argument to the model() function:
model: function (params) {
console.log(params);
//{height: null} when queryParams['height'] is explicitly set in the controller
Is there any way that I can dynamically generate the queryParams for the controller before Ember decides there are none, if I don't set them explicitly?
Also, my URL was incorrect, (as is the one in the Ember guide). It should have been:
[webserver]/#/simplesearch/0?height=10
instead of
[webserver]/#/simplesearch/0?simplesearch[height]=10
In your model hook you need to pass in the params.
App.SimpleSearchRoute = Ember.Route.extend({
model: function (params) {
return this.store.findQuery('simpleSearch', params);
}
});
Here is another question along the same lines.
Cheers
I've been googling and scouring Stack Overflow for some sort of hint on this subject but the information is scattered at best.
I'm trying to Create a new Child Record (Comment) and save it to an existing Parent Record (Post). I am using Ember-Model, rather than Ember-Data, but any tips or pointers would be greatly appreciated.
At the moment, I've been successful creating a new, embedded Comment but only when it is created with a new Post record. So:
How do I go about loading/retrieving the currently loaded Post(parent record) in order to apply Comments (child records) to it?
I've been reading up on controller dependencies, using needs: and this.controllerFor and this.modelFor in order to have access to another controller/model's content but have been unable to wire these things together into something meaningful.
Anyway, here is what I've whittled my application code down to, in the hopes I might be able to stumble into the proper way of doing this...
Routes
App.Router.map(function() {
this.resource('post', { path: '/:post_id' }, function() {
this.resource('comments', { path: '/comments'} );
});
});
I removed all the other resources & routes, so I'm left with App.Post, App.PostIndex, and App.Comments. I think my routes are the issue here, I assume I'm not properly implementing the methods to use the loaded Post record in my Comments route.
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.Post.find();
},
setupController: function(controller, model) { // I'm not certain if this
controller.set('content', model); // setupController is needed?
}
});
App.PostRoute = Ember.Route.extend({
model: function(params) {
return App.Post.find(params.post_id);
},
setupcontroller: function( controller, model) { // again, unsure if this
this.controllerFor('post').get('comments'); // is correct.
controller.set('content', comments);
}
});
App.CommentsRoute = Ember.Route.extend({
afterModel: function() {
this.set('post', this.modelFor('post'));
},
setupcontroller: function( controller, model) {
this.controllerFor('post').get('comments');
controller.set('content', comments);
}
});
Controller
App.CommentsController = Ember.ArrayController.extend({
needs: "post",
actions: {
addComment: function() {
var post = App.Post.create({
title: 'static post title'
});
post.get('comments').create({
message: 'static message'
});
post.save();
}
}
});
This is my current Comments Controller, which can create a new Post with an embedded Comment. I've found and been given numerous examples in which to create the Comment, but none seem to work for me. Basically, I'm struggling with defining the var post = ... as the currently loaded record. I've implemented various approaches in an attempt at trial & error. Thus far I have attempted:
var post = App.Post.create();, returns property undefined, as this would create a new record. However, I gave it a shot as every example i saw related to this defined their record as such.
var post = this.get('post');, returns a cannot call 'get' on undefined. I've tried using this method of defining my current post on both the Comments controller and Post controller.
var post = this.get('controllers.post.content);, returns a 'cyclic error' from the backend I'm using.
var post = App.Post.find();, returns a cannot call 'get' on undefined.
var post = App.Post.find(1);, Again, returns a cannot call 'get' on undefined. Figured I'd give it a shot because this is one of those recurring examples people provide. The backend I use applies its own ID to each record, and I'm unsure if I would be able to/how to have the .find() method use a dynamic ID value and retrieve only the model I just loaded.
I'm guessing that I'm not properly setting up my Routes and Controller dependencies?
If anyone has a suggestion, relevant link, or fix I would be very grateful.
This one (seemingly simple) issue/use case has me at wit's end at this point.
Try this (works pre beta 2):
App.CommentsController = Ember.ArrayController.extend({
actions: {
addComment: function() {
this.content.createRecord({
message: 'static message'
});
}
}
});
Ember Data Beta 2 and later:
App.CommentsController = Ember.ArrayController.extend({
needs: ["post"],
actions: {
addComment: function() {
var post = this.get('controllers.post');
var comment = this.get('store').createRecord('comment', {
message: 'static message',
post: post
});
comment.save().then(function() {
post.addObject(comment);
// You may or may not need to save your post, too. In my case my backend handles
// the inverses of relationships (if existing), so there's no need. We still need
// to do this for Ember, though
});
}
}
});
I have a list of accounts and then i have a view link to view an account in detail and this is the account route. When i click the view link the (guid) doesnt update when going through the router, it only updates in the URL but it doesnt seem to be carrying through to the code.
When i do a browser refresh then the (guid) gets carried through to the router... Its not resolving for some reason.
Im not using ember-data but will use it in the future.
Here is my "Accounts" template code with the "View" link:
{{#each accountdata in controller}}
<tr>
<td>{{accountdata.accountnumber}}</td>
<td>{{accountdata.accountname}}</td>
<td>{{accountdata.accounttypestatus}}</td>
<td>{{accountdata.accountuser}}</td>
<td>{{#linkTo account accountdata}}View{{/linkTo}}</td>
</tr>
{{/each}}
accountdata is the context with the "accountguid" which is my id.
Here is my router:
App.Router.map(function() {
this.resource("accounts", { path: '/accounts' });
this.resource("account", { path: "/accounts/:accountguid" });
});
App.AccountsRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.set('searchfilter','ALL');
controller.search();
}
});
App.AccountRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.show(controller);
},
model: function(params) {
this.controllerFor('account').set('accountguid',params.accountguid);
},
serialize: function(model) {
return {accountguid: Em.get(model, 'accountguid')}
}
});
My controller.show is where i send the context to call a script to display the account details.
So i just need view to carry through the correct accountguid each time which it isnt and then to call the show(context) method.
Thanks
This is my old router code which worked 100%. When i clicked a link it resolved the :accountguid and when i did a browser refresh it did the same thing. i had no problems, everything just worked.
// //Accounts
// accounts: Ember.Route.extend({
// route: '/accounts',
// index: Ember.Route.extend({
// route: '/',
// connectOutlets: function (router) {
// router.get('applicationController').connectOutlet('accounts');
// router.get('accountsController').set('searchfilter','ALL');
// router.get('accountsController').search();
// }
// }),
// view: Ember.Route.extend({
// route: '/:accountguid',
// connectOutlets: function (router, account) {
// router.get('applicationController').connectOutlet('account', account);
// router.get('accountController').show(account);
// //router.get('accountController').connectOutlet('eventloghistory','eventloghistory');
// }
// })
// }),
I managed to solve my problem with the following code. I am now able to refresh the browser and i am able to click the link and it will carry through the current :accountguid in use to the show() method.
App.AccountRoute = Ember.Route.extend({
model: function(params) {
return {accountguid: params.accountguid};
},
setupController: function(controller, model) {
controller.show(model);
},
serialize: function(model) {
return {accountguid: Em.get(model, 'accountguid')}
}
});
Change your router map to the following and it should work
App.Router.map(function() {
this.resource("accounts", function(){
this.resource('account',{path:':account_id'});
});
});
Please, show us the code behind your AccountsController and AccountController. It would be most useful if you provide a jsfiddle with the whole construction.
In general, you may be unaware of the new flow of things. Here is what happens in the two scenarios:
1. You navigate to the AccountRoute by setting the URL (/account/5 for example).
1.1. the 'model' hook of the AccountRoute is called
model: function(params) {
return your model here...
}
with params = { accountguid: 5 }. Because you are not using ember-data, you should implement this hook and initialise and return the model there.
1.2. the setupController hook is called with the AccountController and the model returned by the model hook. Without the code behind
controller.show(controller);
It is not quite clear what its purpose is, but you should probably do something like
setupController: function(controller, model) {
this._super(controller, model);
controller.set('content', model);
controller.show(model);
}
As you can see, by not implementing the model hook, you URL stays correct, but the route does not know how to build the needed model resource.
You transition to the route via a linkTo call
{{#linkTo account accountdata}}View{{/linkTo}}
Here, linkTo expects accountdata to be the full account model for the route. Meaning it may not carry only partial data like id for example (read this for clearance: In latest Ember, how do you link to a route with just the id/name of a model, rather than providing all of its attributes in the linking page?).
2.1. The model hook of the AccountRoute is NOT called. The AccountRoute model property is set to the object that is passed to linkTo instead (in our case 'accountdata').
2.2. setupController is called with AccountController and the accountdata object.
If you accountdata object is not complete, it would be wise to create a complete instance here and set it to the controller.
As you can imagine, if you accountdata is like { id: 5, accountname: "John", accounttypestatus: "A", ...}, then after a click on linkTo, the URL will update correctly to /account/5, but the account template will receive accountdata, rather than an actual account.
P.P. If none of the above is any help, this might be your issue: https://github.com/emberjs/ember.js/issues/1709