Try our conversational search powered by Generative AI!

Anders Hattestad
Nov 30, 2013
  7661
(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 and the never-ending story of the missing globe!

I've worked with Optimizely CMS for 14 years, and there are two things I'm obsessed with: Link validation and the globe that keeps disappearing on...

Tomas Hensrud Gulla | Apr 18, 2024 | Syndicated blog

Visitor Groups Usage Report For Optimizely CMS 12

This add-on offers detailed information on how visitor groups are used and how effective they are within Optimizely CMS. Editors can monitor and...

Adnan Zameer | Apr 18, 2024 | Syndicated blog

Azure AI Language – Abstractive Summarisation in Optimizely CMS

In this article, I show how the abstraction summarisation feature provided by the Azure AI Language platform, can be used within Optimizely CMS to...

Anil Patel | Apr 18, 2024 | Syndicated blog

Fix your Search & Navigation (Find) indexing job, please

Once upon a time, a colleague asked me to look into a customer database with weird spikes in database log usage. (You might start to wonder why I a...

Quan Mai | Apr 17, 2024 | Syndicated blog

The A/A Test: What You Need to Know

Sure, we all know what an A/B test can do. But what is an A/A test? How is it different? With an A/B test, we know that we can take a webpage (our...

Lindsey Rogers | Apr 15, 2024

.Net Core Timezone ID's Windows vs Linux

Hey all, First post here and I would like to talk about Timezone ID's and How Windows and Linux systems use different IDs. We currently run a .NET...

sheider | Apr 15, 2024