Views: 4205
Number of votes: 2
Average rating:

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

    (By Daniel Ovaska , 03 December 2013 10:07, Permanent link)

    Nice! Google maps property is definitely useful. I'll try it out in my next project

    (By Per Magne Skuseth , 14 January 2014 19:43, Permanent link)

    Thanks for sharing!

    (By Joakim Jormelin , 04 March 2014 20:34, Permanent link)

    Anyone got this working with 7.5? I can save values but then property editing won't work for that page anymore...

    (By ulf.kollin , 12 June 2014 10:29, Permanent link)

    I am also trying to fix it for episerver 7.5. Anyone who have done it already?

    (By Mateus , 04 August 2014 12:30, Permanent link)

    I have been able to fix the episerver 7.5 issue. The _stringToList function do not exist, therefore the code breaks when an address is present.

    Just add the code below after the _setValue function on GoogleMapSelector.js

    // Convert a string to a list
    _stringToList: function (value) {

    // Return empty array for
    if (!value || typeof value !== "string") {
    return [];
    }

    // Trim whitespace at start and end
    var trimmed = value.replace(/^\s+|\s+$/g, "");

    // Trim whitespace around each linebreak
    var trimmedLines = trimmed.replace(/(\s*\n+\s*)/g, "\n");

    // Split into list
    var list = trimmedLines.split("\n");

    return list;
    }

    (By Ted , 19 February 2015 11:14, Permanent link)

    This one is made natively for EPiServer 7.5: http://nuget.episerver.com/en/OtherPages/Package/?packageId=EPiServer.GoogleMapsEditor

  Please login to post a comment