Proper way to set multiple models on route; depending on user authentication? - ember.js

I'm currently working on an Ember app and it is coming along fine but since I am new to MVC applications in general there are a lot of concepts that don't come naturally to me.
I am currently trying to return two models for my index route. I referred to another SO question (EmberJS: How to load multiple models on the same route?) for the correct method and it has worked great.
My problem is now that I need to only set one of the two models only if the user is authenticated. I am using ember-simple-auth, and currently this is what I've got:
// app/routes/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
if (this.get('session.isAuthenticated')) {
var _this = this;
this.get('store').find('user', this.get('session.uid')).then(function(user) {
_this.set('model.entries', user.get('entries'));
});
}
return Ember.RSVP.hash({
newEntry: this.get('store').createRecord('entry', {
body: 'Write here ...'
})
});
}
});
For some reason, this does not work. After my route is loaded, the model only has the 'newEntry' property and not an 'entries' property, although the promise does get fulfilled (I put console.logs inside to prove it).
What could be happening? And is this the best way to accomplish this?

There is a set of data that you always want to load, for every user. Do that in the model hook, that is actually the data for the route.
There is another piece of info that you want to add only if a condition is met (authentication). Do that in the afterModel hook.
...is provided the route's resolved model...
http://emberjs.com/api/classes/Ember.Route.html#method_afterModel
So, now you can append or remove data from the model. Or take any relevant action depending on the data that you received.

Related

Ember list of model not reloaded without refreshing page

I'm displaying only published articles by sorting my list with the 'published' attribute.
Now when I edit an article and set it from 'published' to 'draft' and then I return to the list I see the 'draft' article even if I wrote a filter in my controller.
How i'm saving
article.set('isPublished', true);
article.save();
this.transitionToRoute('article.list');
Route :
model() {
return this.get('store').findAll('articles');
}
Controller :
articleSorted: computed.filterBy('model', 'isPublished', true),
Besides before I refresh the page some article are still 'draft' and when I refresh they are 'published'... Just going to another page and return to the list, or doing a browser refresh is enough to list properly only 'published' articles and so solve my problem.
Where am I suppose to look to solve my problem without refreshing ?
Thanks
I'm taking a best guess here based on your question and comments. Have full route and controller code would be helpful, so if this doesn't help I'll need that information.
Based on:
Just going to another page and return to the list, or doing a browser refresh is enough to list properly only 'published' articles and so solve my problem.
I would guess that there is an issue loading the articles or else the computed property is not being re-evaluated when isPublished changes.
I would try to load everything and filter it in a computed property. This might looks like:
import Route from '#ember/routing/route';
import { inject as service } from '#ember/service';
export default Route.extend({
store: service(),
model() {
return this.store.findAll('article');
}
});
import { computed } from '#ember/object';
import Controller from '#ember/controller';
export default Controller.extend({
articles: computed('model.#each.isPublished', function () {
return this.model.filterBy('isPublished');
}),
});
This will load all the articles in the model hook and then handle the filtering in a computed property. When the isPublished property changes on any one of the articles then the list should updated.
The reason for the delay in updating is probably due to the way you're saving the change. When running .save() it's an asynchronous operation that you need to wait on before transitioning. Try:
actions: {
async publishArticle(article){
article.set('isPublished', true);
await article.save();
this.transitionToRoute('article.list');
}
}
Which will wait for the promise to resolve first.
First try to set variable into the model , then make model.save(). like
article.set('name', 'draft');
article.save().then(transitionToarticle).catch(failure);; // => PATCH to '/article/id'

How does Ember Data write over instances of stored model data

