Filter within a date range - ember.js

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'),
[...]

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.

Filter through a hasMany connection

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.

Ember Services - Create default settings for application

My app has a lot of reports, and I have dateBegin and dateEnd in all of then.
The desired behaviour is:
app first load, dateBegin = month begin (jun-01) / dateEnd = today (jun-11)
when user change dates (let's say to mai-01 / mai-31), all controllers get the new dates
The code that I have now:
// app/services/defaults.js
import Ember from 'ember';
export default Ember.Service.extend({
init: function () {
this._super();
var dateEnd = moment().format('YYYY-MM-DD');
var dateBegin = moment().startOf('month').format('YYYY-MM-DD'));
if (!this.get('dateEnd')) { this.set('dateEnd', dateEnd); }
if (!this.get('dateBegin')) { this.set('dateBegin', dateBegin }
}
});
// app/components/select-dates-in-reports.js
import Ember from 'ember';
export default Ember.Component.extend({
defaults: Ember.inject.service(),
displayDateBegin: null,
displayDateEnd: null,
dateBegin: Ember.computed.alias('defaults.dateBegin'),
dateEnd: Ember.computed.alias('defaults.dateEnd'),
setInitialParams: Ember.on('init', function () {
this.set('displayDateBegin', this.get('dateBegin'));
this.set('displayDateEnd', this.get('dateEnd'));
}),
actions: {
chooseParams: function () {
this.set('dateBegin', this.get('displayDateBegin'));
this.set('dateEnd', this.get('displayDateEnd'));
}
}
});
// app/mixins/query-params-for-reports.js
import Ember from 'ember';
export default Ember.Mixin.create({
queryParams: ['dateBegin', 'dateEnd'],
defaults: Ember.inject.service(),
dateBegin: Ember.computed.alias('defaults.dateBegin'),
dateEnd: Ember.computed.alias('defaults.dateEnd')
});
// app/mixins/routes-query-params-for-reports.js
import Ember from 'ember';
export default Ember.Mixin.create({
queryParams: {
dateBegin: {
refreshModel: true
},
dateEnd: {
refreshModel: true
}
},
model: function(params) {
return this.store.find(this.get('modelName'),
{
dateBegin: params.dateBegin,
dateEnd: params.dateEnd
}
);
}
});
It works as desired just after each controller is initialized:
user enter the app, and visit controller1. dateBegin = jun-01 / dateEnd = jun-11
on the same controller1, user change dates to dateBegin = mai-01 / dateEnd = mai-31
user visit controller2. Here is the Problem. The dates are set to dateBegin = jun-01 / dateEnd = jun-11
on the same controller2, user change dates to dateBegin = apr-01 / dateEnd = apr-30
user visit controller1 again. Now it works. The dates are set to dateBegin = apr-01 / dateEnd = apr-30
I tried all I could find over the net.
Create Initializers, used localStorage, etc. Nothing works.
Can anyone helps me??
thanks!
Ember Dependency Injection to the rescue!
You can create a singleton object to hold the dates:
App.SelectedDates = Ember.Object.extend({
dateBegin: null,
dateEnd: null // or whatever value you want...
});
Then, inject that object into ALL controllers like this:
Ember.Application.initializer({
name: 'register-global-dates',
initialize: function(container,app) {
app.register('my:globalDate', App.SelectedDates.create(), {
singleton: true,
instantiate: false
});
app.inject('controller', 'dateglobal', 'my:globalDate');
}
});
Now, in your controller(s), you can do this:
this.dateglobal.set('dateBegin', '2015/01/12');
It's the same object in all controllers.
I hope I am understanding your problem correctly and that this is the solution...

ember-qunit: You can only unload a record which is not inFlight

I have some unit tests that access the store. I would have thought this would be fine, so long as I wrapped them in a run callback. Unfortunately, that's not the case. I'm getting this error:
afterEach failed on #foo: Assertion Failed: You can only unload a record which is not inFlight.
As I understand it, this is exactly what run should be preventing. My test looks something like this:
test('#foo', function(assert) {
var store = this.store();
var model = this.subject();
Ember.run(function() {
var secondModel = store.createRecord('secondModel', { foo: 'bar' });
model.set('secondModel', secondModel);
var foo = model.get('secondModelFoo');
assert.equal(foo, 'bar');
});
});
Seems like this is no longer an issue in Ember Data v1.13.8 in combination with Ember v1.13.7.
For following setup:
models/first-model.js
import DS from 'ember-data';
export default DS.Model.extend({
secondModel: DS.belongsTo('second-model')
});
models/second-model.js
import DS from 'ember-data';
export default DS.Model.extend({
foo: DS.attr('string')
});
tests/unit/models/first-model-test.js
import Ember from 'ember';
import { moduleForModel, test } from 'ember-qunit';
moduleForModel('first-model', 'Unit | Model | first model', {
// Specify the other units that are required for this test.
needs: ['model:second-model']
});
test('it exists', function(assert) {
var model = this.subject();
// var store = this.store();
assert.ok(!!model);
});
test('#foo', function(assert) {
var store = this.store();
var model = this.subject();
Ember.run(function() {
assert.expect(1);
var secondModel = store.createRecord('second-model', { foo: 'bar' });
model.set('secondModel', secondModel);
var foo = model.get('secondModel.foo');
assert.equal(foo, 'bar');
});
});
Tests pass. Demo project repository on GitHub.