November Happy Hour will be moved to Thursday December 5th.

Anders Hattestad
Nov 30, 2013
  7868
(2 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

Dec 3, 2013 10:07 AM

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

Per Magne Skuseth
Per Magne Skuseth Jan 14, 2014 07:43 PM

Thanks for sharing!

Joakim Jormelin
Joakim Jormelin Mar 4, 2014 08:34 PM

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

ulf.kollin@capesand.se
ulf.kollin@capesand.se Jun 12, 2014 10:29 AM

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

Mateus
Mateus Aug 4, 2014 12:30 PM

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;
}

Ted
Ted Feb 19, 2015 11:14 AM

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

Please login to comment.
Latest blogs
Optimizely SaaS CMS + Coveo Search Page

Short on time but need a listing feature with filters, pagination, and sorting? Create a fully functional Coveo-powered search page driven by data...

Damian Smutek | Nov 21, 2024 | Syndicated blog

Optimizely SaaS CMS DAM Picker (Interim)

Simplify your Optimizely SaaS CMS workflow with the Interim DAM Picker Chrome extension. Seamlessly integrate your DAM system, streamlining asset...

Andy Blyth | Nov 21, 2024 | Syndicated blog

Optimizely CMS Roadmap

Explore Optimizely CMS's latest roadmap, packed with developer-focused updates. From SaaS speed to Visual Builder enhancements, developer tooling...

Andy Blyth | Nov 21, 2024 | Syndicated blog

Set Default Culture in Optimizely CMS 12

Take control over culture-specific operations like date and time formatting.

Tomas Hensrud Gulla | Nov 15, 2024 | Syndicated blog