🚀 Welcome to MDriven Learn –  MDriven is now on Discord!  Don’t miss the latest Release Notes.
Google Map with Turnkey
This page was created by Alexandra on 2018-10-27. Last edited by Wikiadmin on 2026-07-29.

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, longitude 18.073, with zoom level 4.
  • Creates one marker for each row rendered with the googlemapmarker directive.
  • Updates a marker when the row's lat, lng, or color value 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 AddMarker action when the user clicks the map.
  • Makes markers draggable and writes the final position to row.lat and row.lng when dragging ends.

Prerequisites

Before adding the component, prepare the following:

  1. Load the Google Maps JavaScript API for the Turnkey application. The Google Maps API requires an API key obtained from Google's developer console.
  2. Create the component assets in the model's AssetsTK area, following the component strategy used for Google Charts.
  3. 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.
  4. Add a collection of marker rows to the ViewModel. Each row needs the properties described below.
  5. Add an AddMarker action that creates a row using the two ViewModel variables populated by the map click: vLat and vLng.

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

  1. Open the Turnkey view containing the component. Confirm that the map is visible and that you can pan and zoom it.
  2. Ensure that a row with both lat and lng creates a marker at the expected location.
  3. Change a row's latitude, longitude, or color in a view that exposes the same server object. Confirm that the marker updates.
  4. Delete a marker row. Confirm that its marker disappears.
  5. Click an unused position on the map. Confirm that AddMarker creates a row with the clicked coordinates and that a new marker appears.
  6. Drag a marker and release it. Confirm that its lat and lng values update.

Limits and implementation notes

  • The script uses global map and dictionaryOfIdsAndMarkers variables. As written, it is intended for one map instance on a page.
  • The marker is shown only when both coordinates are non-null.
  • The googlemapmarker directive expects each repeated scope to expose its item as row. Keep that alias in the ng-repeat, or update the script consistently.
  • The map center and zoom are hard-coded in the directive. Change stockholm and zoom if your application needs a different initial view.

For a broader component-development playground, see Documentation:Turnkey sample Board Map Balls Gantt.

See also