A critical vulnerability was discovered in React Server Components (Next.js). Our systems remain protected but we advise to update packages to newest version. Learn More

Anders Hattestad
Nov 30, 2013
  5290
(0 votes)

Google map selector dojo property for EPiServer 7

I have with collaboration with a colleague of mine made a Google map selector. It’s plain but effective.

The basic concept is like this:

image A user can add a query to search for.
image After searching the dropdown got populated with possible hits
image After selecting one we display the result.
image After saving an reload of the page we show the ma

To archive this we have as always tag over definition like this

Code Snippet
  1. [ContentType(GUID = "64D8C8D0-87E8-4894-B8EA-B4654593741E", AvailableInEditMode = true, DisplayName = "Map")]
  2. [BlockImageUrl("Map.png")]
  3. public class MapBlock : BaseBlock
  4. {
  5.     [UIHint("GoogleMapSelector")]
  6.     public virtual string Coords { get; set; }
  7. }

as you can se we store the value of the search as text, so don’t need a own property. But we need a Descriptor to trigger our JavaScript. And that code look like this

  1. [EditorDescriptorRegistration(TargetType = typeof(string), UIHint = "GoogleMapSelector")]
  2. public class GoogleMapSelectorDescriptor : EditorDescriptor
  3. {
  4.     public override void ModifyMetadata(Shell.ObjectEditing.ExtendedMetadata metadata, IEnumerable<Attribute> attributes)
  5.     {
  6.         ClientEditingClass = "itera.editors.GoogleMapSelector";
  7.         base.ModifyMetadata(metadata, attributes);
  8.     }
  9. }

In the actually script we have an iframe with the Google map JavaScript added.

  1. <iframe src=\"/modules/GoogleMap.aspx\" data-dojo-attach-point=\"iFrameBody\" data-dojo-attach-event='onload:_drawChart' style=\"width:300px;height:300px;\" ></iframe>

And on that page have JavaScript functions that we call from the dojo functions. There is also important to notice that one should draw the chart after all other stuff have been done, else we looses focus to the function and

