diff --git a/addon/components/g-map-address-marker.js b/addon/components/g-map-address-marker.js index b8c707b..10870d7 100644 --- a/addon/components/g-map-address-marker.js +++ b/addon/components/g-map-address-marker.js @@ -1,33 +1,17 @@ import Ember from 'ember'; import layout from '../templates/components/g-map-address-marker'; -/* global google */ -const { computed, observer, run, isPresent, isEmpty, typeOf } = Ember; +const { observer, run, typeOf, inject } = Ember; const GMapAddressMarkerComponent = Ember.Component.extend({ layout: layout, classNames: ['g-map-address-marker'], - map: computed.alias('mapContext.map'), + places: inject.service(), didInsertElement() { - this._super(); - this.initPlacesService(); - }, - - mapWasSet: observer('map', function() { - run.once(this, 'initPlacesService'); - }), - - initPlacesService() { - const map = this.get('map'); - let service = this.get('placesService'); - - if (isPresent(map) && isEmpty(service)) { - service = new google.maps.places.PlacesService(map); - this.set('placesService', service); - this.searchLocation(); - } + this._super(...arguments); + this.searchLocation(); }, onAddressChanged: observer('address', function() { @@ -35,36 +19,23 @@ const GMapAddressMarkerComponent = Ember.Component.extend({ }), searchLocation() { - const service = this.get('placesService'); const address = this.get('address'); - - if (isPresent(service) && isPresent(address)) { - const request = { query: address }; - - service.textSearch(request, (results, status) => { - if (status === google.maps.places.PlacesServiceStatus.OK) { - this.updateLocation(results); - } - }); - } + this.get('places').search(address) + .then((lat, lng, results) => this.updateLocation(lat, lng, results)); }, - updateLocation(results) { - const lat = results[0].geometry.location.lat(); - const lng = results[0].geometry.location.lng(); - - this.set('lat', lat); - this.set('lng', lng); + updateLocation(lat, lng, results) { + this.setProperties({ lat, lng }); this.sendOnLocationChange(lat, lng, results); }, - sendOnLocationChange() { + sendOnLocationChange(lat, lng, results) { const { onLocationChange } = this.attrs; if (typeOf(onLocationChange) === 'function') { - onLocationChange(...arguments); + onLocationChange(lat, lng, results); } else { - this.sendAction('onLocationChange', ...arguments); + this.sendAction('onLocationChange', lat, lng, results); } } }); diff --git a/addon/components/g-map-address-route.js b/addon/components/g-map-address-route.js index 3d7f165..705fe8f 100644 --- a/addon/components/g-map-address-route.js +++ b/addon/components/g-map-address-route.js @@ -1,33 +1,17 @@ import Ember from 'ember'; import layout from '../templates/components/g-map-address-route'; -/* global google */ -const { computed, observer, run, isPresent, isEmpty, typeOf } = Ember; +const { observer, run, typeOf, inject } = Ember; const GMapAddressRouteComponent = Ember.Component.extend({ layout: layout, classNames: ['g-map-address-route'], - map: computed.alias('mapContext.map'), + places: inject.service(), didInsertElement() { this._super(); - this.initPlacesService(); - }, - - mapWasSet: observer('map', function() { - run.once(this, 'initPlacesService'); - }), - - initPlacesService() { - const map = this.get('map'); - let service = this.get('placesService'); - - if (isPresent(map) && isEmpty(service)) { - service = new google.maps.places.PlacesService(map); - this.set('placesService', service); - this.searchLocations(); - } + this.searchLocations(); }, onAddressChanged: observer('originAddress', 'destinationAddress', function() { @@ -35,56 +19,33 @@ const GMapAddressRouteComponent = Ember.Component.extend({ }), searchLocations() { - const service = this.get('placesService'); const originAddress = this.get('originAddress'); const destinationAddress = this.get('destinationAddress'); - if (isPresent(service) && isPresent(originAddress)) { - const originRequest = { query: originAddress }; + this.get('places').search(originAddress) + .then((lat, lng, results) => this.updateOriginLocation(lat, lng, results)); - service.textSearch(originRequest, (results, status) => { - if (status === google.maps.places.PlacesServiceStatus.OK) { - this.updateOriginLocation(results); - } - }); - } - - if (isPresent(service) && isPresent(destinationAddress)) { - const destinationRequest = { query: destinationAddress }; - - service.textSearch(destinationRequest, (results, status) => { - if (status === google.maps.places.PlacesServiceStatus.OK) { - this.updateDestinationLocation(results); - } - }); - } + this.get('places').search(destinationAddress) + .then((lat, lng, results) => this.updateDestinationLocation(lat, lng, results)); }, - updateOriginLocation(results) { - const lat = results[0].geometry.location.lat(); - const lng = results[0].geometry.location.lng(); - - this.set('originLat', lat); - this.set('originLng', lng); + updateOriginLocation(lat, lng, results) { + this.setProperties({ originLat: lat, originLng: lng }); this.sendOnLocationsChange(lat, lng, results); }, - updateDestinationLocation(results) { - const lat = results[0].geometry.location.lat(); - const lng = results[0].geometry.location.lng(); - - this.set('destinationLat', lat); - this.set('destinationLng', lng); + updateDestinationLocation(lat, lng, results) { + this.setProperties({ destinationLat: lat, destinationLng: lng }); this.sendOnLocationsChange(lat, lng, results); }, - sendOnLocationsChange() { + sendOnLocationsChange(lat, lng, results) { const { onLocationsChange } = this.attrs; if (typeOf(onLocationsChange) === 'function') { - onLocationsChange(...arguments); + onLocationsChange(lat, lng, results); } else { - this.sendAction('onLocationsChange', ...arguments); + this.sendAction('onLocationsChange', lat, lng, results); } } }); diff --git a/addon/components/g-map-route-address-waypoint.js b/addon/components/g-map-route-address-waypoint.js index 45988e9..3a6e4bb 100644 --- a/addon/components/g-map-route-address-waypoint.js +++ b/addon/components/g-map-route-address-waypoint.js @@ -1,56 +1,31 @@ import Ember from 'ember'; import layout from '../templates/components/g-map-route-address-waypoint'; -const { isEmpty, isPresent, observer, computed, run } = Ember; +const { observer, run, inject } = Ember; const GMapRouteAddressWaypointComponent = Ember.Component.extend({ layout: layout, classNames: ['g-map-route-address-waypoint'], - map: computed.alias('routeContext.map'), + places: inject.service(), didInsertElement() { this._super(); - this.initPlacesService(); + this.searchLocation(); }, - initPlacesService: Ember.observer('map', function() { - const map = this.get('map'); - let service = this.get('placesService'); - - if (isPresent(map) && isEmpty(service)) { - service = new google.maps.places.PlacesService(map); - this.set('placesService', service); - - this.searchLocation(); - } - }), - onAddressChanged: observer('address', function() { run.once(this, 'searchLocation'); }), searchLocation() { - const service = this.get('placesService'); const address = this.get('address'); - - if (isPresent(service) && isPresent(address)) { - const request = { query: address }; - - service.textSearch(request, (results, status) => { - if (status === google.maps.places.PlacesServiceStatus.OK) { - this.updateLocation(results); - } - }); - } + this.get('places').search(address) + .then((lat, lng) => this.updateLocation(lat, lng)); }, - updateLocation(results) { - const lat = results[0].geometry.location.lat(); - const lng = results[0].geometry.location.lng(); - - this.set('lat', lat); - this.set('lng', lng); + updateLocation(lat, lng) { + this.setProperties({ lat, lng }); } }); diff --git a/addon/components/g-map.js b/addon/components/g-map.js index e347913..55a698d 100644 --- a/addon/components/g-map.js +++ b/addon/components/g-map.js @@ -1,13 +1,16 @@ import Ember from 'ember'; import layout from '../templates/components/g-map'; +/* global google */ -const { isEmpty, isPresent, computed, observer, run } = Ember; +const { isEmpty, isPresent, computed, observer, run, inject } = Ember; export default Ember.Component.extend({ layout: layout, classNames: ['g-map'], bannedOptions: Ember.A(['center', 'zoom']), + places: inject.service(), + init() { this._super(); this.set('markers', Ember.A()); @@ -32,7 +35,9 @@ export default Ember.Component.extend({ if (isEmpty(this.get('map'))) { const canvas = this.$().find('.g-map-canvas').get(0); const options = this.get('permittedOptions'); - this.set('map', new google.maps.Map(canvas, options)); + const map = new google.maps.Map(canvas, options); + this.set('map', map); + this.get('places').start(map); } this.setZoom(); this.setCenter(); diff --git a/addon/services/places.js b/addon/services/places.js new file mode 100644 index 0000000..7eb226c --- /dev/null +++ b/addon/services/places.js @@ -0,0 +1,35 @@ +import Ember from 'ember'; + +const { isPresent, isEmpty, RSVP } = Ember; + +export default Ember.Service.extend({ + init() { + this._super(...arguments); + this.internalService = null; + }, + + start(map) { + if (isEmpty(this.get('internalService'))) { + this.set('internalService', new google.maps.places.PlacesService(map)); + } + }, + + search(address) { + const service = this.get('internalService'); + + if (isPresent(service) && isPresent(address)) { + const request = { query: address }; + + return RSVP.Promise((resolve, reject) => { + service.textSearch(request, (results, status) => { + if (status === google.maps.places.PlacesServiceStatus.OK) { + const firstLocation = results[0].geometry.location; + resolve(firstLocation.lat(), firstLocation.lng(), results); + } else { + reject(); + } + }); + }); + } + } +}); diff --git a/app/services/places.js b/app/services/places.js new file mode 100644 index 0000000..2ded6b6 --- /dev/null +++ b/app/services/places.js @@ -0,0 +1 @@ +export { default } from 'ember-g-map/services/places'; \ No newline at end of file diff --git a/tests/unit/components/g-map-test.js b/tests/unit/components/g-map-test.js index 5b81d56..d1b8a5c 100644 --- a/tests/unit/components/g-map-test.js +++ b/tests/unit/components/g-map-test.js @@ -5,7 +5,15 @@ import sinon from 'sinon'; const { run } = Ember; -let fakeMapObject; +let component, fakeMapObject, fakePlacesService; + +function fakeJquery() { + return { + find: () => { + return { get: () => 'jquery-object' }; + } + }; +} moduleForComponent('g-map', 'Unit | Component | g map', { // Specify the other units that are required for this test @@ -15,10 +23,18 @@ moduleForComponent('g-map', 'Unit | Component | g map', { beforeEach() { fakeMapObject = { setCenter: sinon.stub(), + setOptions: sinon.stub(), setZoom: sinon.stub(), fitBounds: sinon.stub() }; + fakePlacesService = { + start: sinon.stub() + }; sinon.stub(google.maps, 'Map').returns(fakeMapObject); + component = this.subject({ + places: fakePlacesService, + $: fakeJquery + }); }, afterEach() { @@ -26,28 +42,27 @@ moduleForComponent('g-map', 'Unit | Component | g map', { } }); -test('it constructs new `Map` object after render', function(assert) { - const component = this.subject(); +test('it constructs new `Map` object on `didInsertElement` event', function(assert) { + component.trigger('didInsertElement'); - this.render(); sinon.assert.calledOnce(google.maps.Map); assert.equal(component.get('map'), fakeMapObject); }); -test('it constructs new `Map` object with given custom options', function() { - this.subject({ +test('it constructs new `Map` object with given custom options on `didInsertElement` event', function() { + component.setProperties({ options: { googleMapOption: 123 } }); - this.render(); + component.trigger('didInsertElement'); const canvasElement = this.$().find('.g-map-canvas').get(0); sinon.assert.calledWith(google.maps.Map, canvasElement, { googleMapOption: 123 }); }); -test('it constructs new `Map` object with custom options except banned', function() { - this.subject({ +test('it constructs new `Map` object with custom options except banned on `didInsertElement` event', function() { + component.setProperties({ bannedOptions: Ember.A(['bannedOption']), options: { firstOption: 111, @@ -55,7 +70,7 @@ test('it constructs new `Map` object with custom options except banned', functio bannedOption: 333 } }); - this.render(); + component.trigger('didInsertElement'); const canvasElement = this.$().find('.g-map-canvas').get(0); const expectedOptions = { @@ -65,38 +80,31 @@ test('it constructs new `Map` object with custom options except banned', functio sinon.assert.calledWith(google.maps.Map, canvasElement, expectedOptions); }); -test('new `Map` isn\'t constructed if it already present in component', function() { - this.subject({ map: fakeMapObject }); - this.render(); +test('new `Map` isn\'t constructed if it already present on `didInsertElement` event', function() { + component.set('map', fakeMapObject); + component.trigger('didInsertElement'); sinon.assert.notCalled(google.maps.Map); }); test('it triggers `setZoom` on `didInsertElement` event', function() { - const component = this.subject(); - this.render(); - component.setZoom = sinon.spy(); component.trigger('didInsertElement'); + sinon.assert.calledOnce(component.setZoom); }); test('it triggers `setCenter` on `didInsertElement` event', function() { - const component = this.subject(); - this.render(); - component.setCenter = sinon.spy(); component.trigger('didInsertElement'); + sinon.assert.calledOnce(component.setCenter); }); test('it triggers `setOptions` on `permittedOptions` change', function() { - const component = this.subject({ - permittedOptions: { - firstOption: '11' - } - }); - this.render(); + run(() => component.set('permittedOptions', { + firstOption: '11' + })); component.setOptions = sinon.spy(); run(() => component.set('permittedOptions', { @@ -106,36 +114,24 @@ test('it triggers `setOptions` on `permittedOptions` change', function() { }); test('it triggers `setZoom` on `zoom` change', function() { - const component = this.subject(); - this.render(); - component.setZoom = sinon.spy(); run(() => component.set('zoom', 14)); sinon.assert.calledOnce(component.setZoom); }); test('it triggers `setCenter` on `lat` change', function() { - const component = this.subject(); - this.render(); - component.setCenter = sinon.spy(); run(() => component.set('lat', 14)); sinon.assert.calledOnce(component.setCenter); }); test('it triggers `setCenter` on `lng` change', function() { - const component = this.subject(); - this.render(); - component.setCenter = sinon.spy(); run(() => component.set('lng', 21)); sinon.assert.calledOnce(component.setCenter); }); test('it triggers `setCenter` only once on `lat` and `lng` change', function() { - const component = this.subject(); - this.render(); - component.setCenter = sinon.spy(); run(() => component.setProperties({ lng: 1, @@ -145,12 +141,12 @@ test('it triggers `setCenter` only once on `lat` and `lng` change', function() { }); test('it calls `setOptions` of google map on `setOptions`', function() { - const component = this.subject({ + run(() => component.setProperties({ + map: fakeMapObject, permittedOptions: { firstOption: 123 } - }); - this.render(); + })); fakeMapObject.setOptions = sinon.stub(); run(() => component.setOptions()); @@ -159,8 +155,10 @@ test('it calls `setOptions` of google map on `setOptions`', function() { }); test('it calls `setCenter` of google map on `setCenter` with lat/lng present', function() { - const component = this.subject({ lat: 10, lng: 100 }); - this.render(); + run(() => component.setProperties({ + map: fakeMapObject, + lat: 10, lng: 100 + })); fakeMapObject.setCenter = sinon.stub(); const point = {}; @@ -174,8 +172,10 @@ test('it calls `setCenter` of google map on `setCenter` with lat/lng present', f }); test('it calls `setZoom` of google map on `setZoom`', function() { - const component = this.subject({ zoom: 14 }); - this.render(); + run(() => component.setProperties({ + map: fakeMapObject, + zoom: 14 + })); fakeMapObject.setZoom = sinon.stub(); run(() => component.setZoom()); @@ -184,8 +184,10 @@ test('it calls `setZoom` of google map on `setZoom`', function() { }); test('it doesn\'t call `setCenter` of google map on `setCenter` when no lat present', function() { - const component = this.subject({ lat: 10 }); - this.render(); + run(() => component.setProperties({ + map: fakeMapObject, + lat: 10 + })); fakeMapObject.setCenter = sinon.stub(); run(() => component.setCenter()); @@ -193,8 +195,10 @@ test('it doesn\'t call `setCenter` of google map on `setCenter` when no lat pres }); test('it doesn\'t call `setCenter` of google map on `setCenter` when no lng present', function() { - const component = this.subject({ lng: 10 }); - this.render(); + run(() => component.setProperties({ + map: fakeMapObject, + lng: 10 + })); fakeMapObject.setCenter = sinon.stub(); run(() => component.setCenter()); @@ -202,8 +206,7 @@ test('it doesn\'t call `setCenter` of google map on `setCenter` when no lng pres }); test('it calls `fitToMarkers` object on `didInsertElement` if shouldFit is set to true', function() { - const component = this.subject({ shouldFit: true }); - this.render(); + component.set('shouldFit', true); component.fitToMarkers = sinon.stub(); component.trigger('didInsertElement'); @@ -211,8 +214,7 @@ test('it calls `fitToMarkers` object on `didInsertElement` if shouldFit is set t }); test('it calls `fitToMarkers` object on `didInsertElement` if markersFitMode is "init"', function() { - const component = this.subject({ markersFitMode: 'init' }); - this.render(); + component.set('markersFitMode', 'init'); component.fitToMarkers = sinon.stub(); component.trigger('didInsertElement'); @@ -220,8 +222,7 @@ test('it calls `fitToMarkers` object on `didInsertElement` if markersFitMode is }); test('it calls `fitToMarkers` object on `didInsertElement` if markersFitMode is "live"', function() { - const component = this.subject({ markersFitMode: 'live' }); - this.render(); + component.set('markersFitMode', 'live'); component.fitToMarkers = sinon.stub(); component.trigger('didInsertElement'); @@ -229,8 +230,7 @@ test('it calls `fitToMarkers` object on `didInsertElement` if markersFitMode is }); test('it doesn\'t call `fitToMarkers` object on `didInsertElement` if shouldFit is falsy', function() { - const component = this.subject({ shouldFit: null }); - this.render(); + component.set('shouldFit', null); component.fitToMarkers = sinon.stub(); component.trigger('didInsertElement'); @@ -238,8 +238,7 @@ test('it doesn\'t call `fitToMarkers` object on `didInsertElement` if shouldFit }); test('it doesn\'t call `fitToMarkers` object on `didInsertElement` if markersFitMode has unexpected value', function() { - const component = this.subject({ markersFitMode: 'random-value' }); - this.render(); + component.set('markersFitMode', 'random-value'); component.fitToMarkers = sinon.stub(); component.trigger('didInsertElement'); @@ -249,11 +248,11 @@ test('it doesn\'t call `fitToMarkers` object on `didInsertElement` if markersFit test('it triggers `fitToMarkers` on new marker added with markersFitMode set to "live"', function() { const firstMarker = Ember.Object.create({ lat: 1, lng: 2 }); const secondMarker = Ember.Object.create({ lat: 3, lng: 4 }); - const component = this.subject({ markersFitMode: 'live' }); - this.render(); + component.set('markersFitMode', 'live'); + component.fitToMarkers = sinon.stub(); run(() => component.get('markers').addObject(firstMarker)); - component.fitToMarkers = sinon.spy(); + component.fitToMarkers.reset(); run(() => component.get('markers').addObject(secondMarker)); sinon.assert.calledOnce(component.fitToMarkers); }); @@ -261,11 +260,11 @@ test('it triggers `fitToMarkers` on new marker added with markersFitMode set to test('it triggers `fitToMarkers` only once on `lat`/`lng` change of markers with markersFitMode set to "live"', function() { const firstMarker = Ember.Object.create({ lat: 1, lng: 2 }); const secondMarker = Ember.Object.create({ lat: 3, lng: 4 }); - const component = this.subject({ markersFitMode: 'live' }); - this.render(); + component.set('markersFitMode', 'live'); + component.fitToMarkers = sinon.stub(); run(() => component.get('markers').addObjects([firstMarker, secondMarker])); - component.fitToMarkers = sinon.spy(); + component.fitToMarkers.reset(); run(() => { firstMarker.setProperties({ lng: 4, lat: 5 }); secondMarker.setProperties({ lng: 6, lat: 7 }); @@ -276,8 +275,7 @@ test('it triggers `fitToMarkers` only once on `lat`/`lng` change of markers with test('it doesn\'t trigger `fitToMarkers` with markersFitMode !== "live"', function() { const firstMarker = Ember.Object.create({ lat: 1, lng: 2 }); const secondMarker = Ember.Object.create({ lat: 3, lng: 4 }); - const component = this.subject({ markersFitMode: 'init' }); - this.render(); + component.set('markersFitMode', 'init'); run(() => component.get('markers').addObject(firstMarker)); component.fitToMarkers = sinon.spy(); @@ -289,8 +287,7 @@ test('it doesn\'t trigger `fitToMarkers` with markersFitMode !== "live"', functi test('it calls `fitBounds` of google map on `fitToMarkers`', function() { const firstMarker = Ember.Object.create({ lat: 1, lng: 2 }); const secondMarker = Ember.Object.create({ lat: 3, lng: 4 }); - const component = this.subject(); - this.render(); + component.set('map', fakeMapObject); fakeMapObject.fitBounds = sinon.stub(); @@ -323,9 +320,6 @@ test('it calls `fitBounds` of google map on `fitToMarkers`', function() { }); test('it registers marker in `markers` array during `registerMarker`', function(assert) { - const component = this.subject(); - this.render(); - const firstMarker = { name: 'first' }; const secondMarker = { name: 'second' }; const thirdMarker = { name: 'third' }; @@ -340,9 +334,6 @@ test('it registers marker in `markers` array during `registerMarker`', function( }); test('it unregisters marker from `markers` array during `unregisterMarker`', function(assert) { - const component = this.subject(); - this.render(); - const firstMarker = { name: 'first' }; const secondMarker = { name: 'second' }; const thirdMarker = { name: 'third' }; @@ -363,9 +354,6 @@ test('it calls `closeInfowindow` for each marker in group on `groupMarkerClicked const markers = Ember.A([ firstMarker, secondMarker, thirdMarker, fourthMarker ]); markers.forEach((marker) => marker.closeInfowindow = sinon.stub()); - const component = this.subject(); - this.render(); - run(() => component.set('markers', markers)); run(() => component.groupMarkerClicked(thirdMarker, 'blue')); diff --git a/tests/unit/services/places-test.js b/tests/unit/services/places-test.js new file mode 100644 index 0000000..bce48d4 --- /dev/null +++ b/tests/unit/services/places-test.js @@ -0,0 +1,12 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('service:places', 'Unit | Service | places', { + // Specify the other units that are required for this test. + // needs: ['service:foo'] +}); + +// Replace this with your real tests. +test('it exists', function(assert) { + const service = this.subject(); + assert.ok(service); +});