Ok, so I'm new to Ember so bear with me.
I have an Ember application that is communicating with an API that does not adhere to JSONAPI standards, thus I have begun writing my own serializers in order to use Ember Data. However I am finding that when I make multiple requests to the same resource, the data is having trouble writing to the store. Consecutive requests to the same resource always responds with the following error:
TypeError: Cannot convert object to primitive value
Which from my limited understanding, implies that the data I am sending to the store is being treated like a string.
In my Application route I have written a findAll to my model 'listing-item' like so:
model: function() {
return this.store.findAll('listing-item');
},
In a nested 'user' route, when I do any type of request for the listing-item data that returns an array response (query, findAll) for the listing-item data, I get:
TypeError: Cannot convert object to primitive value
at EmptyObject.SETTER_FUNCTION [as title] (ember.debug.js:20672)
at assign (<anonymous>)
at InternalModel.setupData (internal-model.js:244)
at Class._load (store.js:1728)
at Class._pushInternalModel (store.js:2055)
at Class._push (store.js:1995)
at finders.js:141
at Backburner.run (ember.debug.js:720)
at Class._adapterRun (store.js:2253)
at finders.js:139
(Title is a field in my listing item model).
As I mentioned earlier, my API does not adhere to JSONAPI standards, so I've written a listing-item serializer like so:
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
normalizeArrayResponse(store, primaryModelClass, payload) {
payload.data = [];
payload.listing_item._data.forEach(this.formatListingItemArray, payload.data);
delete payload.listing_item;
return payload;
},
formatListingItemArray(listingItem) {
this.push({
type: "listing-item",
id: listingItem.id,
attributes: {
title: listingItem.title,
description: listingItem.description,
buy_now_price: listingItem.buy_now_price,
created_at: listingItem.created_at,
category_id: listingItem.category_id,
subcategory_id: listingItem.subcategory_id,
identity_id: listingItem.identity_id,
listing_number: listingItem.listing_number,
brand_new: listingItem.brand_new,
sold: listingItem.sold,
},
});
},
});
So I suppose my question is, what is Ember Data doing with my data object for this error to occur, and what might I be doing wrong in formatting my data for Ember data to consume.
UPDATES:
It appears as though only the top 3 fields are causing this error to occur. If I comment out the attributes 'title', 'description' and 'buy_now_price' in my serializer, I don't get this error. Also, it appears this only occurs when I navigate to the route, If I am in the /user route when the application loads, both requests work as expected.
Ok, so I've been crawling through ember-data code and found that in the internal-model.js file there is a setup function that looks at the current attributes in the store and compares them to the data being passed from the serializer. It then does an assign() to copy the serializers new data over to the stores object. However for some reason it seems that my stores object has a set of 'getter' and 'setter' function that come back from the store for the problematic fields (title, description and buy_now_price). What I need to know now is why are these functions coming along for the ride and what have I done to cause this?
Picture of getters/setters on ember-data object
Thanks in advance, let me know if there's any more information I need to provide in order to give better context.
So I found the solution to my problem for anyone that experiences a similar issue.
The symptom was as stated above, it seemed as though ember-data was returning getter and setter functions back from the store and having problems placing my new values in their stead. However the clue was that it was only the fields that I was rendering in my template.
The problem seems to be that I was using an Ember Concurrency task to perform the loading of my data, then passing the task straight in to an internal component from the model template. Something like this (not tested code):
The WRONG pattern (one I was using to experience the issue).
//route file - application.js
export default Ember.Route.extend({
model: function() {
return {
listings: this.get('getListings').perform(),
};
},
getListings: task(function * () {
return yield this.store.findAll('listing-item');
}),
})
//application template - application.hbs
{{listing-index listings=model.listings}}
//listing index component template.hbs
{{#if listings.value}}
{{#each listings.value.content as |listing|}}
{{listing._data.title}}
{{listing._data.description}}
{{listing._data.buy_now_price}}
{{/each}}
{{/if}}
The CORRECT pattern seems to be as thus:
//route file - application.js
export default Ember.Route.extend({
model: function() {
return {
listings: this.get('getListings').perform(),
};
},
getListings: task(function * () {
return yield this.store.findAll('listing-item');
}),
})
//application template - application.hbs
{{#if listings.value}}
{{listing-index listings=model.listings.value}}
{{/if}}
//listing index component template.hbs
{{#each listings as |listing|}}
{{listing.title}}
{{listing.description}}
{{listing.buy_now_price}}
{{/each}}
So the problem seems to lie in this pattern of passing the task rather than the content of the task in to my component. When I looped over the listings in the model template then passed the listings straight in to the component, it seemed to have solved my issues. I gather this is something to do with the use of these _data properties. A further explanation would be appreciated but i'll mark this as resolved for now.

Getting different url requests to API depending on how I've gotten to the page

I'm getting some curious behaviour that I can't figure out the reason for.
This is my router:
App.Router.map(function() {
this.resource('mapPieceSets', { path: '/map-pieces' }, function () {
this.resource('mapPieceSet', { path: '/:mapPieceSet_id' }, function () {
this.resource('mapPiece', { path: '/:mapPiece_id' });
});
});
});
I reload the app from the home page #/ then navigate down to the mapPiece route, I get these URLs requested:
[Domain]/api/mapPieceSets/
[Domain]/api/mapPieces/1/
[Domain]/api/mapPieces/2/
And it all works fine (mapPieceSets returns a list of mapPieceSet which have a list of mapPiece against them)
However, if I reload the whilst on a mapPiece routed page, then I get this URL:
[Domain]/api/mapPieceSets/
[Domain]/api/mapPieceSets/?mapPieceSet_id=1
[Domain]/api/mapPieces/?mapPiece_id=1
So switching from /:id= to ?id=, which isn't working on my end points (that's a side issue which I need to resolve), but just wondering why the URLs changed what they're requesting, and why we get a request to mapPieceSets/?mapPieceSet_id=1 when the whole of that object is returned within the response from mapPieceSets/
(If you need any other snippets of code from my app, let me know and I can put them in)
This is a fairly common confusion. When you're in your app navigating around you're often using a link-to which is then telling ember to use the specified model when visiting the route. When you're refreshing the page, Ember has to divine the models using the url /apimapPieceSets/3/2. At that point it will go to each route MapPieceSetsRoute, MapPieceSetRoute, and MapPieceRoute and hit each model hook passing in any associated params. So what you need to tell Ember how to do, is how to load a mapPieceSet, and mapPiece properly. You'll need to setup a model hook for both of those.
App.MapPieceSetsRoute = Em.Route.extend({
// I don't know if you're using Ember Data, but I'm going to assume you are
model: function(params){
return this.store.find('mapPieceSet', params.mapPieceSet_id);
}
});
From what you said, it sounds like the model is already available client side from the mapPieceSets. In that case, you can use the modelFor method to get a parent's route's model and get your model.
App.MapPieceSetsRoute = Em.Route.extend({
// I don't know if you're using Ember Data, but I'm going to assume you are
model: function(params){
return this.modelFor('mapPieceSets').get('properyWithMapPieces').findBy('id', params.mapPieceSet_id);
}
});

Ember.js After refresh page data disappear

Ive got little basic problem with ember.
Here is app: http://emberjs.jsbin.com/qivamuzu/5 (click test - working like a charm, because model is in memory - loaded in index page)
But when you try page #test directly http://emberjs.jsbin.com/qivamuzu/5#/test all the data disappear (and that's bad - does not fired index route and load model). I follow this question Why isn't my ember.js route model being called? but doesn't help me.
I need to use template with model in other templates - I use {{render index}} but I'm not sure what to use and how. Please help me I am stuck.
I'm a little unclear on exactly what you're trying to do.
If you're just trying to use the same model data with a different route (and template) then you can explicitly set the model data to be the same in the route definition:
App = Ember.Application.create();
App.Router.map(function() {
this.route('test');
});
var myModelData = ['red', 'yellow', 'blue'];
App.IndexRoute = Ember.Route.extend({
model : function(){
return myModelData;
}
});
App.TestRoute = Ember.Route.extend({
model : function(){
return myModelData;
}
});
Here's a working JSBin example:
http://emberjs.jsbin.com/qivamuzu/8/edit
EDIT: Additional Information That May Help
Okay, one more stab at it =) When you navigate directly to the test page there is no data because the TestRoute is using the model data from the IndexRoute which hasn't been loaded yet. What you can do is force the creation of the IndexController and model by initializing it from the ApplicationRoute which will always be invoked when you first go to any route in your application.
First you have to generate the controller since it doesn't exist yet.
this.generateController('index');
Then you can get the controller and set its model data:
this.controllerFor('index').set('model', ['red','green','blue']);
Here's a working fiddle and I actually tested it this time to make sure it works when you go straight to #/test. I removed the extra routes and things that aren't actually needed from my previous example.
http://emberjs.jsbin.com/qivamuzu/11#/test

How to structure a multi-record Ember app with named outlets?

I'm trying to build a Tweetdeck-like UI to arrange items from a central library into categories. I really need help wrapping my head around the canonical way of using Ember's router.
Essentially, I have a search UI, which allows the user to open zero or more categories simultaneously. The categories show a list of items, which the user can add to from a central library on the right. By completely ignoring the router and the URL, I have managed to hack together a semi-working proof of concept. Now I want to go back and try to do it the Ember way. Below is a high level sketch of what I am trying to accomplish:
If I understand correctly, the desired URL scheme would be a comma-separate list of model IDs that are currently open. I got a good idea of how to approach that from another question, How to design a router so URLs can load multiple models?.
Unfortunately, there are a few concepts I do not understand:
How do I construct my templates and router, such that the library is displayed with its own model and controller? I assume a named {{outlet}} is the way to go, but I am completely lost when it comes to the renderTemplate configuration. Or perhaps I should use {{render}} instead? In either case, I do not understand the router's role in this situation.
EDIT 1/28: I've added an updated fiddle that includes a standalone library route/template and documents my attempts to render it into the categories template. How does Ember expect me to give the library template its model when I try to embed it into another route? I've tried both {{outlet}} with renderTemplate and {{render}}, but in both cases, I am stuck when it comes to specifying the model.
Using renderTemplate:
App.CategoriesRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('categories');
this.render("library", {
into: "categories",
outlet: "library",
controller: "library",
});
},
});
When my controller receives a request to open a category, how do I communicate that to the router? How is the hash path updated? Who is responsible for loading the appropriate model(s)? I assume I should start with transitionTo or transitionToRoute, but I do not understand the router's role here either. Specific questions:
How do I de-serialize multiple, comma-separated models from the URL? Do I just split on the comma or is there a better way?
Once I get the IDs from the URL, how do I make my model hook return multiple records? Do I just shove them all into an Ember array?
When the controller gets the ID of a new record to open, how do I communicate that to the router?
I've tried to work this out on my own and have read the Ember documentation many times, but I am afraid it is simply over my head. I put together a minimal (currently non-functional) fiddle to outline my thoughts and point out where I am stuck. I would appreciate any help anyone could offer.
this.render does not accept a model parameter, but you could pass the model through the controller property instead, this makes sense to do since the Controller is really a proxy for the model at any rate
App.IndexRoute = Ember.Route.extend({
var self = this,
notesController = self.controllerFor('notes').set('content', self.store.find('notes'));
renderTemplate: function() {
this.render('notes', {
controller: notesController,
into: 'index',
outlet: 'notes'
});
}
});
You could also try something like this from this link.
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
books: this.store.findAll('book'),
genres: this.store.findAll('genre')
});
},
setupController: function(controller, model) {
controller.set('books', model.books);
controller.set('genres', model.genres);
}
});
Here, they load multiple models into one route using Ember.RSVP.hash and then using setupController they set each model (Rails: instance variable?) individually.
I'm assuming using this method that you could load as many models as you needed.