GoogleMapSelector.js
  1. /*
  2. Dojo widget for editing a list of strings. Also see property type PropertyStringList in /Models/Properties.
  3. */
  4.  
  5. define([
  6.     "dojo/_base/array",
  7.     "dojo/_base/connect",
  8.     "dojo/_base/declare",
  9.     "dojo/_base/lang",
  10.  
  11.     "dijit/_CssStateMixin",
  12.     "dijit/_Widget",
  13.     "dijit/_TemplatedMixin",
  14.     "dijit/_WidgetsInTemplateMixin",
  15.  
  16.     "dijit/form/Textarea",
  17.  
  18.     "epi/epi",
  19.     "epi/shell/widget/_ValueRequiredMixin"
  20. ],
  21. function (
  22.     array,
  23.     connect,
  24.     declare,
  25.     lang,
  26.  
  27.     _CssStateMixin,
  28.     _Widget,
  29.     _TemplatedMixin,
  30.     _WidgetsInTemplateMixin,
  31.  
  32.     Textarea,
  33.     epi,
  34.     _ValueRequiredMixin
  35. ) {
  36.  
  37.     return declare("itera.editors.GoogleMapSelector", [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin, _CssStateMixin, _ValueRequiredMixin], {
  38.  
  39.         templateString: "<div class=\"dijitInline\">"+
  40.                         "   <div data-dojo-attach-point=\"stateNode, tooltipNode\">"+
  41.                         "        <input type='text' data-dojo-attach-point=\"searchValue\" />" +
  42.                         "        <input type='button' data-dojo-attach-event=\"onclick:_onClick\" value=\"Search\" /><br />"+                            
  43.                         "        <select data-dojo-attach-point='searchResult' data-dojo-attach-event='onchange:_onChange' ><option value=''>skriv inn tekst og trykk Search</option></select><hr />" +
  44.                         "        <iframe src=\"/modules/GoogleMap.aspx\" data-dojo-attach-point=\"iFrameBody\" data-dojo-attach-event='onload:_drawChart' style=\"width:300px;height:300px;\" ></iframe><br />" +
  45.                         "           <input type='button' data-dojo-attach-event=\"onclick:_clearResult\" value=\"Clear\" />" +
  46.                        "       <textarea data-dojo-attach-point=\"text\" data-dojo-attach-event=\"onchange:_onChange\" style=\"width:300px;height:40px;display:block;\" ></textarea>" +
  47.  
  48.                        
  49.                         "    </div>"+
  50.                         "    <br />"+
  51.                         
  52.                         "</div>",
  53.  
  54.         baseClass: "epiStringList",
  55.  
  56.        
  57.  
  58.         intermediateChanges: false,
  59.  
  60.         value: null,
  61.         multiple: true,
  62.  
  63.         onChange: function (value) {
  64.             this.text.value = value;
  65.             // Event
  66.         },
  67.  
  68.         postCreate: function () {
  69.             // call base implementation
  70.             this.inherited(arguments);
  71.             //this.set('value', this.value);
  72.            
  73.            // this.savedValue.value = this.value;
  74.             // Init textarea and bind event
  75.             //this.textArea.set("intermediateChanges", this.intermediateChanges);
  76.             //this.connect(this.textArea, "onChange", this._onTextAreaChanged);
  77.         },
  78.  
  79.         isValid: function () {
  80.             return true;
  81.         },
  82.         
  83.  
  84.         _drawChart: function () {
  85.             if (this.iFrameBody.contentWindow) {
  86.                 if (this.value) {
  87.                     $(this.iFrameBody).css("display", "block");
  88.                     this.iFrameBody.contentWindow.DrawChart(this.value, this.iFrameBody);
  89.                 } else {
  90.                     $(this.iFrameBody).css("display", "none");
  91.  
  92.                 }
  93.             }
  94.         },
  95.         _onClick: function (event) {
  96.             var address = this.searchValue.value;
  97.             var options = this.iFrameBody.contentWindow.DoSearch(address, this.searchResult);
  98.             
  99.             
  100.         },
  101.         _clearResult: function (event) {
  102.             this._set('value', "");
  103.             this.onChange(this.value);
  104.             this._drawChart();
  105.             this.searchResult.innerHTML = "<option value=''>skriv inn tekst og trykk Search</option>";
  106.             this.searchValue.value = "";
  107.             
  108.         },
  109.         
  110.         _onChange: function (event) {
  111.             // summary:
  112.             //    Handles the textbox change event and populates this to the onChange method.
  113.             // tags:
  114.             //    private
  115.             //alert("yo");
  116.             this._set('value', event.target.value);
  117.             this.onChange(this.value);
  118.             this._drawChart();
  119.         },
  120.         // Setter for value property
  121.         _setValueAttr: function (value) {
  122.             this._setValue(value, true);
  123.         },
  124.  
  125.         //_setReadOnlyAttr: function (value) {
  126.         //    this._set("readOnly", value);
  127.         //    this.savedValue.set("readOnly", value);
  128.         //},
  129.  
  130.         // Setter for intermediateChanges
  131.         _setIntermediateChangesAttr: function (value) {
  132.             //this.textArea.set("intermediateChanges", value);
  133.             //this._set("intermediateChanges", value);
  134.         },
  135.  
  136.         // Event handler for textarea
  137.         _onTextAreaChanged: function (value) {
  138.             this._setValue(value, false);
  139.         },
  140.  
  141.         _setValue: function (value, updateTextarea) {
  142.             // Assume value is an array
  143.            // this.savedValue.set("value", value);
  144.             // Assume value is an array
  145.             var list = value;
  146.             if (typeof value === "string") {
  147.                 // Split list
  148.                 list = this._stringToList(value);
  149.  
  150.             } else if (!value) {
  151.                 // use empty array for empty value
  152.                 list = [];
  153.             }
  154.             if (list.length == 1 && list[0] == "")
  155.                 list = [];
  156.            
  157.             var txt = list.join("");
  158.             this._set("value", txt);
  159.  
  160.             if (updateTextarea && list.length>0) {
  161.                
  162.               
  163.                     this.text.value = txt;
  164.                  
  165.  
  166.                
  167.                // this.text.set("value", list.join("\n"));
  168.                 
  169.                 var elements = txt.split('$');
  170.                 this.searchResult.innerHTML = "<option value=\"" + txt + "\">" + elements[1] + "</option>";
  171.  
  172.                 this._drawChart(txt);
  173.  
  174.             }
  175.         }
  176.     });
  177. });

