Ember.js length of array of related objects - javascript

Im using Ember Data and Ember CLI. I have a simple one-to-many relationship between two models. I'm trying to create a computed property that returns the number of items that are attached to the current model.
models/account.js
// Account model
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
notes: DS.hasMany('note', { async: true })
});
models/note.js
// Note model
import DS from 'ember-data';
export default DS.Model.extend({
body: DS.attr('string'),
date: DS.attr('number'), // unix timestamp
account: DS.belongsTo('account', { async: true })
});
controllers/account/index.js
// account/index controller
import Ember from 'ember';
export default Ember.ObjectController.extend({
oldNotesCount: function() {
var notes = this.get('notes');
console.log('=-=', notes.length); // undefined *****
return notes.length;
}.property('notes.#each')
});
How come notes.length is undefined?
I've simplified this example... I can't use {{notes.length}} in my situation as there will be more calculations going on—this is just the first step.

You marked notes association as async so this.get('notes') returns a promise. Promises do not have length property hence getting undefined.
To get data in an async association call then on returned promise with a function as argument. Association data will be passed to that function as first argument.
export default Ember.ObjectController.extend({
oldNotesCount: function() {
var _this = this;
this.get('notes').then(function(notes){
_this.set('oldNotesCount', notes.get('length');
});
return null;
}.property('notes.#each')
});

Related

ember js params is exists

So I need to do so that clicking on the store would stop the page with the goods of this store. When passing the store's id through the link URL changes but in the product's route model(params) the params is empty
Model name (product?)
import DS from 'ember-data';
import { empty } from '#ember/object/computed';
export default DS.Model.extend({
name: DS.attr('string'),
quantity: DS.attr('string'),
price: DS.attr('string'),
shops: DS.belongsTo('shop', {asynq: true}),
isNotValid: empty('name'),
});
Model name (shop?)
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
address: DS.attr('string'),
phone: DS.attr('string'),
products: DS.hasMany('product',{asynq: true}),
});
Controller name (?)
import Controller from '#ember/controller';
export default Controller.extend({
isNew: false,
actions: {
newProduct() {
this.toggleProperty('isNew');
},
cancelNewProducts() {
this.set('isNew', false);
},
addNewProduct() {
const name = this.get('name');
const quantity = this.get('quantity');
const price = this.get('price');
let shop = this.get('store').peekRecord('shop', );
let product = this.get('store').createRecord('product', { name,quantity,price });
shop.get('products').pushObject(product);
product.save().then( function() {
shop.save();
});
product.save().then( ()=> this.set('isNew',false));
},
},
});
Route name (?)
import Route from '#ember/routing/route';
export default Route.extend({
model(params) {
console.log(params.shop_id);
return this.store.query('product', {shops:params.shop_id});
},
actions: {
deleteProduct(product) {
let confirmation = confirm('Are you sure?');
if (confirmation) {
product.destroyRecord();
}
},
editProducts(product) {
console.log(id)
product.set('isEditing', true);
},
cancelProductsEdit(product) {
product.set('isEditing', false);
product.rollbackAttributes();
},
saveProducts(product) {
if (product.get('isNotValid')) {
return;
}
product.set('isEditing', false);
product.save();
},
},
});
Router
import EmberRouter from '#ember/routing/router';
import config from './config/environment';
const Router = EmberRouter.extend({
location: config.locationType,
rootURL: config.rootURL
});
Router.map(function() {
this.route('shops', function() {
this.route('new');
this.route('edit', { path: '/:shop_id/edit' });
});
this.route('products', { path: '/:shop_id/products' }); // this should be in the map, right?
}); // ?
export default Router;
My problem is that I can not make a request to the server and get the goods only from the store with the available id.But when I click on any store displays all the goods. AND i can't access params(shop_id) from shops in my controller,
I just did a pretty invasive reformatting of your program. Take note of the router. Looks like there are some mistakes in there. Also, you don't provide an id to 'peek' with. In your controller. We also don't know how those files are connected or what their names are. You should edit on top of my formatting to help clarify. {async: true} is spelled with a q in your program.
As far as your question... I'll try and reword it. The word 'store' is confusing... because of the data 'store' that we are used to talking about. I think you mean that you have a 'shop' resource and that you want to 'click' on the shop... (likely a component in an each loop) and then you say you want to 'stop' the page with the goods(products) - so... maybe you mean 'stock' or 'show' the products for that shop. This could be in the component (if only a few) - or you could shoot over to a 'detail' page for the shop - that displayed all of the products for that shop. Because you mention params and ID, I think you mean to go the detail route.
This is my best guess at your question: "I have resources for 'shop' and 'product.' I'm building a UI where shops are listed. I would like to make the shops clickable and when clicked, transition to the shop detail page - where I can list all associated products. My link-to helper(not shown here) takes in a shop ID - but the transition is not successful and the params isn't recognized. What am I doing wrong?"
For this question, you could likely create a more simplified version in an ember-twiddle to get to the bottom of things. We don't really need most of those actions to get to the source of the confusion.
It's admitedly hard to show these things / when you have a server - or a mirage server or whatever your setup is. Here's an example of the routing I would suggest - with some basic dummy data - in an embertwiddle. The data isn't real ember objects / but see the link-to and the shop detail route for what you'd likely use. Good luck!
Other notes:
ember-data uses an attribute called isNew for records - so, you may want to think of a different name for what you're doing /

