You can add an interactive Google Map to an MDriven Turnkey view, display ViewModel rows as live markers, create rows by clicking the map, and write marker drag positions back to the server.
This integration is a custom AngularJS component for Turnkey. It uses a map directive to create the Google map and a marker directive to keep one Google Maps marker associated with each row in a ViewModel collection. For the general approach to custom HTML and Angular bindings in Turnkey, see View Override.
What this example does
The component supports the following behavior:
- Creates a Google map centered at latitude
59.325, longitude18.073, with zoom level4. - Creates one marker for each row rendered with the
googlemapmarkerdirective. - Updates a marker when the row's
lat,lng, orcolorvalue changes. - Removes a marker when its Angular scope is destroyed, for example when the corresponding row is removed from the displayed collection.
- Creates a server-side row by calling the
AddMarkeraction when the user clicks the map. - Makes markers draggable and writes the final position to
row.latandrow.lngwhen dragging ends.
Prerequisites
Before adding the component, prepare the following:
- Load the Google Maps JavaScript API for the Turnkey application. The Google Maps API requires an API key obtained from Google's developer console.
- Create the component assets in the model's AssetsTK area, following the component strategy used for Google Charts.
- Add a ViewModel for the map view. All data used by the component must be available in the ViewModel; Turnkey HTML can bind only to ViewModel data.
- Add a collection of marker rows to the ViewModel. Each row needs the properties described below.
- Add an
AddMarkeraction that creates a row using the two ViewModel variables populated by the map click:vLatandvLng.
Use the Turnkey developer information for the view to find the Angular binding expression for your collection. It shows the data exposed by the ViewModel and the generated Angular expressions. See View Override for binding examples.
Define the ViewModel data
The marker directive expects the following names on each row. The ViewModel property names are the client-side contract for this script.
| Item | Required value | Purpose |
|---|---|---|
lat
|
Latitude number, or null | Sets the marker latitude. If either coordinate is null, the marker is removed from the map. |
lng
|
Longitude number, or null | Sets the marker longitude. |
color
|
Optional color name | Selects the Google marker icon URL http://maps.google.com/mapfiles/ms/icons/<color>-dot.png.
|
VMClassId.asString
|
Turnkey row identity | Associates a Google Maps marker with its ViewModel row. |
vLat and vLng
|
ViewModel variables | Receive the coordinates from a map click before AddMarker executes.
|
AddMarker
|
ViewModel action | Creates the row that represents the clicked map location. |
For example, a marker row with lat = 59.325, lng = 18.073, and color = blue appears at the initial map center with the blue icon. Changing lat to 60 updates its position. Clearing lat or lng removes it from the map.
The client-side property name does not need to be the same as the persistent model property name. What matters to this script is the name exposed by the ViewModel.
Add the component markup
Create markup for the component and assign it to a ViewModel column with the Angular_Ext_Component tagged value googlemap. That tag connects the Turnkey view column to the directive named googlemap.
The map element needs a CSS class with an explicit height and width. Without dimensions, the map has no visible area.
<div googlemap class="googlemap"></div>
<span ng-repeat="row in data.VM_..." googlemapmarker></span>
Replace data.VM_... with the Angular repeat expression for the collection of marker rows in your ViewModel. Copy this expression from the Turnkey developer information for the view. The ng-repeat creates one span and one googlemapmarker directive instance per row; the span itself does not need visible content.
Use CSS appropriate for the layout. The following example establishes a visible map area:
.googlemap {
height: 400px;
width: 100%;
}
Add the Angular directives
Place the following script in the JavaScript module for the component. It registers googlemap and googlemapmarker with the Turnkey Angular application module.
var map;
var dictionaryOfIdsAndMarkers = {};
function InstallTheDirectiveFor_GoogleMap(streamingAppController) {
streamingAppController.directive('googlemap', ['$document', function ($document) {
return {
link: function (scope, element, attr) {
var stockholm = { lat: 59.325, lng: 18.073 };
map = new google.maps.Map(element[0], {
zoom: 4,
center: stockholm
});
google.maps.event.addListener(map, 'click', function (event) {
placeMarker(event.latLng, scope);
});
}
};
}]);
streamingAppController.directive('googlemapmarker', ['$document', function ($document) {
return {
link: function (scope, element, attr) {
scope.$watch('row.lat', function (newv, oldval, thescope) {
UpdatePosition(thescope.row);
});
scope.$watch('row.lng', function (newv, oldval, thescope) {
UpdatePosition(thescope.row);
});
scope.$watch('row.color', function (newv, oldval, thescope) {
UpdatePosition(thescope.row);
});
scope.$watch('row.VMClassId.asString', function (newv, oldval, thescope) {
var marker = dictionaryOfIdsAndMarkers[oldval];
dictionaryOfIdsAndMarkers[oldval] = null;
dictionaryOfIdsAndMarkers[newv] = marker;
});
scope.$on('$destroy', function (thescope) {
dictionaryOfIdsAndMarkers[thescope.currentScope.row.VMClassId.asString].setMap(null);
});
var newmarker = new google.maps.Marker();
newmarker.setDraggable(true);
newmarker.setTitle("Drag me!");
dictionaryOfIdsAndMarkers[scope.row.VMClassId.asString] = newmarker;
newmarker.addListener('dragend', function (event) {
scope.row.lat = event.latLng.lat();
scope.row.lng = event.latLng.lng();
});
UpdatePosition(scope.row);
}
};
}]);
console.trace("googlemap component Loaded");
}
InstallTheDirectiveFor_GoogleMap(angular.module(MDrivenAngularAppModule));
Add the helper functions
Add these helper functions in the same JavaScript module.
function UpdatePosition(row) {
var markertoUpdate = dictionaryOfIdsAndMarkers[row.VMClassId.asString];
if (row.lat != null && row.lng != null) {
markertoUpdate.setPosition({ lat: row.lat, lng: row.lng });
markertoUpdate.setMap(map);
if (row.color != null)
markertoUpdate.setIcon('http://maps.google.com/mapfiles/ms/icons/' + row.color + '-dot.png');
}
else {
markertoUpdate.setMap(null);
}
}
function placeMarker(location, scope) {
scope.data.VM_Variables.vLat = location.lat();
scope.data.VM_Variables.vLng = location.lng();
scope.data.Execute('AddMarker');
}
How synchronization works
ViewModel row to marker
When Angular renders a row in the repeated collection, googlemapmarker creates a google.maps.Marker. It stores that marker in dictionaryOfIdsAndMarkers, using row.VMClassId.asString as the key.
The directive watches row.lat, row.lng, and row.color. A change to any of these values calls UpdatePosition. For example, if another Turnkey view changes a row's color from blue to yellow, the watched color value causes the map marker icon to update.
A row can receive a new identity after it is created. The watcher for row.VMClassId.asString moves the stored marker from the old key to the new key.
Marker to ViewModel row
When a user finishes dragging a marker, the dragend listener assigns the dropped coordinates to scope.row.lat and scope.row.lng. Turnkey then handles those bound row values as part of the view data.
When a user clicks the map, placeMarker copies the clicked latitude and longitude to VM_Variables.vLat and VM_Variables.vLng, then executes AddMarker. The action must use these variables to create a new marker row. Once that row appears in the repeated collection, Angular creates its marker.
Row deletion
When Angular destroys a marker row scope, the $destroy handler calls setMap(null). Google Maps then removes that marker from the displayed map.
Marker colors
The script constructs an icon URL from the color value. For example, blue becomes:
http://maps.google.com/mapfiles/ms/icons/blue-dot.png
Use a color value for which the corresponding Google icon exists. An invalid icon name can cause the marker icon not to display. If color is null, the script leaves the marker's default icon unchanged.
Test the integration
- Open the Turnkey view containing the component. Confirm that the map is visible and that you can pan and zoom it.
- Ensure that a row with both
latandlngcreates a marker at the expected location. - Change a row's latitude, longitude, or color in a view that exposes the same server object. Confirm that the marker updates.
- Delete a marker row. Confirm that its marker disappears.
- Click an unused position on the map. Confirm that
AddMarkercreates a row with the clicked coordinates and that a new marker appears. - Drag a marker and release it. Confirm that its
latandlngvalues update.
Limits and implementation notes
- The script uses global
mapanddictionaryOfIdsAndMarkersvariables. As written, it is intended for one map instance on a page. - The marker is shown only when both coordinates are non-null.
- The
googlemapmarkerdirective expects each repeated scope to expose its item asrow. Keep that alias in theng-repeat, or update the script consistently. - The map center and zoom are hard-coded in the directive. Change
stockholmandzoomif your application needs a different initial view.
For a broader component-development playground, see Documentation:Turnkey sample Board Map Balls Gantt.