The /Modules/GoogleMap.aspx looks like this

GoogleMap.aspx
  1. <!DOCTYPE html>
  2. <html xmlns="http://www.w3.org/1999/xhtml">
  3. <head runat="server">
  4.     <script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"></script>
  5.     <style>
  6.         html, body, #map-canvas {  height: 300px; margin: 0px; padding: 0px;  width: 300px; }
  7.     </style>
  8.     <script>
  9.         var map;
  10.         var currentMarker;
  11.         function DoSearch(query, obj) {
  12.             geocoder.geocode({ 'address': query }, function (results, status) {
  13.                 var html = "";
  14.                 html += "<option value=''>Resultater for " + query + "</option>";
  15.                 if (status == google.maps.GeocoderStatus.OK) {
  16.                     for (var i = 0; i < results.length; i++) {
  17.                         html += "<option value=\"" + results[i].geometry.location + "$" + results[i].formatted_address + "\">" + results[i].formatted_address + "</option>";
  18.                     }
  19.                 }
  20.                 obj.innerHTML = html;
  21.             });
  22.         }
  23.         function DrawChart(data, obj) {
  24.             var elements = data.toString().split('$');
  25.             var matches = elements[0].toString().substr(1, elements[0].length - 2);
  26.             var location = matches.toString().split(',');
  27.             var myLatlng = new google.maps.LatLng(parseFloat(location[0]), parseFloat(location[1]));
  28.  
  29.             var mapOptions = {
  30.                 draggable: false,
  31.                 zoomControl: false,
  32.                 scrollwheel: false,
  33.                 disableDoubleClickZoom: true,
  34.                 mapTypeControl: false,
  35.                 zoom: 15,
  36.                 center: myLatlng,
  37.                 mapTypeId: google.maps.MapTypeId.ROADMAP
  38.             }
  39.             map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);
  40.  
  41.            
  42.  
  43.             // To add the marker to the map, use the 'map' property
  44.             currentMarker = new google.maps.Marker({
  45.                 position: myLatlng,
  46.                 map: map,
  47.                 title: elements[1]
  48.             });
  49.         }
  50.  
  51.         function initialize() {
  52.             geocoder = new google.maps.Geocoder();
  53.          }
  54.     </script>
  55.  
  56.  
  57. </head>
  58. <body onload="initialize()">
  59.     <form id="form1" runat="server">
  60.         <div>
  61.             <div id="map-canvas"></div>
  62.         </div>
  63.     </form>
  64. </body>
  65. </html>

So after all this is done, we have a block with a Google map selector. Then the last piece is to show the value.

There there are a  little trick. To ensure that the map is shown the JavaScript http://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false needs to be loaded. But you don't want to load that JavaScript on all page requests.

We therefore engineered a construct that we added to the footer of the page

Code Snippet
  1. <script>
  2.     function AddIfGoogleMap(src) {
  3.         if ($(document.body).find('div.googleMap').length>0)
  4.             document.write('<' + 'script src="' + src + '"' +
  5.                            ' type="text/javascript"><' + '/script>');
  6.     }
  7.     AddIfGoogleMap("http://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false");
  8. </script>

This finds if there is a div with class Google Map the JavaScript will be loaded

Then we after this script add this script