Ember js service and filterBy

i have a service to manage all the errors and alerts in my app. and the code looks like this
Service
import Ember from 'ember';
export default Ember.Service.extend({
messages: null,
init() {
this._super(...arguments);
this.set('messages', []);
},
add: function (severity, msg, messageType) {
if (severity === 'error') {severity = 'danger';}
var msgObject ={
severity: severity,
messageType: messageType,
msg: msg,
msgId: new Date()
};
this.get('messages').pushObject(msgObject);
},
remove(msgId) {
this.get('messages').removeObject(msgId);
},
empty() {
this.get('messages').clear();
}
});
Component
import Ember from 'ember';
export default Ember.Component.extend({
messageType:'global',
messageHandler: Ember.inject.service(),
messages: function(){
return this.get('messageHandler.messages').filterBy('messageType',this.get('messageType'));
}.property('messageHandler.messages'),
actions : {
dismissAllAlerts: function(){
this.get('messageHandler').empty();
},
dismissAlert: function(msgId){
this.get('messageHandler').remove(msgId);
}
}
});
Initializer
export function initialize(container, application) {
application.inject('component', 'messageHandler', 'service:message-handler');
}
export default {
name: 'message-handler',
initialize : initialize
};
Template
import Ember from 'ember';
export default Ember.Component.extend({
messageType:'global',
messageHandler: Ember.inject.service(),
messages: function(){
return this.get('messageHandler.messages');
}.property('messageHandler.messages'),
actions : {
dismissAllAlerts: function(){
this.get('messageHandler').empty();
},
dismissAlert: function(msgId){
this.get('messageHandler').remove(msgId);
}
}
});
and whenever there is an error i will add it like this
this.get('messageHandler').add('error',"Unable to get ossoi details","global");
my problem is the filterBy in the component is not working. if i remove the filterBy() it works and i can see the error in the template. am kinda new to ember so if anyone can help me figure out what am missing here or if there is a better way of doing this please let me know
filterBy usage is good and it should be working well. but messages computed property will not be recomputed whenever you add/remove item from messageHandler.messages.
messages: Ember.computed('messageHandler.messages.[]', function() {
return this.get('messageHandler.messages').filterBy('messageType', this.get('messageType'));
}),
In the above code I used messageHandler.messages.[] as dependant key for the messages computed property so that it will be called for add/remove items.
Refer:https://guides.emberjs.com/v2.13.0/object-model/computed-properties-and-aggregate-data/
Computed properties dependent on an array using the [] key will only
update if items are added to or removed from the array, or if the
array property is set to a different array.

