Ember sort newly added data - ember.js

I started working on a new ember project. I have never used ember before.
I am working with an api that does not conform with the JSON API spec and does not have a websocket.
So I poll the api to get the latest data.
I can get the latest data but it is rendered in the view on the bottom instead of the top. I have looked at 1, 2, 3 to no avail.
How do I get the new data to render at the top of the list?
//sample output after a new job fetched
2
1
3
//desired output
3
2
1
This is a new project so I don't want to use anything that will be depreciated in 2.0 (controllers, etc.). I am open to changing the model(s) if that works.
My route looks like this:
//routes query.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
var interval = 1000; // every second
Ember.run.later(this, function() {
this.model().then(function(json) {
this.controller.set('model', json);
}.bind(this));
}, interval);
return Ember.RSVP.hash({
query: this.store.find('query'),
job: this.store.peekAll('job')
});
},
});
My models are:
//models query.js
import DS from 'ember-data';
import Ember from 'ember';
export default DS.Model.extend({
jobs: DS.hasMany('job'),
count: DS.attr('number'),
jobQ: ['took'],
jobsSorted: Ember.computed.sort('jobs', 'jobQ'), <-- this doesn't seem to work
});
//models job.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
submitter: DS.attr('string'),
submission_time: DS.attr('date'), // '2015-04-27 15:14:55', // date?
completion_time: DS.attr('date'), // '2015-04-27 15:15:08',
took: DS.attr('number'),
statuses: DS.attr('string'), // object
query: DS.belongsTo('query'),
});
So my serializer is:
// serializers query.js
import DS from 'ember-data';
import assign from 'npm:object-assign';
export default DS.RESTSerializer.extend({
isNewSerializerAPI: true,
normalizeResponse: function (store, primaryModelClass, payload) {
// delete things we don't want
delete payload.error;
delete payload.status;
// populate array
var jobs = [],
relationships = [];
// copies the jobs to a new var keep the payload clean
Object.keys(payload.jobs).forEach((key) => {
let attributes = {};
assign(attributes, payload.jobs[key]);
delete attributes.id;
jobs.push({ id: key, type: 'job', attributes: attributes});
relationships.push({ id: key, type: 'job'});
});
var c = payload.count;
//jobs
// [{
// id:
// type:
// attributes: {
// name:
// filter:
// ...
// },
// ...
// }]
return {
data: {
id: 1,
type: 'query',
relationship: {
job: {
data: relationships
}
},
attributes: { count: c }
},
included: jobs
};
}
});

