-
stop app (
ctrl+C
) -
ember install ember-esri-loader
-
in app/styes/app.css
-
add the following to the top of the file:
/* esri styles */
@import url('https://js.arcgis.com/4.6/esri/css/main.css');
- and add the following at the end:
/* map */
.extents-map {
height: 300px;
margin-bottom: 20px;
}
ember s
ember g service map-service
ember g component extents-map
rm app/templates/components/extents-map.hbs
- replace contents of app/services/map-service.js with
import Service from '@ember/service';
import { inject as service } from '@ember/service';
export default Service.extend({
esriLoader: service('esri-loader'),
// create a new map object at an element
newMap(element, mapOptions) {
// load the map modules
this.get('esriLoader').loadModules(['esri/Map', 'esri/views/MapView'])
.then(([Map, MapView]) => {
if (!element || this.get('isDestroyed') || this.get('isDestroying')) {
// component or app was likely destroyed
return;
}
// Create the Map
var map = new Map(mapOptions);
// show the map at the element
this._view = new MapView({
map,
container: element,
zoom: 2
});
});
},
// destroy the map if it was already created
destroyMap() {
if (this._view) {
delete this._view;
}
}
});
- replace contents of app/components/extents-map.js with:
import Component from '@ember/component';
import { inject as service } from '@ember/service';
export default Component.extend({
classNames: ['extents-map'],
mapService: service('map-service'),
// wait until after the component is added to the DOM before creating the map
didInsertElement () {
this._super(...arguments);
// create a map at this element's DOM node
const mapService = this.get('mapService');
mapService.newMap(this.elementId, { basemap: 'gray' });
},
// destroy the map before this component is removed from the DOM
willDestroyElement () {
this._super(...arguments);
const mapService = this.get('mapService');
mapService.destroyMap();
}
});
-
in app/templates/items.hbs add
{{extents-map}}
above the table -
go to the items route,
Notice that:
- you should see a map
Goal: stub the map service so we don't create a map when testing
ember install ember-sinon-qunit
- in tests/integration/components/extents-map-test.js:
- replace
import { module, test } from 'qunit';
with:
import { module } from 'qunit';
import test from 'ember-sinon-qunit/test-support/test';
- replace the contents of the 'it renders' test with:
// stub the newMap() function so that a map is not constructed
const mapService = this.owner.lookup('service:map-service');
const stub = this.stub(mapService, 'newMap');
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.set('myAction', function(val) { ... });
await render(hbs`{{extents-map}}`);
assert.ok(stub.calledOnce, 'newMap was called once');
- run
ember t -s
- open chrome devtools, inspect network requests
Notice that:
- all tests pass
- no network requests are made for JSAPI
Once the map has loaded, and whenever map component's items are updated:
- clear map graphics
- loop through items, and for each
- create a
new Graphic()
from the item - add the graphic to the map
2 sets of async state: Application (Ember) and map:
- each has own lifecyle (event)
- up to developer to keep 2 sets of state in sync.
Converting item to a Graphic:
- get
geometry
by converting itemextent
from coordinate array to extent JSON - get
attributes
from itemtitle
andsnippet
- get
infoTemplate
andsymbol
from config
-
ember g util map/coords-to-extent
-
run tests w/
ember test -s
-
in tests/unit/utils/map/coords-to-extent-test.js:
-
remove the unused
hooks
argument from themodule()
callback function -
replace the existing
test()
with the following:
test('it works', function(assert) {
const coords = [[-53.2316, -79.8433], [180, 79.8433]];
let result = mapCoordsToExtent(coords);
assert.deepEqual(result, {
type: 'extent',
xmin: -53.2316,
ymin: -79.8433,
xmax: 180,
ymax: 79.8433,
spatialReference:{
wkid:4326
}
});
});
test('it handles invalid coords', function(assert) {
let result = mapCoordsToExtent([]);
assert.equal(result, undefined);
});
- replace app/utils/map/coords-to-extent.js content with:
// expect [[-53.2316, -79.8433], [180, 79.8433]] or []
export default function mapCoordsToExtent (coords) {
if (coords && coords.length === 2) {
return {
type: 'extent',
xmin: coords[0][0],
ymin: coords[0][1],
xmax: coords[1][0],
ymax: coords[1][1],
spatialReference:{
wkid:4326
}
};
}
}
- stop tests by typing
q
Add a function to the map service that lets the component add graphics to the map.
- in app/services/map-service.js:
- replace the contents of
newMap
with:
// load the map modules
return this.get('esriLoader').loadModules(['esri/Map', 'esri/views/MapView', 'esri/Graphic'])
.then(([Map, MapView, Graphic]) => {
if (!element || this.get('isDestroyed') || this.get('isDestroying')) {
// component or app was likely destroyed
return;
}
// create function to return new graphics
this._newGraphic = (jsonGraphic) => {
return new Graphic(jsonGraphic);
};
var map = new Map(mapOptions);
// show the map at the element and
// hold on to the view reference for later operations
this._view = new MapView({
map,
container: element,
zoom: 2
});
return this._view.when(() => {
this._view.on("mouse-wheel", function(evt){
// prevents zooming with the mouse-wheel event
evt.stopPropagation();
});
// let the caller know that the map is available
return;
});
});
- then add this method:
// clear and add graphics to the map
refreshGraphics (jsonGraphics) {
const view = this._view;
if (!view || !view.ready) {
return;
}
// clear any existing graphics
view.graphics.removeAll();
// convert json to graphics and add to map's graphic layer
if (!jsonGraphics || jsonGraphics.length === 0) {
return;
}
jsonGraphics.forEach(jsonGraphic => {
view.graphics.add(this._newGraphic(jsonGraphic));
});
},
- run
ember s
Notice that:
- the app functions the same as before (our changes didn't break anything)
Before we add the code to show graphics, let's put any optional parameters into the application config.
- stop app if running (
ctrl+C
) - in config/environment.js add this to
APP
:
map: {
options: {
basemap: 'gray'
},
itemExtents: {
symbol: {
color: [51, 122, 183, 0.125],
outline: {
color: [51, 122, 183, 1],
width: 1,
type: 'simple-line',
style: 'solid'
},
type: 'simple-fill',
style: 'solid'
},
popupTemplate: {
title: '{title}',
content: '{snippet}'
}
}
}
-
run
ember s
-
in app/templates/items.hbs pass the items to the map component by updating the
extents-map
invocation to:{{extents-map items=model.results}}
-
in app/components/extents-map.js add these
import
statements
import config from '../config/environment';
import coordsToExtent from '../utils/map/coords-to-extent';
- then add this method:
// show new item extents on map
showItemsOnMap () {
const { symbol, popupTemplate } = config.APP.map.itemExtents;
const items = this.get('items');
const jsonGraphics = items && items.map(item => {
const geometry = coordsToExtent(item.extent);
return { geometry, symbol, attributes: item, popupTemplate };
});
this.get('mapService').refreshGraphics(jsonGraphics);
},
- then update contents of
didInsertElement()
to:
this._super(...arguments);
// create a map at this element's DOM node
const mapService = this.get('mapService');
// create a map at this element's DOM node
mapService.newMap(this.elementId, config.APP.map.options)
.then(() => {
this.showItemsOnMap();
});
- visit the items route
Notice that:
- the extents appear on the map
- you can click on one and see the popup
- but they don't update when you change the query, or page, so
back in app/components/extents-map.js add this method:
// whenever items change, update the map
didUpdateAttrs () {
this.showItemsOnMap();
},
- try searching, paging, navigating back to home and searching from there
Notice that:
- see the extents on the map change when you change query/page
in tests/integration/components/extents-map-test.js:
- add this to the top of the file:
import { resolve } from 'rsvp';
- replace the
stub
with:
const stub = this.stub(mapService, 'newMap').callsFake(() => {
// return a promise
return resolve();
});
- run
ember t
Notice that:
- tests pass