Filter through a hasMany connection - ember.js

I have a route and a destination model and would like to create a filteredRoutes computed property which only contains routes which have a destination.name with the value of selectedDestination. I can't figure out the last puzzle piece to do that. How can I filter that?
controller.js
filteredRoutes: Ember.computed('model.routes', 'selectedDestination', function() {
var selectedDestination = this.get('selectedDestination');
var routes = this.get('model.routes');
if(selectedDestination) {
routes = routes.filter(function(route) {
// ????
});
}
}),
app/destination/model.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string')
});
app/route/model.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
destinations: DS.hasMany('destinations', { async: true })
});

Use filter, as you had suggested, with the filter condition being the presence of at least one destination whose name is equal to the selected one.
filteredRoutes: Ember.computed(
'model.routes.#each.destinations.#each.name',
'selectedDestination',
function() {
var selectedDestination = this.get('selectedDestination');
return this.get('model.routes') . filter(
route =>
route.get('destinations') . find(
destination =>
destination.get('name') === selectedDestination
)
);
}
)
In English:
Find the routes which have at least one destination whose name is the same as the selected destination.

Related

Slow Ember Computed Property

I've build a computed property inside my model to run a calculation on distance to a location using user location and point of interest location (in this case vineyards). The calculation seems to take a second and the template renders even when the milesAway attribute has not been set. Thus not rendering the pertinent information. Any ideas? Model code below...
import DS from 'ember-data';
import attr from 'ember-data/attr';
import { belongsTo, hasMany } from 'ember-data/relationships';
import EmberObject, { computed, observer } from '#ember/object';
import { inject as service } from '#ember/service';
export default DS.Model.extend({
featuredImages: hasMany('contentful-asset'),
featured: attr('boolean'),
name: attr('string'),
slug: attr('string'),
rating: attr('number'),
location: attr('string'),
latitude: attr('string'),
longitude: attr('string'),
bodyOne: attr('string'),
milesAway: attr('string', {async: false}),
googleMapsApi: Ember.inject.service(),
googleLocation: computed(function() {
let userLocation = 'Hard Coded Test Address';
let endLocation = this.get('location');
let milesAway = '';
let googleMapsApi = this.get('googleMapsApi');
this.get('googleMapsApi.google').then((google) => {
var self = this;
let distanceMatrixService = new google.maps.DistanceMatrixService();
function calculateDistance() {
distanceMatrixService.getDistanceMatrix({
origins: [userLocation],
destinations: [endLocation],
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.IMPERIAL,
avoidHighways: false,
avoidTolls: false
}, callback);
}
function callback(response, status) {
if (status != google.maps.DistanceMatrixStatus.OK) {
self.toggleProperty('errorState');
} else {
if (response.rows[0].elements[0].status === "ZERO_RESULTS") {
self.toggleProperty('errorState');
} else {
var distance = response.rows[0].elements[0].distance;
var distance_text = distance.text;
self.set('milesAway', distance_text);
}
}
}
calculateDistance();
});
})
});
A few things stand out...
Your computed property googleLocation is missing it's dependencies, which will prevent it being updated. At the very least, location should be listed.
Also, computed properties are meant to be 'pure functions', so that they have no side-effects whenever they are executed. So, instead of setting milesAway within the computed function, you should return a value from your computed function, which may as well be named milesAway. I can't comment too much on the distance calculation part as I've not done that yet myself.
You should remove this line from your code:
milesAway: attr('string', {async: false}),
and do something like the following structure:
milesAway: computed('location', function() {
let location = this.get('location');
// Your code to calculate the distance
return distance;
})
However, models are essentially intended for persistent data rather than transient application data. milesAway will vary per user (I see you have a hardcoded userLocation while you're working on this) for each vineyard that uses this model. So, you probably want to move this computed property out of your model and into your component or controller. So, in your component or controller, you'd have:
milesAway: computed('model.location', function() {
let location = this.get('model.location');
// Your code to calculate the distance
return distance;
})

Validating computed property in model

So far, I have an 'address' model set as the following:
import Ember from 'ember';
import DS from 'ember-data';
import { validator, buildValidations } from 'ember-cp-validations';
const { attr } = DS;
const { computed } = Ember;
const Validations = buildValidations({
streetName: validator('presence', true),
});
export default DS.Model.extend(Validations, {
/* Properties */
streetName: attr(),
streetNumberNum: attr('number'),
streetNumberAlpha: attr(),
addressDetails: attr(),
municipalityName: attr(),
zip: attr(),
/* computed */
streetNum: computed('streetNumberNum', 'streetNumberAlpha', function() {
return this.get('streetNumberNum') + '' + this.get('streetNumberAlpha');
})
});
I created the computed property streetNum so that I can use it in the template. The idea is to allow the user to input, in a field, his address street number (including an alpha) within a unique field (i.e. 1000a).
I would like to use the ember-cp-validations addon for validations purposes; and I was wondering how can I make ember understand this usecase.

Ember sort newly added data

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

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);
},
});

Filter within a date range

I have a cruise model which hasMany trips. A trip stores the start and end date. I'd like to filter cruises which start at or later than earliestStartDate and which end latests at latestEndDate.
app/models/cruise.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
trips: DS.hasMany('trip', { async: true })
});
app/models/trip.js
import DS from 'ember-data';
export default DS.Model.extend({
starts_on: DS.attr('date'),
ends_on: DS.attr('date')
});
app/templates/index.hbs
{{date-picker date=earliestStartDate valueFormat='YYYY-MM-DD'}
{{date-picker date=latestEndDate valueFormat='YYYY-MM-DD'}}
How can I change filteredCruises in this controller to filter the cruises which have trips within the given dates?
app/controllers/index.js
[...]
filteredCruises: function() {
var earliestStartDate = this.get('earliestStartDate');
var latestEndDate = this.get('latestEndDate');
var cruises = this.get('model.cruises');
return cruises;
}.property('model.cruises','earliestStartDate','latestEndDate'),
[...]
I haven't touched Ember in a while, and I haven't tested/executed this code, but I think this might work for you (and I am sure this can be optimized):
[...]
filteredCruises: function() {
var earliestStartDate = this.get('earliestStartDate');
var latestEndDate = this.get('latestEndDate');
var cruises = this.get('model.cruises');
return cruises.filter(function(cruise) {
return cruise.get('trips').filter(function(trip) {
return ((trip.get('starts_on') >= earliestStartDate) && (trip.get('ends_on') <= latestEndDate));
});
});
}.property('model.cruises','earliestStartDate','latestEndDate'),
[...]