Skip to content

Commit

Permalink
Merge pull request #14 from asennikov/address-marker
Browse files Browse the repository at this point in the history
Address marker
  • Loading branch information
asennikov committed Jan 3, 2016
2 parents c88a080 + f75bda7 commit 2dcc031
Show file tree
Hide file tree
Showing 10 changed files with 260 additions and 2 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,30 @@ one Info Window is open at each moment for Markers of each group.
{{/g-map}}
```

## Marker bound to address query

Proxy `g-map-address-marker` component takes address string as parameter
and translates it to lat/lng under the hood.
Other optional parameters are the same as for `g-map-marker`.
Requires `places` library to be specified in `environment.js`.

```javascript
ENV['g-map'] = {
libraries: ['places']
}
```

```handlebars
{{#g-map lat=37.7833 lng=-122.4167 zoom=12 as |context|}}
{{g-map-address-marker context address="San Francisco, Russian Hill"}}
{{#g-map-address-marker context address="Delft, The Netherlands"}}
{{#g-map-infowindow markerContext}}
Works in block form too.
{{/g-map-infowindow}}
{{/g-map-address-marker}}
{{/g-map}}
```

## Map with route between 2 locations

Using Google Maps [Directions](https://developers.google.com/maps/documentation/javascript/directions) service.
Expand Down
58 changes: 58 additions & 0 deletions addon/components/g-map-address-marker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import Ember from 'ember';
import layout from '../templates/components/g-map-address-marker';
/* global google */

const { computed, observer, run, isPresent, isEmpty } = Ember;

const GMapAddressMarkerComponent = Ember.Component.extend({
layout: layout,
classNames: ['g-map-address-marker'],

map: computed.alias('mapContext.map'),

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

onAddressChanged: observer('address', function() {
run.once(this, 'updateLocation');
}),

updateLocation() {
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.set('lat', results[0].geometry.location.lat());
this.set('lng', results[0].geometry.location.lng());
}
});
}
}
});

GMapAddressMarkerComponent.reopenClass({
positionalParams: ['mapContext']
});

export default GMapAddressMarkerComponent;
7 changes: 7 additions & 0 deletions addon/templates/components/g-map-address-marker.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{#if hasBlock}}
{{#g-map-marker mapContext lat=lat lng=lng icon=icon group=group as |markerContext|}}
{{yield markerContext}}
{{/g-map-marker}}
{{else}}
{{g-map-marker mapContext lat=lat lng=lng icon=icon group=group}}
{{/if}}
1 change: 1 addition & 0 deletions app/components/g-map-address-marker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'ember-g-map/components/g-map-address-marker';
6 changes: 5 additions & 1 deletion config/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@
/* jshint node: true */

module.exports = function(/* environment, appConfig */) {
return { };
return {
'g-map': {
libraries: ['places']
}
};
};
2 changes: 2 additions & 0 deletions tests/dummy/app/controllers/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import Ember from 'ember';
export default Ember.Controller.extend({
randomVariable: 111,

addressQuery: 'SF, Lafayette Park',

customOptions: {
mapTypeId: google.maps.MapTypeId.TERRAIN
},
Expand Down
2 changes: 1 addition & 1 deletion tests/dummy/app/styles/app.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.g-map-canvas {
width: 600px;
width: 100%;
height: 600px;
}
11 changes: 11 additions & 0 deletions tests/dummy/app/templates/application.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<h2 id="title">Ember-g-map Component</h2>

<p>
<label>Address Query for Bound Marker</label>
{{input value=addressQuery}}
</p>

{{#g-map shouldFit=true options=customOptions as |context|}}
{{g-map-marker context lat=37.7933 lng=-122.4467}}

Expand All @@ -15,6 +20,12 @@
{{/g-map-infowindow}}
{{/g-map-marker}}

{{#g-map-address-marker context address=addressQuery as |markerContext|}}
{{#g-map-infowindow markerContext}}
<h1>Bound to Address Query</h1>
{{/g-map-infowindow}}
{{/g-map-address-marker}}

{{#g-map-infowindow context lat=37.7333 lng=-122.4367 onClose="onInfowindowClosed"}}
<h1>Independent InfoWindow</h1>
<p>This InfoWindow can be placed anywhere on the map.</p>
Expand Down
32 changes: 32 additions & 0 deletions tests/integration/components/g-map-address-marker-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

moduleForComponent('g-map-address-marker', 'Integration | Component | g map address marker', {
integration: true
});

test('it renders', function(assert) {
assert.expect(2);

// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });

this.render(hbs`
{{#g-map as |context|}}
{{g-map-address-marker context}}
{{/g-map}}
`);

assert.equal(this.$().text().trim(), '');

// Template block usage:
this.render(hbs`
{{#g-map as |context|}}
{{#g-map-address-marker context}}
template block text
{{/g-map-address-marker}}
{{/g-map}}
`);

assert.equal(this.$().text().trim(), 'template block text');
});
119 changes: 119 additions & 0 deletions tests/unit/components/g-map-address-marker-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import Ember from 'ember';
import { moduleForComponent } from 'ember-qunit';
import test from '../../ember-sinon-qunit/test';
import GMapComponent from 'ember-g-map/components/g-map';
import sinon from 'sinon';

const { run } = Ember;

let fakePlacesService;
let component;

moduleForComponent('g-map-address-marker', 'Unit | Component | g map address marker', {
// Specify the other units that are required for this test
// needs: ['component:foo', 'helper:bar'],
unit: true,

beforeEach() {
fakePlacesService = {
textSearch: sinon.stub()
};
sinon.stub(google.maps.places, 'PlacesService').returns(fakePlacesService);
component = this.subject({
mapContext: new GMapComponent()
});
},

afterEach() {
google.maps.places.PlacesService.restore();
}
});

test('it calls `initPlacesService` on `didInsertElement`', function() {
component.initPlacesService = sinon.stub();
component.trigger('didInsertElement');
sinon.assert.calledOnce(component.initPlacesService);
});

test('it constructs new `PlacesService` on `initPlacesService` call', function(assert) {
run(() => component.set('map', {}));
run(() => component.initPlacesService());

sinon.assert.calledOnce(google.maps.places.PlacesService);
assert.equal(component.get('placesService'), fakePlacesService);
});

test('new `PlacesService` isn\'t being constructed if it\'s already present in component', function() {
run(() => component.setProperties({
placesService: fakePlacesService,
map: {}
}));
run(() => component.initPlacesService());

sinon.assert.notCalled(google.maps.places.PlacesService);
});

test('it triggers `initPlacesService` on `mapContext.map` change', function() {
run(() => component.set('mapContext', { map: '' }));
component.initPlacesService = sinon.spy();
run(() => component.set('mapContext.map', {}));
sinon.assert.calledOnce(component.initPlacesService);
});

test('it triggers `updateLocation` on `initPlacesService` call', function() {
component.updateLocation = sinon.spy();

run(() => component.set('map', {}));
run(() => component.initPlacesService());

sinon.assert.calledOnce(component.updateLocation);
});

test('it triggers `updateLocation` on `address` change', function() {
component.updateLocation = sinon.spy();
run(() => component.set('address', 'query string'));
sinon.assert.calledOnce(component.updateLocation);
});

test('it calls `textSearch` of placesService on `updateLocation`', function() {
run(() => component.set('address', 'query string'));
run(() => component.set('placesService', fakePlacesService));

fakePlacesService.textSearch = sinon.stub();
run(() => component.updateLocation());

const correctRequest = { query: 'query string' };

sinon.assert.calledOnce(fakePlacesService.textSearch);
sinon.assert.calledWith(fakePlacesService.textSearch, correctRequest);
});

test('it sets `lat` & `lng` of the first textSearch result on `updateLocation`', function(assert) {
const results = [{
geometry: {
location: {
lat: () => 12,
lng: () => -20
}
}
}, {
geometry: {
location: {
lat: () => 24,
lng: () => 100
}
}
}];
const status = google.maps.places.PlacesServiceStatus.OK;
fakePlacesService.textSearch.callsArgWith(1, results, status);

run(() => component.set('address', 'query string'));
run(() => component.set('placesService', fakePlacesService));

run(() => component.updateLocation());

assert.equal(component.get('lat'), 12);
assert.equal(component.get('lng'), -20);

fakePlacesService.textSearch = sinon.stub();
});

0 comments on commit 2dcc031

Please sign in to comment.