A template that looks like this would work just fine:
{{#each model.query as |query|}}
{{#each query.jobsSorted as |job|}}
{{job.took}}
{{/each}}
{{/each}}
What does your template look like? Although to sort it on a descending order you would need to add :desc to the sort order:
export default DS.Model.extend({
jobs: DS.hasMany('job'),
count: DS.attr('number'),
jobQ: ['took:desc'],
jobsSorted: Ember.computed.sort('jobs', 'jobQ')
});
Here's a JSFiddle demonstrating that Ember.computed.sort works like you're trying to use it: http://emberjs.jsbin.com/towerukafa/3/edit?html,css,js,output

Related

RangeError: Maximum call stack size exceeded when using ember-cli-mirage

I am having issue using ember-cli-mirage. My instance of mirage is configured to use the RESTSerializer and the EmbeddedRecordsMixin for relationships. I am able to get records with relationship without a problem, however when I save the parent record I get the following error coming from the ember-data code:
RangeError: Maximum call stack size exceeded
Weirdly enough, if I just removed the EmbeddedRecordsMixin everything works fine. Is there some restriction or special thing you need to do use the EmbeddedRecordsMixin with mirage
// models/artist.js
import DS from 'ember-data';
const { attr, hasMany, Model } = DS;
export default Model.extend({
name: attr('string'),
genre: attr('string'),
country: attr('string'),
bio: attr(),
albums: hasMany('album', {
async: false
}),
songs: hasMany('song', {
async: false
})
});
// serializers/artist.js
import DS from 'ember-data';
const { RESTSerializer } = DS;
export default RESTSerializer.extend(EmbeddedRecordsMixin, {
attrs: {
albums: {
embedded: 'always'
},
songs: {
embedded: 'always'
}
}
});
// mirage/models/artist.js
import { Model, hasMany } from 'ember-cli-mirage';
export default Model.extend({
albums: hasMany(),
songs: hasMany()
});
// mirage/factories/artist.js
import { Factory, faker } from 'ember-cli-mirage';
export default Factory.extend({
name() {
return faker.name.findName();
},
genre() {
return faker.hacker.noun();
},
country() {
return faker.address.country();
},
bio() {
return faker.lorem.sentence();
},
afterCreate(artist, server) {
server.createList('album', 3, { artist });
server.createList('song', 3, { artist });
}
});
// mirage/serializers/artist.js
import { RestSerializer } from 'ember-cli-mirage';;
export default RestSerializer.extend({
embed: true,
include: ['songs', 'albums']
});

Modifying model in Ember

I'm putting together an app that displays a list of stores (with add/edit/delete options), and clicking on a store name takes you to the list of items in that store (again with add/edit/delete).
The model:
// app/models/shop.js
import DS from 'ember-data';
export default DS.Model.extend({
shopName: DS.attr('string'),
shopDetails: DS.attr('string'),
shopStock: DS.attr('array', {
defaultValue() {
return [];
}
})
});
Basically model should be as:
{
"shopName": "someName",
"shopDetails": "someDetails",
"shopStock": [
{
"name": "foo",
"description": "bar",
"price": "555"
}
]
}
For each shop the route is dynamical:
// app.router.js
Router.map(function() {
this.route('shop', function() {
this.route('stock', { path: '/:shop_id/stock' });
this.route('edit', { path: '/:shop_id/edit' });
});
});
And in the controller I have:
actions: {
saveItem() {
const newItem = {
name: this.get('itemName'),
description: this.get('itemDescription'),
price: this.get('itemPrice')
};
}
}
The question is, how do I push the newItem object into model's shopStock array?
Since you want to create/edit/save/delete the related child records, you should create a new model for the child (shopStock) that belongsTo the parent (shop).
// app/models/shop-stock.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
price: DS.attr('string'),
shop: DS.belongsTo('shop')
})
});
Your shop model should also have another field added, shopStocks: DS.hasMany('shop-stock').
When you want to add child records to the parent, you will use the .pushObject() method. See the Model Relationships section of the Guides for more details.

EmberData peekRecord() method

The app I've been putting together looks like a list of stores (with add/edit/delete options), and clicking on a store name takes you to the list of items in that store (again with add/edit/delete).
The router:
Router.map(function() {
this.route('about');
this.route('stores');
this.route('shop', function() {
this.route('items', { path: '/:shop_id/items' });
this.route('edit', { path: '/:shop_id/edit' });
});
});
The model:
// app/models/shop.js
import DS from 'ember-data';
export default DS.Model.extend({
shopName: DS.attr(),
shopDetails: DS.attr(),
items: DS.hasMany('item')
});
and:
// app/models/item.js
import DS from 'ember-data';
export default DS.Model.extend({
itemName: DS.attr(),
itemDetails: DS.attr(),
itemPrice: DS.attr(),
parentShop: DS.belongsTo('shop')
});
Route for the page that displays the list of items is:
// app/routes/shop/items.js
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
return this.get('store').findRecord('shop', params.shop_id, {
include: 'items'
});
}
});
Now as far as I understand before adding an item to a shop I have to call peekRecord():
// app/controllers/shop/items.js
actions: {
saveItem() {
let currentShop = this.get('store').peekRecord('shop', /* shop id here */);
//rest of the code
}
}
from:
// app/templates/shop/items.hbs
<button type="submit" {{action 'saveItem'}}>Save</button>
The question is, how do I pass the shop id as the second argument for peekRecord()?
Figured it.
// app/templates/shop/items.hbs
<button type="submit" {{action 'saveItem' model}}>Save</button>
and
// app/controllers/shop/items.js
actions: {
saveItem(param) {
let currentShop = this.get('store').peekRecord('shop', param.id);
//rest of the code
}
}

Trouble generating a model instance and applying it to a specific key in Ember Mirage