Define custom root json node on ember serializer

I'm trying to get brands items from my REST API with ember; but my API response does not match with ember-data expected. for example:
My model:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
isActive: DS.attr('boolean')
});
My API url: http://localhost:3000/api/brands
its response with:
{"success":true,
"data":[
{"id":1,"name":"Mine","isActive":true,"createdAt":"2017-04-23T20:36:49.000Z","updatedAt":"2017-04-23T20:44:32.000Z"},
{"id":2,"name":"forever","isActive":true,"createdAt":"2017-04-23T20:41:14.000Z","updatedAt":"2017-04-23T20:43:57.000Z"}
]
}
but, Ember is expecting some like this:
"brands": [{
"id": 1,
"name": "foo",
"isActive": "foo"
}]
I'm trying to change the root json node in serializer called brand.js, but I can not make it work. :(
here my serializer/brand.js
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
});
and my adapters/application.js
import DS from 'ember-data';
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';
import config from '../config/environment';
export default DS.RESTAdapter.extend(DataAdapterMixin, {
host: `${config.host}`,
namespace: `${config.namespace}`,
authorizer: 'authorizer:custom'
});
on browser console this message appears:
WARNING: Encountered "success" in payload, but no model was found for model name "success" (resolved model name using vanely-web#serializer:brand:.modelNameFromPayloadKey("success"))
WARNING: Encountered "data" in payload, but no model was found for model name "datum" (resolved model name using vanely-web#serializer:brand:.modelNameFromPayloadKey("data"))
How can I say to ember where the correct data is?.Some help is appreciated.
Sorry if my English is not well.
As you already did, you can override the RESTSerializer for every model.
What you want to achieve is response normalization. You can normalize your response by overriding normalizeResponse in your serializer (see the docs):
import Ember from 'ember';
import DS from 'ember-data';
const {
RESTSerializer
} = DS;
const {
get
} = Ember;
export default RESTSerializer.extend({
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
// Only do normalization for reading:
if(requestType !== 'createRecord' && requestType !== 'updateRecord' && requestType !== 'deleteRecord') {
// Do your normalization here. For example (not tested):
payload = {
brands: get(payload, 'data')
};
}
return this._super(store, primaryModelClass, payload, id, requestType);
}
});
Instead of overriding normalizeResponse, you could also override the other normalization methods.

How do I fetch Ember-Data Values in an ArrayController using Ember-CLI?

I'm new to Ember.js and Ember-cli and have this rather basic problem of not being able to fetch a specific model value in my ArrayController and manipulate it here. I read several times through the ember.js doc about Controller Setup and Display of Models
http://emberjs.com/guides/controllers/representing-multiple-models-with-arraycontroller/
but didn't find a solution to my case yet.
Here are my route, controller and model files:
// /routes/sequences.js
import Ember from 'ember';
var SequencesRoute = Ember.Route.extend({
model: function() {
return this.store.find('sequences');
},
setupController: function(controller, model) {
this._super(controller, model);
}
});
export default SequencesRoute;
// /controllers/sequences.js
import Ember from 'ember';
var SequencesController = Ember.ArrayController.extend({
init: function() {
this._super();
this.typeSeq();
},
i: 0,
seqData: function(){
var seq = this.get('model.nts');
return seq;
}.property('model.nts'),
len: function(){
var slen = this.get('seqData');
return slen.length;
}.property('seqData'),
typeSeq: function() {
var tthis = this;
var sequence = this.get('seqData');
var lenn = this.get('len');
if (tthis.i <= lenn){
console.log(tthis.i);
console.log(sequence.substring(tthis.i,tthis.i+1));
tthis.i++;
Ember.run.later(function(){
tthis.typeSeq();
}, 200
);
}
else{
return false;
}
}
});
export default SequencesController;
// /models/sequences.js
import DS from 'ember-data';
var Genes = DS.Model.extend({
geneName: DS.attr('string'),
nts: DS.attr('string')
});
Genes.reopenClass({
FIXTURES: [
{
id: 1,
geneName: "Monoamine Oxidase A",
nts: "CTGCAGCGAGCGCGGGAAGCGGGACAGGGCCTAGAGTCACTTCTCCCCGCCCCTGACTGGCCCG"
},
{
id: 2,
geneName: "Monoamine Oxidase B",
nts: "TTTCTGCAGCGAGCGCGGGAAGCGGGACAGGGCCTAGAGTCACTTCTCCCCGC"
}
]
});
export default Genes;
Especially setting the property for seqData in my controller is not clear to me. What has exactly to be called to get my model data into the property? Will the setupController method in my router set a model property in my ArrayController embermagically, so I can fetch it via model.nts ? Right now, my ember app will build, but the var sequence, which is set in the typeSeq function, remains undefined.
Would be great to hear some answers from you or grab a link for a nice tutorial regarding this topic!
1) sequence should be singular I believe, but maybe the other way works?
return this.store.find('sequence');
instead of
return this.store.find('sequences');
2) no need to do all that init/seqData stuff, the content property of the array controller will automatically be set to this.store.find('sequence'); from your route model hook, you can just do this
len: function(){
var length = [];
this.get('content').forEach(function(item) {
length.push(item.get('nts').length);
});
return length;
}.property('content'),

