Filter models by related model (hasMany) - ember.js

I have a list of products with related tags. I want to filter the list to only show products that have the specified tag:
App.Product = DS.Model.extend({
tags: DS.hasMany('Tag', { async: true }),
name: DS.attr( 'string' )
});
App.Tag = DS.Model.extend({
name: DS.attr('string')
});
App.ProductsTaggedRoute = Ember.Route.extend({
model: function(params) {
var store = this.store;
return store.find('product').then(function() {
store.filter('product', function(product, index, enumerable) {
var match = false;
product.get('tags').then(function(tags) {
tags.forEach(function(tag) {
if(tag.get('name') === 'Tag 1') {
console.log(product.get('name') + ' true');
match = true;
} else {
console.log(product.get('name') + ' false', tag.get('name'));
}
});
});
return match;
});
});
}
});
App.Product.FIXTURES = [
{ id: 1, tags: [1,2,3], name: "test 1" },
{ id: 2, tags: [3], name: "test 2" },
{ id: 3, tags: [2,1], name: "test 3" },
{ id: 4, tags: [], name: "test 4" }
];
App.Tag.FIXTURES = [
{ id: 1, name: "Tag 1" },
{ id: 2, name: "Tag 2" },
{ id: 3, name: "Tag 3" },
{ id: 4, name: "Tag 4" }
];
The output is:
test 2 false undefined
test 3 false undefined
test 3 false undefined
test 1 true
test 1 false Tag 2
test 1 false Tag 3
I don't understand why the first three are undefined? Also, I don't get any output in the template so it seems like the filter function isn't right:
{{#each controller}}
{{ name }}
{{/each}}

You'll need to make sure the records are resolved before using the filter on it. This is a perfect use case for promises. You return a promise, and control what's resolved and when.
http://emberjs.jsbin.com/OxIDiVU/84/edit
model: function(params) {
var store = this.store;
return new Em.RSVP.Promise(function(resolve){
//find products
store.find('product').then(function(products) {
// get all the tag promises
var promiseArr = products.getEach('tags');
//wait on them
Em.RSVP.all(promiseArr).then(function() {
var filter = store.filter('product', function(product, index, enumerable) {
var match = false;
product.get('tags').forEach(function(tag) {
if(tag.get('name') === 'Tag 1') {
console.log(product.get('name') + ' true');
match = true;
} else {
console.log(product.get('name') + ' false', tag.get('name'));
}
});
return match;
}); //filter
resolve(filter);
}); // RSVP All
}); //find
}); // promise
}

Related

Ember sideload data not linked

I'm new to using Ember and was assigned to an ongoing project and need to resolve the following:
export default class OrderModel extends Model.extend(LoadableModel) {
#attr('string') status;
#attr('number') total;
#hasMany('order-item', { async: true }) orderItems;
}
export default class OrderItemModel extends Model.extend(LoadableModel) {
#attr('number', { defaultValue: 0 }) discount;
#attr('number', { defaultValue: 0 }) price;
#hasMany('item-fix', { async: false }) fixes;
}
export default class ItemFixModel extends Model.extend(LoadableModel) {
#attr('number', { defaultValue: 0 }) price;
}
and when I do let order = await this.store.findRecord('order', order_id, { reload: true });
the json response is:
data: {
type: "orders",
id: "1584",
attributes: {
status: "in_progress",
total: 1300
},
relationships: {
order-items: {
data: [
{
type: "order-items",
id: "1801
}
]
}
}
},
included: [
{
type: "order-items"
id: "1801",
attributes: {
discount: 0,
price: 1200
},
relationships: {
item-fixes: {
data: [
{
type: "item-fixes",
id: "335"
}
]
}
},
{
type: "item-fixes",
id: "335",
attributes: {
price: 100
}
}
]
but when I inspect the orderItem inside the order variable, the itemFixes are empty, but the is in the sideload of the response.
¿How can I link this nested relationship?
Also, here is the serializer.
export default DS.JSONAPISerializer.extend({
serialize(snapshot) {
let serialized = this._super(...arguments);
let { adapterOptions } = snapshot;
if (adapterOptions && adapterOptions.addPromotionCode) {
return { code: serialized.data.attributes.code }
}
serialized.included = A([]);
snapshot.eachRelationship((key, relationship) => {
if (relationship.kind === 'belongsTo') {
if (!isBlank(snapshot.belongsTo(key))) {
let node = snapshot.belongsTo(key).record.serialize({ includeId: true }).data;
delete node.relationships;
serialized.included.pushObject(node);
}
} else if (relationship.kind === 'hasMany') {
if (!isBlank(snapshot.hasMany(key))) {
snapshot.hasMany(key).forEach(ele => {
let node = ele.record.serialize({ includeId: true }).data;
delete node.relationships;
serialized.included.pushObject(node);
});
}
}
});
return serialized;
}
});
In your response the relationship name is item-fixes but in your model it's just fixes. It must be the same.

Can I pass an array of object to filterBy?

My ember version is 1.13 and I would like to ask if the line of code below is applicable to the version of my ember app above?
console.log(this.get('arrayOfObjects').filterBy('zoneName', ['zoneNameOne', 'zoneNameTwo'])); ?
Sample data of selectedZoneOrCityName is
selectedZoneOrCityName = ['zoneNameOne', 'zoneNameTwo'],
I want to use it something like these
if (selectedZoneOrCityName) {
return this.get('arrayOfObjects').filterBy('zoneName', selectedZoneOrCityName).mapBy('cityName');
} else {
console.log('reads nothing');
return [];
}
you can use simple filter like below code snippet.
var arrayOfObjects = [
{
id: 1,
name: 'one',
zoneName: 'zoneNameOne'
},
{
id: 2,
name: 'two',
zoneName: 'one zoneName'
},
{
id: 3,
name: 'three',
zoneName: 'zoneNameOne'
},
{
id: 4,
name: 'four',
zoneName: 'zoneNameTwo'
}
];
var selectedZoneOrCityName = ['zoneNameOne', 'zoneNameTwo'];
arrayOfObjects = arrayOfObjects.filter((item) => {
return selectedZoneOrCityName.includes(item.zoneName);
});
console.log('final filtered array : ', arrayOfObjects);
if you are usign filterBy then you have to chain filterBy for each array value.

get from model and then set a new property on it

I have a component:
App.MyChildComponent = Ember.Component.extend({
addTooltips: Ember.on('didInsertElement', function() {
var me = this;
var metrics = this.get('option.metrics');
metrics.forEach(function(e, i) {
me.get('option.metrics').objectAt(i - 1).set('tooltipDisabled', true);
});
});
})
Which is generated inside an each loop by a different component:
App.MyParentComponent = Ember.Component.extend({
...
})
And the template of MyParentComponent is:
{{#each option in options}}
{{my-child option=option}}
{{/each}}
All this, is called by a view with a template like this:
{{my-parent options=options}}
options is defined in the model of the view with:
App.MyViewModel = Ember.Object.extend({
options: Ember.A([
{ metrics: Ember.A([
{ name: 'a' },
{ name: 'b' },
{ name: 'c' }
]) },
{ metrics: Ember.A([
{ name: 'd' },
{ name: 'e' },
{ name: 'f' }
]) },
{ metrics: Ember.A([
{ name: 'g' },
{ name: 'h' },
{ name: 'i' }
]) }
]),
});
When I run me.get('option.metrics').objectAt(i - 1).set('tooltipDisabled', true); I get:
Uncaught TypeError: me.get(...).objectAt(...).set is not a function
What am I doing wrong?
Vanilla JavaScript objects don't have set methods. Use Ember.Objects instead:
App.MyViewModel = Ember.Object.extend({
options: Ember.A([
{ metrics: Ember.A([
Ember.Object.create({ name: 'a' }),
// ...
]) }
]),
});
Demo.

ember Uncaught Error: assertion failed: Emptying a view in the inBuffer state

I get this assertion when run the code below:
Emptying a view in the inBuffer state is not allowed and should not
happen under normal circumstances. Most likely there is a bug in your
application. This may be due to excessive property change
notifications.
Link to demo:
http://plnkr.co/edit/s3bUw4JFrJvsL690QUMi
var App = Ember.Application.create({
Store: DS.Store.extend({
revision: 4,
adapter: DS.FixtureAdapter.create()
}),
Router: Ember.Router.extend({
root: Ember.Route.extend({
index: Ember.Route.extend({
route: "/",
connectOutlets: function(router){
var person;
person = App.Person.find(657);
person.addObserver("isLoaded", function() {
return router.get('router.applicationController').connectOutlet("things", person.get("things"));
});
}
})
})
}),
ApplicationController: Em.Controller.extend(),
ApplicationView: Em.View.extend({
template: Em.Handlebars.compile("{{outlet}}")
}),
ThingsController: Em.ArrayController.extend({
thingTypes: (function() {
return App.ThingType.find();
}).property()
}),
ThingsView: Em.View.extend({
template: Em.Handlebars.compile([
'{{#each controller.thingTypes}}',
'{{this.name}}',
'{{/each}}',
'{{#each controller.content}}',
'{{this.title}}',
'{{/each}}'].join(""))
}),
//MODELS
Person: DS.Model.extend({
things: DS.hasMany('App.Thing', {
embedded: true
})
}),
Thing: DS.Model.extend({
description: DS.attr('string'),
thingType: DS.belongsTo("App.ThingType", {
embedded: true
}),
title: (function() {
return this.get("thingType.name");
}).property("description")
}),
ThingType: DS.Model.extend({
name: DS.attr("string")
})
});
App.Person.FIXTURES = [
{
id: 657,
things: [
{
id: 1,
description: "Some text",
thing_type: {
id: 1,
name: "type 1"
}
}, {
id: 2,
description: "Some text",
thing_type: {
id: 2,
name: "type 2"
}
}
]
}
];
App.ThingType.FIXTURES = [
{
id: 1,
name: "type 1"
}, {
id: 2,
name: "type 2"
}, {
id: 3,
name: "type 3"
}
];
Why is this happening?
I was having the same error while trying to load a list of dropdown values from fixtures. What resolved it was overriding queryFixtures on the fixture adapter:
App.FixtureAdapter = DS.FixtureAdapter.extend
latency: 200
queryFixtures: (records, query, type) ->
records.filter (record) ->
for key of query
continue unless query.hasOwnProperty(key)
value = query[key]
return false if record[key] isnt value
true
I probably wouldn't have figured it out had I not set the latency first. Then the error was a bit more descriptive.
a bit late I guess... but I got it to work here:
http://plnkr.co/edit/hDCT4Qy1h5aE6GjM76qp
Didn't change the logic but where its called
I modified your router like this:
Router: Ember.Router.extend({
root: Ember.Route.extend({
index: Ember.Route.extend({
route: "/",
connectOutlets: function(router) {
var person;
router.set('router.applicationController.currentPerson', App.Person.find(657));
}
})
})
})
And created an ApplicationController:
ApplicationController: Em.Controller.extend({
currentPerson: null,
currentPersonLoaded: function() {
this.connectOutlet("things", this.get("currentPerson.things"));
}.observes("currentPerson.isLoaded"),
})
I dont know if this is the output you wished but the bug vanished!

Sencha Touch 2 list/store

I have a problem with an list-component on the sencha touch 2 framework. My problem is, that my list doesn't show the calculated distance between the current position and the places.
First I use an navigation view with a list (name + distance) when the user click the name more details about them appears (+ the back-button will generated automatic). If the back-button is pressed the list shows the correct distance in the list. But I really need the distance is shown at the first time. I tried a lot but nothing helps.
I use a model and a store:
'Ext.define('Guide.store.ProjekteList', {
extend: 'Ext.data.Store',
config:{
model: "Guide.model.ProjekteList",
autoLoad:true,
sorters: ['distance'],
storeId: 'ProjekteList',
proxy: {
type: 'ajax',
url : 'PHP/get_MainList.php',
reader: {
type: 'json',
rootProperty:'items'
}
},
listeners: {
load : function(){
this.each(function(store){
var newData = getDis(store.data);
});//each
}//load func
}// listener
}//config
});// klasse
var getDis = function(dataset) {
var geo = Ext.create('Ext.util.Geolocation', {
autoUpdate: false,
listeners: {
locationupdate: function(geo) {
polat = geo.getLatitude();
polng = geo.getLongitude();
var B1 = dataset.Lat / 180 * Math.PI;
var B2 = polat / 180 * Math.PI;
var L1 = dataset.Lng / 180 * Math.PI;
var L2 = polng / 180 * Math.PI;
var zwi = Math.acos(Math.sin(B1)*Math.sin(B2) + Math.cos(B1)*Math.cos(B2)*Math.cos(L2-L1));
var r = 6378.137; //km
dataset.distance = r * zwi;
dataset.distance = Math.round(dataset.distance*100)/100;
},
locationerror: function(geo, bTimeout, bPermissionDenied, bLocationUnavailable, message) {
if(bTimeout){
alert('Timeout occurred.');
} else {
alert('Error occurred.');
}
}
}
});
geo.updateLocation();
return dataset;
};'
and my model:
'Ext.define('Guide.model.ProjekteList', {
extend: 'Ext.data.Model',
config: {
fields: ['Projektname', 'Lat', 'Lng', 'distance', 'ID'],
}
});
here is my navigation view:
'Ext.define('Guide.view.ProjekteList', {
extend: 'Ext.navigation.View',
xtype: 'projektelist',
config: {
title: 'Orte',
iconCls:'Projekte' ,
id: 'listButton',
items:[
{
xtype: 'list',
onItemDisclosure: true,
/* plugins: [
{
xclass: 'Ext.plugin.ListPaging',
autoPaging: false,
}
], */
title: 'Sehenswerte Orte',
store: 'ProjekteList',
itemId: 'liste',
itemTpl: '<h2>{Projektname}</h2> Entfernung: {distance} km',
listeners: {
show: function(){
this.refresh();
} //show function
}, //listeners
} // item
] //items
}// config
}); '
and my controller
Ext.define("Guide.controller.ProjekteList", {
extend: "Ext.app.Controller",
views: ['ProjektList'],
config: {
refs: {
projekt: 'projektelist',
},
control: {
'projektelist list': {
itemtap: 'showDetail'
}
}
},
showDetail: function(list, index, element, record) {
var projektid = record.get('ID');
Ext.StoreMgr.get('ProjektDetail').setProxy({url:'PHP/get_Detail.php?
ID='+projektid}).load();
this.getProjekt().push({
xtype: 'projektdetails',
});
} // showDetail function
});
Thanks in advance!
I found the solution:
only change the listener in the navigation view from "show" to "painted" and it works fine.