While generating an instance of a staffMember within ember-cli-mirage I'm attempting to create a 'task' and assign it to the key 'tasksCreated' within the staffMember model. My current code is as follows;
It's creating a task, and creating a staffMember but the two have no relationships built between them.
app/models/staff-member.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
atWork: DS.attr('boolean'),
avatar: DS.attr(),
tasksCreated: DS.hasMany('task', {
inverse: 'creator'
}),
tasksAssigned: DS.hasMany('task', {
inverse: 'assignee'
})
});
app/models/tasks.js
import DS from 'ember-data';
export default DS.Model.extend({
creator: DS.belongsTo('staff-member', { inverse: null }),
assignee: DS.belongsTo('staff-member', { inverse: null }),
creationDate: DS.attr(),
description: DS.attr('string'),
urgency: DS.attr()
});
mirage/models/staff-member.js
import {Model, hasMany} from 'ember-cli-mirage';
export default Model.extend({
tasksCreated: hasMany('task'),
tasksAssigned: hasMany('task', { inverse: 'assignee'})
});
mirage/models/task.js
import {Model, belongsTo} from 'ember-cli-mirage';
export default Model.extend({
creator: belongsTo('staff-member'),
assignee: belongsTo('staff-member')
});
mirage/factories/staff-member.js
import { Factory, faker, trait } from 'ember-cli-mirage';
export default Factory.extend({
name: faker.name.firstName,
atWork: faker.random.boolean,
avatar: faker.image.avatar,
withTasks: trait({
afterCreate(staffMember, server){
server.createList('task',2,{tasksCreated: [staffMember]});
}
})
});
mirage/factories/task.js
import { Factory, faker } from 'ember-cli-mirage';
export default Factory.extend({
creationDate: faker.date.recent,
description: faker.lorem.sentence,
urgency: faker.random.number({
'min': 0,
'max': 4
})
});
mirage/scenarios/default.js
export default function(server ) {
server.createList('staff-member', 4, 'withTasks');
}
I tried to remove most of the code that wasn't directly related to setting the relationships up. Before fiddling with the factories, try to get the relationships working in just the default scenario.
I've made a working twiddle here: https://ember-twiddle.com/5bcdcdee50faa0c0a679c3c4d35fe0ea?openFiles=mirage.scenarios.default.js%2C
Here's what the default scenario looks like:
export default function(server ) {
let member1 = server.create('staff-member');
server.createList('task', 2, { creator: member1 });
server.create('task', { assignee: member1 });
let member2 = server.create('staff-member');
server.create('task', { creator: member2 });
server.createList('task', 3, { assignee: member2 });
}
As far as your factory afterCreate code goes, one error is in this line
server.createList('task',2,{tasksCreated: [staffMember]});
You're creating a task, but passing in tasksCreated as a property of that task. But tasks don't have a tasksCreated property, staffMembers do. Maybe something like this is what you're looking for:
server.createList('task', 2, { creator: staffMember });

Ember .save() only saving one attribute

I am trying to create and save a model in Ember but only the first entry in my form is saving leaving the others blank.
Before saving all of the models attributes are correct. (shown with logs)
Any help greatly appreciated!
My Model is:
import DS from 'ember-data';
export default DS.Model.extend({
endpoint: DS.attr('string'),
playerVersion: DS.attr('string'),
createdAt: DS.attr('date'),
updatedAt: DS.attr('date')
});
My Route is:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.createRecord('account');
}
});
My Controller is:
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
save: function() {
var _this = this;
var model = this.get('model');
console.log(model.get('endpoint')); //Shows correct endpoint
console.log(model.get('playerVersion')); //Shows correct playerVersion
model.save().then(function(account) {
console.log(model.get('endpoint')); //Shows correct endpoint
console.log(model.get('playerVersion')); //Shows nothing
_this.transitionToRoute('accounts.index');
});
return false;
}
}
});
UPDATE - Was some settings needed on the custom Serializer needed.
export default JsonApiSerializer.extend({
keyForAttribute: function(key) {
//return Ember.String.dasherize(key);
return Ember.String.camelize(key);
},
keyForRelationship: function(key) {
//return Ember.String.dasherize(key);
return Ember.String.camelize(key);
},
keyForSnapshot: function(snapshot) {
//return Ember.String.dasherize(snapshot.typeKey);
return Ember.String.camelize(snapshot.typeKey);
},
});