how to filter model to get data in realtime in emberfire - ember.js

so i have quota model like this :
export default DS.model.extend({
quota : DS.attr('number'),
sellerId: DS.attr('string'),
buyerId:DS.attr('string') });
and i have assignQuota routes with dynamic segment like this:
this.route('assignQuota', {path:'/assignQuota/:buyer_id'}
and in assignQuota.js :
export default Ember.Route.extend({
model(params) {
const sellerId = this.get("session").get("uid");
return this.store.query('quota',{
orderBy:'buyerId',
equalTo: params.buyer_id
}).then(function(quota){
return quota.filterBy('sellerId',sellerId)
});
}
});
and in my template (simplify) is like this:
{{#each model as |quota|}}
{{quota.quota}}
{{/each}}
it worked but if someone add data or delete data in quota model, the list didn't update automatically in the template.
The template only refresh after i refresh the browser. The funny thing is if I use ember inspector to inspect the data for quota, it shown that the model already changes if someone changes the model but the template didn't reflect the changes.
please help
thanks

The issue lies, how are you doing transitionTo to assignQuota route, If you are passing model to the dynamic segment,then it will skip calling the model hook and it will render same model data.

The reason is that the model does not observe changes.
Create a computed property and make it observer change changes of the model, and then using the computed value to create a list (your each loop).
quotaList: Ember.computed('model.[]', function() {
// Your update logic here
// return the new value
})

Related

EmberJS 2.7 = has_many configuration for Ember-Data and Active Model Serializers, using Ember-Power-Select (and side loaded, not embedded data)

This is a similar question to this one, except this is for the latest versions of Ember and Active Model Serializers (0.10.2).
I have a simple Parent:Child relationship.
app/models/trail.js
import Ember from 'ember';
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr(),
// relationships
employees: DS.hasMany('employee', { async: true }),
});
app/models/employee.js
import DS from 'ember-data';
import Person from '../models/person';
export default Person.extend({
status: DS.attr(),
statusCode: DS.attr(),
});
app/models/person.js
import Ember from 'ember';
import DS from 'ember-data';
export default DS.Model.extend({
avatarUrl: DS.attr(),
firstName: DS.attr(),
lastName: DS.attr(),
fullName: Ember.computed('firstName', 'lastName', function() {
return `${this.get('lastName')}, ${this.get('firstName')}`;
}),
});
When I create a new Trail, and select two employees for the 'hasMany', the following json arrives the server (from the Rails log):
{"data":
{"attributes":
{"name":"TEST3",
"gpx-file-url":"a url",
"distance-value":"5"},
"relationships":
{"employees":{"data":[]}}, "type":"trails"}}
My question is, what has happened to the employees? Where are the id's of the employees (they already exist both in the database and in the Ember Store - ie, I am not trying to create child records in this request).
EDIT
I just found this question, which explains that the id's for a hasMany relationship are not sent by Ember's JSONAPISerializer to the API - since the foreign key here actually has to be persisted in each child record. So essentially by 'selecting' employees, you need to save the fact that they now have a parent. So the selected employee records need to be persisted.
But my understanding was that this all works "out of the box" and that Ember would automatically fire a POST request to do this, but that seems to not be the case.
This then gets to the real question - how do I update those children?
UPDATE - BOUNTY ADDED AS THIS HAS QUESTION HAS EVOLVED
After further analysis, it became clear that a new model was required - Assignments. So now the problem is more complex.
Model structure is now this:
Trail
hasMany assignments
Employee
hasMany assignments
Assignment
belongsTo Trail
belongsTo Employee
In my 'new Trail' route, I use the fantastic ember-power-select to let the user select employees. On clicking 'save' I plan to iterate through the selected employees and then create the assignment records (and obviously save them, either before or after saving the Trail itself, not sure which is best yet).
The problem is still, however, that I don't know how to do that - how to get at the 'selected' employees and then iterate through them to create the assignments.
So, here is the relevant EPS usage in my template:
in /app/templates/trails/new.hbs
{{#power-select-multiple options=model.currentEmployees
searchPlaceholder="Type a name to search"
searchField="fullName"
selected=staff placeholder="Select team member(s)"
onchange=(route-action 'staffSelected') as |employee|
}}
<block here template to display various employee data, not just 'fullName'/>
{{/power-select-multiple}}
(route-action is a helper from Dockyard that just automatically sends the action to my route, works great)
Here is my model:
model: function () {
let myFilter = {};
myFilter.data = { filter: {status: [2,3] } }; // current employees
return Ember.RSVP.hash({
trail: this.store.createRecord('trail'),
currentEmployees: this.store.query('employee', myFilter).then(function(data) {return data}),
});
},
actions: {
staffSelected (employee) {
this.controller.get('staff').pushObject(employee);
console.log(this.controller.get('staff').length);
},
}
I only discovered today that we still need controllers, so this could be my problem! Here it is:
import Ember from 'ember';
export default Ember.Controller.extend({
staff: [] <- I guess this needs to be something more complicated
});
This works and I see one object is added to the array in the console. But then the EPS refuses to work because I get this error in the console:
trekclient.js:91 Uncaught TypeError: Cannot read property 'toString' of undefined(anonymous function) # trekclient.js:91ComputedPropertyPrototype.get # vendor.js:29285get #
etc....
Which is immediately follow by this:
vendor.js:16695 DEPRECATION: You modified (-join-classes (-normalize-class "concatenatedTriggerClasses" concatenatedTriggerClasses) "ember-view" "ember-basic-dropdown-trigger" (-normalize-class "inPlaceClass" inPlaceClass activeClass=undefined inactiveClass=undefined) (-normalize-class "hPositionClass" hPositionClass activeClass=undefined inactiveClass=undefined) (-normalize-class "vPositionClass" vPositionClass activeClass=undefined inactiveClass=undefined)) twice in a single render. This was unreliable in Ember 1.x and will be removed in Ember 3.0 [deprecation id: ember-views.render-double-modify]
So I imagine this is because the examples in the documentation just uses an array containing strings, not actual Ember.Objects. But I have no clue how to solve this.
So, I decided to throw away the controller (ha ha) and get creative.
What if I added a property to the Trail model? This property can basically be a 'dummy' property that collected the selected employees.
in /app/models/trail.js
selectedEmps: DS.hasMany('employee', async {false})
I set async to false since we will not persist them and before saving the new Trail I can just set this to null again.
in /app/templates/trails/new.js
{{#power-select-multiple options=model.currentEmployees
searchPlaceholder="Type a name to search"
searchField="fullName"
selected=model.selectedEmps placeholder="Select team member(s)"
onchange=(action (mut model.selectedEmps)) as |employee|
}}
<block here again/>
{{/power-select-multiple}}
This works, it doesn't 'blow up' after selecting the first employee. I can select multiple and delete them from the template. The control seems to work fine, as it is mutating 'model.selectedEmps' directly.
Now, I think this is a hack because I have two problems with it:
If I change the 'mut' to an action, so I can add further logic, I
cannot figure out how to access what is actually stored in the
propery 'model.selectedEmps'
Even if I can figure out (1) I will have to always make sure that
'selectedEmps' is emptied when leaving this route, otherwise the
next time this route is entered, it will remember what was
selected before (since they are now in the Ember.Store)
The fundamental issue is that I can live with 'mut' but still have the problem that when the user hits 'Save' I have to figure out which employees were selected, so I can create the assignments for them.
But I cannot figure out how to access what is selected. Maybe something this Spaghetti-Monster-awful mess:
save: function (newObj) {
console.log(newObj.get('selectedEmps'));
if (newObj.get('isValid')) {
let emp = this.get('store').createRecord('assignment', {
trail: newObj,
person: newObj.get('selectedEmps')[0]
})
newObj.save().then( function (newTrail) {
emp.save();
//newTrail.get('selectedEmps')
// this.transitionTo('trails');
console.log('DONE');
});
}
else {
alert("Not valid - please provide a name and a GPX file.");
}
},
So there are two problems to solve:
How to get the selected employees, iterate and create the
assignments.
How to then save the results to the API (JSON-API using Rails). I
presume that newObj.save and each assignment.save will take care
of that.
UPDATE
The developer of EPS kindly pointed out that the action handler receives an array, since I changed to using a multiple select, not a single select as it had been earlier. So the action is receiving the full array of what is currently selected. DOH!
I was thus able to update the action handler as follows, which now successfully stores the currently selected employees in the staff property of the controller. One step closer.
staffSelected(newList) {
existing.forEach(function(me){
if (!newList.includes(me)) {
existing.removeObject(me); // if I exist but the newList doesn't have me, remove me
}
});
newList.forEach(function(me){
if (!existing.includes(me)) {
existing.pushObject(me); // if I don't exist but the newList has me, add me
}
});
}
Perhaps not the best way to intersect 2 arrays but that's the least of my concerns at 4am on a Saturday night. :(
FINAL PROBLEM UPDATE - how to save the data?
Ok, so now that I can get the selected employees, I can create assignments, but still cannot figure out what Ember requires for me to save them, this save action throws an error:
save: function (newObject) {
if (newObject.get('isValid')) {
let theChosenOnes = this.controller.get('theChosenOnes');
let _store = this.get('store');
theChosenOnes.forEach(function (aChosenOne) {
_store.createRecord('assignment', {
trail: newObject,
person: aChosenOne,
});
});
newObject.save().then(function (newTrail) {
newTrail.get('assignments').save().then(function() {
console.log('DONE');
});
});
}
get(...).save is not a function
The problem with your final update is that in Ember Data 2.x, relationships are asynchronous by default, so what's returned from newTrail.get('assignments') is not a DS.ManyArray, which has a .save, but a PromiseArray, which doesn't have that.
You need a small tweak to do this instead, so you call .save on the resolved relationship:
newObject.save().then(function (newTrail) {
newTrail.get('assignments').then(assignments => assignments.save()).then(function() {
console.log('DONE');
});
});

Proper way to set multiple models on route; depending on user authentication?

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.

Ember Model with only One Instance

I'm trying to set up a shopping bag model in Ember. The shopping bag will be created on load of the page and saved in LocalStorage using the LocalStorage adapter. At any given time, there should only be one instance of a bag saved, as a user only needs to add products to one shopping bag. My question is this: it seems that I'm being hack-y with my methods of getting and setting data on my bag as Ember data caters to models with more than one instance. Is there a better way to structure/define my bag model that is better suited for one-instance models? Here's my model:
import DS from 'ember-data';
export default DS.Model.extend({
products: DS.hasMany('product', {async: true}),
productCount: function() {
return this.get('products.length');
}.property('products.length')
});
When I want to get the productCount in my template, the only way I can seem to get it to print is use an {{#each}} statement with {{productCount}} nested inside. As there is only one bag, this seems inefficient. In other parts of my code, I need to get the current instance of the bag and act on it. To get this to work, I'm finding all bags, then getting the firstObject, which also seems hack-y:
import Ember from "ember";
export default Ember.ArrayController.extend({
actions: {
addToBag: function(model) {
this.store.find('bag').then(function(bags) {
var bag = bags.get('firstObject');
bag.get('products').then(function(products) {
products.pushObject(model);
bag.save();
});
});
}
}
});
My application route uses the bag as its model, and sets up the controller:
import Ember from "ember";
var ApplicationRoute = Ember.Route.extend({
activate: function() {
var store = this.store;
store.find('bag').then(function(bags) {
var existing_bag = bags.get('firstObject');
// If there isn't already a bag instantiated, make one and save it
if(typeof existing_bag === 'undefined') {
var new_bag = store.createRecord('bag');
new_bag.save();
}
});
},
model: function() {
return this.store.find('bag');
},
setupController: function(controller,model) {
controller.set('content', model);
}
});
Any ideas here to make this more efficient? I don't want this to fester into code that is messy. Thanks so much in advance!
If that is your BagController above, it should be an ObjectController instead.
The reason why you're having to get the first object is because find fetches all items of that model type in your store. You may only have one, but find doesn't know that unless you provide an id and if this bag hasn't been stored in your database, it may not have one yet.
Instead of fetching the bag model from the store, I would link you Bag and Products controllers with needs, then simply access the model property of that controller. You can even set up an alias to be able to access it quickly.
For example:
import Ember from "ember";
export default Ember.ArrayController.extend({
needs: 'bag',
bag: Ember.computed.alias("controllers.bag.model").
actions: {
addToBag: function(model) {
this.get('bag').get('products').pushObject(model);
}
}
});
The ApplicationRoute is only fired once, when you're app first boots up so you don't need to check if there's already a bag model present. The only one that will be there is the one you create. You should do this in the model hook. You don't need to set content as the model. It'll be hooked up like that by default.
import Ember from "ember";
export default Ember.Route.extend({
model:function() {
return this.store.createRecord('bag');
}
});
If you may want the ability to have multiple shopping bags going forward you could try using the 'singleton' approach Discourse follows: https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/mixins/singleton.js
Basically it adds 'current' property that you can use as your single instance throughout your code.
However, if you won't have a need to have multiple instances this may not be the best choice.

Getting model data from controller

In Ember if I have a model that is a list of users, if in the UsersController I do:
users = this.get('model');
users.map(function(user) {
user.name
});
should it not resolve the promise and return the user records? I'm confused on why this is not working for me, or how to get the model data the correct way. Thank you in advance.
The model promise is resolved by the router. Ember, by default, sets the controller's content property as the route's model unless you override the route's setupController() method. Your issue lies in the formatting of the map function.
It seems like you're using an array controller, because the model is an array, so do the following:
App.UsersController = Em.ArrayController.extend({
users: function() {
return this.map(function(user) {
return user.get('name');
});
}.property('#each.user'),
});
You can make this code even more streamlined by using Em.Array's mapBy() method, as follows:
App.UsersController = Em.ArrayController.extend({
users: function() {
return this.mapBy('name');
}.property('#each.user'),
});
If you're using the list of users in your template you can do this easily with a {{#each users}} helper. However, if you're using this list for other properties in the controller, be sure to to use the right observer to watch for items being added to the array:
someOtherProperty: function() {
var users = this.get('users');
// Do stuff with users array here...
}.observes('users.[]')
See setting up a route's model and setting up a controller if you're unfamiliar with how the models stuff works.

Ember-Data parent template not updating when model is changed

I am using EAK with a simple nested routing structure, but changing the parent model in the child controller does not change the top-level view. For example if I have the following router.js file:
this.resource('similar', function() {
this.resource('list', { path: '/list/:phone_id' });
this.resource('upload');
this.resource('new');
});
For the 'similar' route model I am using ember-data together with the RESTAdapter which is backed Flask.
export default Ember.Route.extend({
model: function() {
return this.store.find('phone');
}
});
If I manipulate the model inside the 'upload' controller then the changes are not reflected in the template, e.g.
var record = this.store.createRecord('phone', {
numbers: [1,2,3,4]
});
record.save();
will not change "{{#each list in model}} {{list.numbers}} {{/each}}".
If I reload the page it works fine. What am I doing wrong?
Instead of store.find which hits the server and stores that exact list, try using store.filter. store.filter "remains up to date as new records are loaded into the store or created locally"
http://emberjs.com/api/data/classes/DS.Store.html#method_filter
The problem here was that the REST endpoint did not return the new record (with the id attribute set).