Code Snippet
  1. function GetScale(scale, width, height) {
  2.  
  3.  
  4.     if (scale > 0) {
  5.         if (width)
  6.             return Math.ceil(width / scale);
  7.         if (height)
  8.             return Math.ceil(height * scale);
  9.     }
  10.     return 0;
  11.  
  12. }
  13. function GetScaleInner(obj, key) {
  14.     var h = obj.attr("data-image-" + key + "-height");
  15.     var w = obj.attr("data-image-" + key + "-width");
  16.  
  17.     if (h != "" && w != "") {
  18.         return w / h;
  19.     }
  20.     return 0;
  21. }
  22. function ResizeGoogleMap100Procent(obj) {
  23.     var scale = GetScaleInner(obj, "scale");
  24.     obj.css("width", "100%");
  25.     obj.css("height", "");
  26.     var width = obj.width();
  27.     var height = GetScale(scale, width);
  28.     obj.css("height", height);
  29.     obj.css("width", width);
  30.  
  31.     var data = obj.attr("data-google-map-cords");
  32.     var elements = data.toString().split('$');
  33.     var matches = elements[0].toString().substr(1, elements[0].length - 2);
  34.     var location = matches.toString().split(',');
  35.     var myLatlng = new google.maps.LatLng(parseFloat(location[0]), parseFloat(location[1]));
  36.     var mapOptions = {
  37.         draggable: false,
  38.         zoomControl: false,
  39.         scrollwheel: false,
  40.         disableDoubleClickZoom: true,
  41.         mapTypeControl:false,
  42.         zoom: 15,
  43.         center: myLatlng,
  44.         mapTypeId: google.maps.MapTypeId.ROADMAP
  45.     }
  46.     map = new google.maps.Map(obj[0], mapOptions);
  47.     // To add the marker to the map, use the 'map' property
  48.     currentMarker = new google.maps.Marker({
  49.         position: myLatlng,
  50.         map: map,
  51.         title: elements[1]
  52.     });
  53. }
  54. $(document).ready(function () {
  55.     $(document.body).find('div.googleMap').each(function (i) {
  56.  
  57.         ResizeGoogleMap100Procent($(this));
  58.     });
  59. });

With this in place we can add this html code anywhere

  1. <div class="googleMap" data-image-scale-width="16" data-image-scale-height="9" data-epi-use-mvc='True' data-google-map-cords="@Model.Coords" data-epi-property-name='Coords'></div>

And it will show a map with 100% width and 16/9 scale

Nov 30, 2013

Comments

Please login to comment.
Latest blogs
A day in the life of an Optimizely OMVP: Learning Optimizely Just Got Easier: Introducing the Optimizely Learning Centre

On the back of my last post about the Opti Graph Learning Centre, I am now happy to announce a revamped interactive learning platform that makes...

Graham Carr | Jan 31, 2026

Scheduled job for deleting content types and all related content

In my previous blog post which was about getting an overview of your sites content https://world.optimizely.com/blogs/Per-Nergard/Dates/2026/1/sche...

Per Nergård (MVP) | Jan 30, 2026

Working With Applications in Optimizely CMS 13

💡 Note:  The following content has been written based on Optimizely CMS 13 Preview 2 and may not accurately reflect the final release version. As...

Mark Stott | Jan 30, 2026

Experimentation at Speed Using Optimizely Opal and Web Experimentation

If you are working in experimentation, you will know that speed matters. The quicker you can go from idea to implementation, the faster you can...

Minesh Shah (Netcel) | Jan 30, 2026

How to run Optimizely CMS on VS Code Dev Containers

VS Code Dev Containers is an extension that allows you to use a Docker container as a full-featured development environment. Instead of installing...

Daniel Halse | Jan 30, 2026

A day in the life of an Optimizely OMVP: Introducing Optimizely Graph Learning Centre Beta: Master GraphQL for Content Delivery

GraphQL is transforming how developers query and deliver content from Optimizely CMS. But let's be honest—there's a learning curve. Between...

Graham Carr | Jan 30, 2026