Last active
August 29, 2015 14:14
-
-
Save tomwayson/c5fe0d8c62618fe8b253 to your computer and use it in GitHub Desktop.
angular-esri-map base gist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(angular) { | |
'use strict'; | |
angular.module('esri.map', []); | |
angular.module('esri.map').factory('esriLoader', function ($q) { | |
return function(moduleName){ | |
var deferred = $q.defer(); | |
require([moduleName], function(module){ | |
if(module){ | |
deferred.resolve(module); | |
} else { | |
deferred.reject('Couldn\'t load ' + moduleName); | |
} | |
}); | |
return deferred.promise; | |
}; | |
}); | |
})(angular); | |
(function (angular) { | |
'use strict'; | |
angular.module('esri.map').service('esriRegistry', function ($q) { | |
var registry = {}; | |
return { | |
_register: function(name, deferred){ | |
// if there isn't a promise in the registry yet make one... | |
// this is the case where a directive is nested higher then the controller | |
// needing the instance | |
if (!registry[name]){ | |
registry[name] = $q.defer(); | |
} | |
var instance = registry[name]; | |
// when the deferred from the directive is rejected/resolved | |
// reject/resolve the promise in the registry with the appropriate value | |
deferred.promise.then(function(arg){ | |
instance.resolve(arg); | |
return arg; | |
}, function(arg){ | |
instance.reject(arg); | |
return arg; | |
}); | |
return function(){ | |
delete registry[name]; | |
}; | |
}, | |
get: function(name){ | |
// is something is already in the registry return its promise ASAP | |
// this is the case where you might want to get a registry item in an | |
// event handler | |
if(registry[name]){ | |
return registry[name].promise; | |
} | |
// if we dont already have a registry item create one. This covers the | |
// case where the directive is nested inside the controler. The parent | |
// controller will be executed and gets a promise that will be resolved | |
// later when the item is registered | |
var deferred = $q.defer(); | |
registry[name] = deferred; | |
return deferred.promise; | |
} | |
}; | |
}); | |
})(angular); | |
(function(angular) { | |
'use strict'; | |
angular.module('esri.map').directive('esriMap', function($q, $timeout, esriLoader, esriRegistry) { | |
return { | |
// element only | |
restrict: 'E', | |
// isoloate scope | |
scope: { | |
// two-way binding for center/zoom | |
// because map pan/zoom can chnage these | |
center: '=?', | |
zoom: '=?', | |
itemInfo: '=?', | |
// one-way binding for other properties | |
basemap: '@', | |
// function binding for event handlers | |
load: '&', | |
extentChange: '&' | |
}, | |
// replace tag with div with same id | |
compile: function($element, $attrs) { | |
// remove the id attribute from the main element | |
$element.removeAttr('id'); | |
// append a new div inside this element, this is where we will create our map | |
$element.append('<div id=' + $attrs.id + '></div>'); | |
// since we are using compile we need to return our linker function | |
// the 'link' function handles how our directive responds to changes in $scope | |
/*jshint unused: false*/ | |
return function(scope, element, attrs, controller) { | |
}; | |
}, | |
// directive api | |
controller: function($scope, $element, $attrs) { | |
// only do this once per directive | |
// this deferred will be resolved with the map | |
var mapDeferred = $q.defer(); | |
// add this map to the registry | |
if($attrs.registerAs){ | |
var deregister = esriRegistry._register($attrs.registerAs, mapDeferred); | |
// remove this from the registry when the scope is destroyed | |
$scope.$on('$destroy', deregister); | |
} | |
require(['esri/map','esri/arcgis/utils'], function(Map, arcgisUtils) | |
{ | |
if($attrs.webmapId) | |
{ | |
arcgisUtils.createMap($attrs.webmapId,$attrs.id).then(function(response) | |
{ | |
mapDeferred.resolve(response.map); | |
var geoCenter = response.map.geographicExtent.getCenter(); | |
$scope.center.lng = geoCenter.x; | |
$scope.center.lat = geoCenter.y; | |
$scope.zoom = response.map.getZoom(); | |
$scope.itemInfo = response.itemInfo; | |
}); | |
} | |
else | |
{ | |
// setup our map options based on the attributes and scope | |
var mapOptions = {}; | |
// center/zoom/extent | |
// check for convenience extent attribute | |
// otherwise get from scope center/zoom | |
if ($attrs.extent) { | |
mapOptions.extent = $scope[$attrs.extent]; | |
} else { | |
if ($scope.center.lng && $scope.center.lat) { | |
mapOptions.center = [$scope.center.lng, $scope.center.lat]; | |
} else if ($scope.center) { | |
mapOptions.center = $scope.center; | |
} | |
if ($scope.zoom) { | |
mapOptions.zoom = $scope.zoom; | |
} | |
} | |
// basemap | |
if ($scope.basemap) { | |
mapOptions.basemap = $scope.basemap; | |
} | |
// initialize map and resolve the deferred | |
var map = new Map($attrs.id, mapOptions); | |
mapDeferred.resolve(map); | |
} | |
mapDeferred.promise.then(function(map) | |
{ | |
// make a reference to the map object available | |
// to the controller once it is loaded. | |
map.on('load', function() { | |
if (!$attrs.load) { | |
return; | |
} | |
$scope.$apply(function() { | |
$scope.load()(map); | |
}); | |
}); | |
// listen for changes to scope and update map | |
$scope.$watch('basemap', function(newBasemap, oldBasemap) { | |
if (map.loaded && newBasemap !== oldBasemap) { | |
map.setBasemap(newBasemap); | |
} | |
}); | |
$scope.inUpdateCycle = false; | |
$scope.$watch(function(scope){ return [scope.center.lng,scope.center.lat, scope.zoom].join(',');}, function(newCenterZoom,oldCenterZoom) | |
// $scope.$watchGroup(['center.lng','center.lat', 'zoom'], function(newCenterZoom,oldCenterZoom) // supported starting at Angular 1.3 | |
{ | |
if( $scope.inUpdateCycle ) { | |
return; | |
} | |
console.log('center/zoom changed', newCenterZoom, oldCenterZoom); | |
newCenterZoom = newCenterZoom.split(','); | |
if( newCenterZoom[0] !== '' && newCenterZoom[1] !== '' && newCenterZoom[2] !== '' ) | |
{ | |
$scope.inUpdateCycle = true; // prevent circular updates between $watch and $apply | |
map.centerAndZoom([newCenterZoom[0], newCenterZoom[1]], newCenterZoom[2]).then(function() | |
{ | |
console.log('after centerAndZoom()'); | |
$scope.inUpdateCycle = false; | |
}); | |
} | |
}); | |
map.on('extent-change', function(e) | |
{ | |
if( $scope.inUpdateCycle ) { | |
return; | |
} | |
$scope.inUpdateCycle = true; // prevent circular updates between $watch and $apply | |
console.log('extent-change geo', map.geographicExtent); | |
$scope.$apply(function() | |
{ | |
var geoCenter = map.geographicExtent.getCenter(); | |
$scope.center.lng = geoCenter.x; | |
$scope.center.lat = geoCenter.y; | |
$scope.zoom = map.getZoom(); | |
// we might want to execute event handler even if $scope.inUpdateCycle is true | |
if( $attrs.extentChange ) { | |
$scope.extentChange()(e); | |
} | |
$timeout(function(){ | |
// this will be executed after the $digest cycle | |
console.log('after apply()'); | |
$scope.inUpdateCycle = false; | |
},0); | |
}); | |
}); | |
// clean up | |
$scope.$on('$destroy', function () { | |
map.destroy(); | |
// TODO: anything else? | |
}); | |
}); | |
}); | |
// method returns the promise that will be resolved with the map | |
this.getMap = function() { | |
return mapDeferred.promise; | |
}; | |
// adds the layer, returns the promise that will be resolved with the result of map.addLayer | |
this.addLayer = function(layer) { | |
return this.getMap().then(function(map) { | |
return map.addLayer(layer); | |
}); | |
}; | |
// array to store layer info, needed for legend | |
// TODO: is this the right place for this? | |
// can it be done on the legend directive itself? | |
this.addLayerInfo = function(lyrInfo) { | |
if (!this.layerInfos) { | |
this.layerInfos = [lyrInfo]; | |
} else { | |
this.layerInfos.push(lyrInfo); | |
} | |
}; | |
this.getLayerInfos = function() { | |
return this.layerInfos; | |
}; | |
} | |
}; | |
}); | |
})(angular); | |
(function(angular) { | |
'use strict'; | |
angular.module('esri.map').directive('esriFeatureLayer', function ($q) { | |
// this object will tell angular how our directive behaves | |
return { | |
// only allow esriFeatureLayer to be used as an element (<esri-feature-layer>) | |
restrict: 'E', | |
// require the esriFeatureLayer to have its own controller as well an esriMap controller | |
// you can access these controllers in the link function | |
require: ['esriFeatureLayer', '^esriMap'], | |
// replace this element with our template. | |
// since we aren't declaring a template this essentially destroys the element | |
replace: true, | |
// define an interface for working with this directive | |
controller: function ($scope, $element, $attrs) { | |
var layerDeferred = $q.defer(); | |
require([ | |
'esri/layers/FeatureLayer'], function (FeatureLayer) { | |
var layer = new FeatureLayer($attrs.url); | |
layerDeferred.resolve(layer); | |
}); | |
// return the defered that will be resolved with the feature layer | |
this.getLayer = function () { | |
return layerDeferred.promise; | |
}; | |
}, | |
// now we can link our directive to the scope, but we can also add it to the map.. | |
link: function (scope, element, attrs, controllers) { | |
// controllers is now an array of the controllers from the 'require' option | |
var layerController = controllers[0]; | |
var mapController = controllers[1]; | |
layerController.getLayer().then(function (layer) { | |
// add layer | |
mapController.addLayer(layer); | |
//look for layerInfo related attributes. Add them to the map's layerInfos array for access in other components | |
mapController.addLayerInfo({ | |
title: attrs.title || layer.name, | |
layer: layer, | |
hideLayers: (attrs.hideLayers) ? attrs.hideLayers.split(',') : undefined, | |
defaultSymbol: (attrs.defaultSymbol) ? JSON.parse(attrs.defaultSymbol) : true | |
}); | |
// return the layer | |
return layer; | |
}); | |
} | |
}; | |
}); | |
})(angular); | |
(function(angular) { | |
'use strict'; | |
/** | |
* @ngdoc directive | |
* @name esriApp.directive:esriLegend | |
* @description | |
* # esriLegend | |
*/ | |
angular.module('esri.map') | |
.directive('esriLegend', function ($document, $q) { | |
return { | |
//run last | |
priority: -10, | |
scope:{}, | |
replace: true, | |
// require the esriMap controller | |
// you can access these controllers in the link function | |
require: ['^esriMap'], | |
// now we can link our directive to the scope, but we can also add it to the map.. | |
link: function(scope, element, attrs, controllers){ | |
// controllers is now an array of the controllers from the 'require' option | |
var mapController = controllers[0]; | |
var targetId = attrs.targetId || attrs.id; | |
var legendDeferred = $q.defer(); | |
require(['esri/dijit/Legend', 'dijit/registry'], function (Legend, registry) { | |
mapController.getMap().then(function(map) { | |
var opts = { | |
map: map | |
}; | |
var layerInfos = mapController.getLayerInfos(); | |
if (layerInfos) { | |
opts.layerInfos = layerInfos; | |
} | |
// NOTE: need to come up w/ a way to that is not based on id | |
// or handle destroy at end of this view's lifecyle | |
var legend = registry.byId(targetId); | |
if (legend) { | |
legend.destroyRecursive(); | |
} | |
legend = new Legend(opts, targetId); | |
legend.startup(); | |
scope.layers = legend.layers; | |
angular.forEach(scope.layers, function(layer, i) { | |
scope.$watch('layers['+i+'].renderer',function() { | |
legend.refresh(); | |
}); | |
}); | |
legendDeferred.resolve(legend); | |
}); | |
}); | |
} | |
}; | |
}); | |
})(angular); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html ng-app="esri-map-example"> | |
<head> | |
<meta name="viewport" content="initial-scale=1.0, user-scalable=no"> | |
<meta charset="utf-8"> | |
<link rel="stylesheet" type="text/css" href="http://js.arcgis.com/3.11/esri/css/esri.css"> | |
</head> | |
<body ng-controller="MapController"> | |
<esri-map id="map" center="map.center" zoom="map.zoom" basemap="topo"> | |
<esri-feature-layer url="http://services.arcgis.com/rOo16HdIMeOBI4Mb/arcgis/rest/services/Portland_Parks/FeatureServer/0"></esri-feature-layer> | |
<esri-feature-layer url="http://services.arcgis.com/rOo16HdIMeOBI4Mb/arcgis/rest/services/Heritage_Trees_Portland/FeatureServer/0"></esri-feature-layer> | |
</esri-map> | |
<p>Lat: {{ map.center.lat | number:3 }}, Lng: {{ map.center.lng | number:3 }}, Zoom: {{map.zoom}}</p> | |
<script type="text/javascript" src="http://js.arcgis.com/3.11compact"></script> | |
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script> | |
<script src="angular-esri-map.js"></script> | |
<script type="text/javascript"> | |
angular.module('esri-map-example', ['esri.map']) | |
.controller('MapController', function ($scope) { | |
$scope.map = { | |
center: { | |
lng: -122.676207, | |
lat: 45.523452 | |
}, | |
zoom: 12 | |
}; | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment