I'm currently leveraging ember-cli-geolocate along with ember-google-maps to provide users with the closest point of interest to their current location. I've got the code working in a component but have now realized that I'm not able to sort.
Code below:
//routes/vineyard/index.js
import Ember from 'ember';
import { later } from '#ember/runloop';
import { inject as service } from '#ember/service';
import $ from 'jquery';
const { Route, set } = Ember;
export default Route.extend({
model() {
return this.store.findAll('vineyard');
},
setupController(controller, model) {
set(controller, 'vineyards', model);
},
activate() {
this.controllerFor('vineyard/index').send('distanceFrom');
}
});
//controllers/vineyard/index.js
import Ember from 'ember';
import { inject as service } from '#ember/service';
import $ from 'jquery';
export default Ember.Controller.extend({
userLocation: null,
endLocation: null,
milesAway: null,
locationIsLoading: true,
failState: false,
googleMapsApi: service(),
geolocation: service(),
panelActions: Ember.inject.service(),
userLocationChanged: function () {
this.get('userLocation');
this.toggleProperty('locationIsLoading');
}.observes('userLocation'),
actions: {
distanceFrom: function() {
this.get('geolocation').trackLocation().then((geoObject) => {
let currentLocation = this.get('geolocation').get('currentLocation');
this.set('userLocation', currentLocation);
}, (reason) => {
// this.toggleProperty('failState');
// $('.error').css('height', '220px');
// $('.error > p').css('height', 'auto');
console.log('Geolocation failed because ' + reason);
});
},
stopError: function() {
this.toggleProperty('failState');
$('.error').css('height', '0');
$('.location-loader').animate({opacity: '0'}, 1000);
}
},
});
components/miles-away.js
import Component from '#ember/component';
import { inject as service } from '#ember/service';
import { later } from '#ember/runloop';
import $ from 'jquery';
export default Component.extend({
googleMapsApi: service(),
geolocation: service(),
userLocation: null,
endLocation: null,
milesAway: null,
distanceLoading: true,
errorState: false,
fadeClass: '',
didInsertElement() {
this.set('self', this);
var address = this.get('address');
var location = this.get('location');
var distanceLoading = this.get('distanceLoading');
var userLocationLat = location[0];
var userLocationLon = location[1];
let userLocation = '' + userLocationLat + ',' + userLocationLon
this.set('userLocation', userLocation);
this.set('endLocation', address);
this.send('getDistance');
},
actions: {
getDistance: function() {
// let milesAway = this.get('milesAway');
let userLocation = this.get('userLocation');
let endLocation = this.get('endLocation');
this._super(...arguments);
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 {
// var origin = response.originAddresses[0];
// var destination = response.destinationAddresses[0];
if (response.rows[0].elements[0].status === "ZERO_RESULTS") {
self.toggleProperty('errorState');
} else {
var distance = response.rows[0].elements[0].distance;
// var distance_value = distance.value;
var distance_text = distance.text;
// const miles = distance_text.substring(0, distance_text.length - 3);
self.toggleProperty('distanceLoading');
self.set('milesAway', distance_text);
later((function() {
$('.miles-away').addClass('fade-in');
}), 45);
}
}
}
calculateDistance();
});
},
}
});
components/miles-away.hbs
{{#unless distanceLoading}}
<div class="miles-away-container">
<p class="miles-away">{{milesAway}}</p>
</div>
{{/unless}}
{{yield}}
and finally, the template in which this is rendered..(just providing a snippet)
templates/vineyard/index.hbs
<div class="distance">
{{#if failState}}
{{svg-jar 'fail'}}
{{/if}}
{{#if locationIsLoading}}
{{location-loader}}
{{else}}
{{miles-away location=userLocation address=vineyard.location}}
{{/if}}
</div>
I'm open to implementing this in a completely different way, I know it's not even close to proper or perfect.
Related
I'm having some sorting issues inside one of my components and cannot seem to figure it out. Currently it seems to be sorting correctly, but it's putting what should be the 2nd one sorted, at the bottom. Here is my component, hoping someone could give some insight here...Thanks.
import Component from '#ember/component';
import { inject as service } from '#ember/service';
import EmberObject, { computed, observer } from '#ember/object';
export default Component.extend({
googleMapsApi: service(),
geolocation: service(),
sortDefinition: ['distanceTo'],
sortedVineyards: Ember.computed.sort('model', 'sortDefinition'),
didInsertElement() {
this.send('distanceFrom');
},
actions: {
distanceFrom(){
let distanceFromLoading = this.get('distanceFromLoading');
let userLocation = this.get('userLocation');
var userLocationLat = userLocation[0];
var userLocationLon = userLocation[1];
let userLocationFormat = '' + userLocationLat + ',' + userLocationLon;
// console.log(userLocationFormat);
var self = this;
let model = this.get('model');
// console.log(model);
model.forEach(function(item) {
let endLocation = '' + item.get('location');
self._super(...arguments);
self.get('googleMapsApi.google').then((google) => {
var self = this;
let distanceMatrixService = new google.maps.DistanceMatrixService();
function calculateDistance() {
distanceMatrixService.getDistanceMatrix({
origins: [userLocationFormat],
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) {
} else {
if (response.rows[0].elements[0].status === "ZERO_RESULTS") {
} else {
var distance = response.rows[0].elements[0].distance;
var distance_text = distance.text;
item.set('distanceTo', distance_text);
}
}
}
calculateDistance();
});
});
}
}
});
Turns out in my example, distance_text (the sort definition) was a string. Given my small data set it looked like it was half sorting, when likely it wasn't sorting at all. Turned that number into a proper integer and everything worked nicely.
In my implementation below I try get the token from ember-simple-auth-token and use with a URL, used to connect with ActionCable:
...
export default Route.extend({
store: service(),
currentUser: service(),
session: service(),
cable: service(),
setupConsumer: on('init', function() {
let token = this.get('session.data.authenticated.jwt');
let consumer = this.get('cable')
.createConsumer(`wss://api.${config.APP.host}/cable?token=${token}`);
let channelMixin = Mixin.create({
received(data) {
this.get('store').pushPayload(data);
}
});
consumer.subscriptions.create({
channel: 'ChatroomsChannel'
}, channelMixin);
}),
...
});
This works only at first request. I need store this in a cookie? Thanks.
The follow code solves my issue:
import Route from 'ember-route';
import service from 'ember-service/inject';
import Mixin from 'ember-metal/mixin';
import config from 'apollo-enterprise/config/environment';
export default Route.extend({
session: service(),
cable: service(),
afterModel(model) {
this.get('session.store').restore().then((data) => {
let token = data.authenticated.jwt;
let consumer = this.get('cable')
.createConsumer(`wss://api.${config.APP.host}/cable?token=${token}`);
let channelMixin = Mixin.create({
store: service(),
received(data) {
this.get('store').pushPayload(data);
}
});
consumer.subscriptions.create({
channel: 'MessagesChannel',
chatroom_id: model.id
}, channelMixin);
});
},
setupController(controller) {
this._super(...arguments);
controller.set('message', {});
},
actions: {
sendMessage(params) {
let chatroom = this.controller.get('model');
let message = this.get('store').createRecord('message', params);
message.set('chatroom', chatroom);
message.save().then(() => {
this.controller.set('message', {});
});
}
}
});
I simply want to display my longitude on my app landing page/index/application.hbs. I am embarrassed to say how long I have been working at this! Can anyone help me?
//geoservice.js
import Ember from 'ember';
export default Ember.Service.extend({
longitude: function(position){
return position.coords.longitude;
},
latitude: function(position){
return position.coords.latitude;
}
});
//geo-component.js
import Ember from 'ember';
export default Ember.Component.extend({
geoservice: Ember.inject.service(),
myLongitude: function(){
if (navigator.geolocation) {
return this.get('geoservice').longitude(navigator.geolocation.getCurrentPosition());
} else {
return "Geolocation is not supported by this browser.";
}
}.on('init'),
});
//application.hbs
<h2 id="title">Welcome to Ember</h2>
{{outlet}}
{{geo-component.myLongitude}}
The problem wasn't consuming a service in a component, but that 1) the Geolocation API is asynchronous, and 2) you don't render a component like you did. This works:
app/components/geo-location.js
import Ember from 'ember';
export default Ember.Component.extend({
geo: Ember.inject.service(),
loading: true,
error: null,
latitude: null,
longitude: null,
setPosition: Ember.on('init', function() {
this.get('geo').getPosition().then((position) => {
this.set('latitude', position.latitude);
this.set('longitude', position.longitude);
this.set('loading', false);
}).catch((error) => {
this.set('error', error);
});
})
});
app/services/geo.js
import Ember from 'ember';
export default Ember.Service.extend({
getPosition() {
return new Ember.RSVP.Promise((success, error) => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(success, error)
} else {
error( new Error("Geolocation is not supported by this browser.") );
}
}).then((position) => {
return { latitude: position.coords.latitude, longitude: position.coords.longitude };
});
}
});
app/templates/components/geo-location.hbs
{{#if error}}
{{error}}
{{else if loading}}
Loading...
{{else}}
lat: {{latitude}}, long: {{longitude}}
{{/if}}
app/templates/application.hbs
<h2 id="title">Welcome to Ember</h2>
{{geo-location}}
the serializer in unit test is not processing json at all, but it works as expected in the application. Yeah, I wrote it afterwards, but the question is - why it's not working? I tried also to create it in place, inherit from RESTSerializer, create models in place, but none of that worked.
Maybe someone can give a clue?
update
looks like everything begins in the
isPrimaryType: function (store, typeName, primaryTypeClass) {
var typeClass = store.modelFor(typeName);
return typeClass.modelName === primaryTypeClass.modelName;
},
last string returns false, because of primaryTypeClass.modelName is undefined
Serializer unit test
import DS from 'ember-data';
import { moduleForModel, test } from 'ember-qunit';
import setupStore from 'app/tests/helpers/setup-store';
import Profile from 'app/models/profile';
import Email from 'app/models/email';
import Address from 'app/models/address';
import ProfileSerializer from 'app/serializers/profile';
var env;
moduleForModel('profile', 'Unit | Serializer | profile', {
needs: ['serializer:profile', 'serializer:email', 'serializer:address', 'model:contactable', 'model:email', 'model:address'],
beforeEach: function () {
env = setupStore({
profile: Profile,
email: Email,
address: Address
});
env.registry.register('serializer:profile', ProfileSerializer);
env.profileSerializer = env.container.lookup('serializer:profile');
},
teardown: function() {
Ember.run(env.store, 'destroy');
}
});
test('it converts embedded records attributes', function(assert) {
// expect(3);
let payload = {
id: 1,
first_name: "Carlo",
last_name: "Schuppe",
company: "Metz-Witting",
birthday: "01-10-1985",
photo: null,
emails: [{address: "foo#bar.baz", id: 1, type: "main"}],
addresses: [{city: "Brooklyn", id: 1, type: "main"}]
},
parsed = {
"data":
{
"id":"1",
"type":"profile",
"attributes": { "firstName":"Carlo","lastName":"Schuppe","company":"Metz-Witting","birthday":"01-10-1985","photo":null },
"relationships": {
"emails": { "data": [{"id":"1","type":"email"}] },
"addresses": { "data": [{"id":"1","type":"address"}] }
}
},
"included":[
{"id":"1","type":"email","attributes":{"address":"foo#bar.baz", "kind": "main"},"relationships":{"contactable":{"data":{"type":"profile","id":"1"}}}},
{"id":"1","type":"address","attributes":{"city":"Brooklyn", "kind": "main"},"relationships":{"contactable":{"data":{"type":"profile","id":"1"}}}}
]
},
find, update, findAllRecordsJSON;
Ember.run(function() {
find = env.profileSerializer.normalizeResponse(env.store, Profile, payload, '1', 'findRecord');
// update = env.profileSerializer.normalizeResponse(env.store, Profile, payload, '1', 'updateRecord');
// findAllRecordsJSON = env.profileSerializer.normalizeResponse(env.store, Profile, payload, '1', 'findAll');
});
assert.deepEqual(find, parsed);
// assert.deepEqual(update, parsed);
// assert.deepEqual(findAllRecordsJSON, parsed);
});
setup_store.js
import Ember from 'ember';
import DS from 'ember-data';
// import ActiveModelAdapter from 'active-model-adapter';
// import ActiveModelSerializer from 'active-model-adapter/active-model-serializer';
export default function setupStore(options) {
var container, registry;
var env = {};
options = options || {};
if (Ember.Registry) {
registry = env.registry = new Ember.Registry();
container = env.container = registry.container();
} else {
container = env.container = new Ember.Container();
registry = env.registry = container;
}
env.replaceContainerNormalize = function replaceContainerNormalize(fn) {
if (env.registry) {
env.registry.normalize = fn;
} else {
env.container.normalize = fn;
}
};
var adapter = env.adapter = (options.adapter || '-default');
delete options.adapter;
if (typeof adapter !== 'string') {
env.registry.register('adapter:-ember-data-test-custom', adapter);
adapter = '-ember-data-test-custom';
}
for (var prop in options) {
registry.register('model:' + Ember.String.dasherize(prop), options[prop]);
}
registry.register('store:main', DS.Store.extend({
adapter: adapter
}));
registry.optionsForType('serializer', { singleton: false });
registry.optionsForType('adapter', { singleton: false });
registry.register('adapter:-default', DS.Adapter);
registry.register('serializer:-default', DS.JSONSerializer);
registry.register('serializer:-rest', DS.RESTSerializer);
registry.register('serializer:-rest-new', DS.RESTSerializer.extend({ isNewSerializerAPI: true }));
registry.register('adapter:-active-model', DS.ActiveModelAdapter);
registry.register('serializer:-active-model', DS.ActiveModelSerializer.extend({isNewSerializerAPI: true}));
registry.register('adapter:-rest', DS.RESTAdapter);
registry.injection('serializer', 'store', 'store:main');
registry.register('transform:string', DS.StringTransform);
registry.register('transform:number', DS.NumberTransform);
registry.register('transform:date', DS.DateTransform);
registry.register('transform:main', DS.Transform);
env.serializer = container.lookup('serializer:-default');
env.restSerializer = container.lookup('serializer:-rest');
env.restNewSerializer = container.lookup('serializer:-rest-new');
env.store = container.lookup('store:main');
env.adapter = env.store.get('defaultAdapter');
env.registry.register('serializer:-active-model', DS.ActiveModelSerializer.extend({isNewSerializerAPI: true}));
env.registry.register('adapter:-active-model', DS.ActiveModelAdapter);
env.registry.register('serializer:application', DS.ActiveModelSerializer.extend({isNewSerializerAPI: true}));
return env;
}
output
{
"data": null,
"included": []
}
I have following in my controller, and facing issue while updating property with array change..
import Ember from 'ember';
export default Ember.Controller.extend({
imageIds: Object.keys(JSON.parse(localStorage.image_ids || "{}")),
// imageIds = ['gnffffffffjdf', 'hzfyfsidfulknm', 'euriekjhfkejh']
previewImageId: function() {
return this.imageIds.get('firstObject');
}.property('imageIds.[]'),
actions: {
addDetails: function() {
this.transitionToRoute('items.add_item');
},
removeImage: function(image_id) {
var uploaded = JSON.parse(localStorage.image_ids || "{}");
delete uploaded[image_id]
localStorage.image_ids = JSON.stringify(uploaded);
this.get("imageIds").removeObject(image_id);
// this.set("imageIds", Object.keys(JSON.parse(localStorage.image_ids || "{}")));
},
updatePreview: function(image_id){
this.set("previewImageId", image_id);
var uploaded = JSON.parse(localStorage.image_ids || "{}");
uploaded[image_id] = image_id;
localStorage.image_ids = JSON.stringify(uploaded);
// this.set("imageIds", Object.keys(JSON.parse(localStorage.image_ids)));
this.get("imageIds").pushObject(image_id);
}
},
init: function(){
var controller = this;
Ember.$('body').on('click', ".current_image", function() {
var public_id = Ember.$(this).attr('id');
controller.set("previewImageId", public_id);
});
}
});
Whenever there is any change in the imageIds array, previewImageId should be updated.
tried using pushObject, removeObject, .get and .set options.
But still no luck
Can anyone pls help me?
ANSWER:
import Ember from 'ember';
export default Ember.Controller.extend({
imageIds: function() {
return Object.keys(JSON.parse(localStorage.image_ids || "{}"));
}.property(),
previewImageId: function() {
return this.get("imageIds.firstObject");
}.property('imageIds.[]'),
actions: {
addDetails: function() {
this.transitionToRoute('items.add_item');
},
removeImage: function(image_id) {
var uploaded = JSON.parse(localStorage.image_ids || "{}");
delete uploaded[image_id]
localStorage.image_ids = JSON.stringify(uploaded);
this.get("imageIds").removeObject(image_id);
},
updatePreview: function(image_id){
var uploaded = JSON.parse(localStorage.image_ids || "{}");
uploaded[image_id] = image_id;
localStorage.image_ids = JSON.stringify(uploaded);
this.get("imageIds").unshiftObject(image_id);
}
},
init: function(){
var controller = this;
Ember.$('body').on('click', ".current_image", function() {
var public_id = Ember.$(this).attr('id');
controller.get("imageIds").removeObject(public_id);
controller.get("imageIds").unshiftObject(public_id);
});
}
});
Here previously I tried with setting value to previewImageId.. which was wrong way, as it overrides my computed property.
I could see that you are setting the previewImageId cp in a couple of places. You should make the computed property as a setter and getter aware.
Take a look here for an example
If the cp is implemented without a setter, then setting some value on the cp will overwrite its computed function.
Here is a working demo for your use case.
Basically I made the imageIds a property. Here is the code snippet:
App.IndexController = Ember.ArrayController.extend({
imageIds: function() {
return this.get("content");
}.property(),
previewImageId: function() {
return this.get("imageIds").get("firstObject");
}.property("imageIds.[]"),
actions: {
remove: function(item) {
this.get("imageIds").removeObject(item);
}
}
});
Hope this helps!