RESTAdapter in EmberJS

I am new to EmberJs and I don't clearly understand in Ember's Adapter .I just try the ember adapter in my App.Js like that and I got this error ( Assertion failed: You tried to set adapter property to an instance of DS.Adapter, where it should be a name or a factory ) . My ember's code in App.js is :
//Store
App.Adapter = DS.RESTAdapter.extend();
App.Store = DS.Store.extend({
revision: 12,
adapter: App.Adapter.create()
});
//Models
App.Product = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
price: DS.attr('number')
});
// Products Route
App.ProductsRoute = Ember.Route.extend({
model: (function() {
return this.store.find('Product');
})
});
return App;
I think you misunderstand the way you setup and configure adapters.
//
// Application-wide adapter, everything will use this unless you override it
//
App.ApplicationAdapter = DS.RESTAdapter.extend({
host: 'https://api.example.com'
});
//
// Product model, will use ApplicationAdapter
//
App.Product = DS.Model.extend({
name : DS.attr('string'),
description : DS.attr('string'),
price : DS.attr('number')
});
//
// Invoice model, will use fixtures, so specify a different adapter
//
App.InvoiceAdapter = DS.FixtureAdapter.extend({ /* options */ });
App.Invoice = DS.Model.extend({
name : DS.attr('string'),
amount : DS.attr('number')
});
//
// Routes, these should work as expected
//
App.ProductRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('product', params.id);
}
});
App.ProductsRoute = Ember.Route.extend({
model: function() {
return this.store.find('product');
}
});
App.InvoicesRoute = Ember.Route.extend({
model: function() {
return this.store.find('invoice');
}
});
return App;
Ember will know which models/routes/etc to use based on their names - see http://emberjs.com/guides/concepts/naming-conventions/ for the details.
Define the store in this way
App.Store = DS.Store.extend({
revision: 12,
adapter: App.Adapter
});
Without the create().
The main use of the adapter is to do the serialization and deserailzation of the data according to some conventions like constructing the url to post or get data from and then constructing the actual objects from the response. The Default adapter used by ember data models is the Rest adapter.
see
http://emberjs.com/guides/models/the-rest-adapter/
for more details
To use a different adapter other than the rest adapter you can specify its name like
Storm.Store = DS.Store.extend({
adapter: '_ams',
});
Try:
App.ApplicationAdapter = DS.RESTAdapter.extend();
This works for me

Categories