Sitecore Custom Google Map Field

So I’ve playing around with Sitecore CMS lately. For you that don’t know about Sitecore, you could refer to this Link.

In this post, I would like to share how to create a custom field in Sitecore. The idea for this post is, we will create a custom Sitecore’s field, that could set a latitude and longitude value from Google Map. It’ll consist of a textbox control that will hold the longitude and latitude and 2 buttons; set location and clear location. When we click set locationbutton, it’ll open up a popup windows, and you can point to a specific location, whether the clear button will clear our field value.

I found a very great article from Alex Shyba and Adam Conn that will tell you more specific about how we create a custom field in Sitecore, or you always could find a Sitecore’s documentation in Sitecore Developer Network.

So basically, our custom field will look like this

image

Here’s the step about how we do this Smile.

Create visual studio project

1

Add reference to Sitecore.Kernel library

Don’t forget to add reference to sitecore.kernel.dll and set it’s copy local properties to false.

Create our custom field class

Create our custom field class that inherited from Sitecore.Shell.Applications.ContentEditor.Text and Sitecore.Shell.Applications.ContentEditor.IContentField

And here’s our initial class looks like

   1: #region Implementation of IContentField

   2:

   3:         public string GetValue()

   4:         {

   5:             throw new System.NotImplementedException();

   6:         }

   7:

   8:         public void SetValue(string value)

   9:         {

  10:             throw new System.NotImplementedException();

  11:         }

  12:

  13:         #endregion

GetValue and SetValue method is just a simple getter and setter method that comes from IContentField interface. And here’s our code looks like after put our simple getter and setter logic.

   1: public class GoogleMapField : Text, IContentField

   2:     {

   3:         #region Implementation of IContentField

   4:

   5:         public string GetValue()

   6:         {

   7:             return Value;

   8:         }

   9:

  10:         public void SetValue(string value)

  11:         {

  12:             Value = value;

  13:         }

  14:

  15:         #endregion

  16:     }

There’s a couples thing that we should override from our Text base class. It’s about how we render our custom field control in content editor (if you want to customize it of course) and how we handle message from content editor. For example, we click set location or clear location from sitecore content editor… Wait, you said set location. And what is that? Yep, what i mean is these buttons:

3

Here’s the example of our HandleMessage method:

   1: public override void HandleMessage(Sitecore.Web.UI.Sheer.Message message)

   2: {

   3:     //let base class do what it's need to do.

   4:     base.HandleMessage(message);

   5:

   6:     //And after that, we will take a charge from here 😛

   7:     if (message["id"] != ID || string.IsNullOrWhiteSpace(message.Name)) return;

   8:

   9:     switch (message.Name)

  10:     {

  11:         case "customGmap:SetLocation": //It defined in core database

  12:             Sitecore.Context.ClientPage.Start(this, "SetLocation");

  13:             return;

  14:         case "customGmap:ClearLocation": //it also defined in core database

  15:             Sitecore.Context.ClientPage.Start(this, "ClearLocation");

  16:             return;

  17:     }

  18:

  19:     if (Value.Length > 0) SetModified();

  20:

  21:     Value = string.Empty;

  22: }

If you see the code snippet above, we have a check logic for Message.Name, which is tell you which action that we do in content editor. Where’s that customGmap:SetLocation comes from? We have to set it in sitecore core database. But before we do that, we should create our sitecore configuration for our custom field first.

So for me, I’ll create a new application configuration file that named MyTestApp.SitecoreCustom.Configuration.config in App_Config/Include in our project solution. Here’s our configuration looks like:

4

   1: <configuration xmlns:x="http://www.sitecore.net/xmlconfig/">

   2:   <sitecore>

   3:     <controlSources>

   4:       <source mode="on" namespace="MyTestApp.SitecoreCustom.Fields.GMapField" assembly="MyApp.SitecoreCustom" prefix="custom" />

   5:     </controlSources>

   6:   </sitecore>

   7: </configuration>

I found a nice article by Brian Pedersen that will tell you about how sitecore configuration works. Smile This configuration will be used in setting up our sitecore custom field item in core database later on.

So now we have our half baked custom field class and configuration file. It’s time to update our sitecore. We need to switch our sitecore database from master to core.

5 6

note: don’t forget to login in Desktop Mode

7

Open sitecore content editor and go to /sitecore/system/Field types. Create a new folder called Custom Typesthat inherited from /sitecore/templates/Common/Folder. Create our new custom field under custom types folder that we created before and inherited from /sitecore/templates/System/Templates/Template field type.

image

If you look at to control property, it’s filled with custom:GoogleMapField. It’s comes from our previous configuration file that we created. custom is our prefix, and GoogleMapField is our class name. Let say you put other value in configuration for prefix, ex: myfield, so have to put myfield:GoogleMapField.

As I said before, we need a 2 buttons, one for open a pop up windows that will show up a google map that we could pick our desire location, and the other one is for clear location value.

What we need to do is, create a new folder under GMapLocationPicker item and name it’s folder item Menu.Create 2 items under menu folder called Set Location and Clear Location that inherited from /sitecore/templates/System/Menus/Menu item.

8

Now we select our Set Location menu item, and look at to Message property.

image

Familiar with this one? or do you feel something like deja vu? Smile with tongue out Yep, it’s in our HandleMessage method from our custom field class. Let me put HandleMessage method to make it more clear.

   1: switch (message.Name)

   2: {

   3:     case "customGmap:SetLocation": //It defined in core database

   4:         Sitecore.Context.ClientPage.Start(this, "SetLocation");

   5:         return;

   6:     case "customGmap:ClearLocation": //it also defined in core database

   7:         Sitecore.Context.ClientPage.Start(this, "ClearLocation");

   8:         return;

   9: }

This means, when we set customGmap:SetLocation(id=$Target) in our sitecore menu item, it’ll send Message.Name parameter as customGmap:SetLocation.

So after we finished setup our sitecore item for our custom fields, let’s open again our custom class in our project. Notice in above code snippet, we have

   1: case "customGmap:SetLocation": //It defined in core database

   2:     Sitecore.Context.ClientPage.Start(this, "SetLocation");

”SetLocation” is a callback method from ClientPage. We need to define it in our class. Here’s the callback method for SetLocation and ClearLocation.

   1: protected void SetLocation(ClientPipelineArgs args)

   2: {

   3:     //Check if popup windows is postback

   4:     if (args.IsPostBack)

   5:     {

   6:         //check whether popup windows has selected value

   7:         if (args.HasResult && Value.Equals(args.Result) == false)

   8:         {

   9:             //tell content editor that value in field is modified

  10:             SetModified();

  11:

  12:             //set current field value with selected value from popup window

  13:             SetValue(args.Result);

  14:         }

  15:     }

  16:     else

  17:     {

  18:         //show popup

  19:         //get popup control that named GmapLocationPickerDialog

  20:         var url = UIUtil.GetUri("control:GmapLocationPickerDialog");

  21:

  22:         //Try to get Current Value (if it previously has a value)

  23:         var value = GetValue();

  24:         if (!string.IsNullOrEmpty(value))

  25:         {

  26:             //passing current value to querystring so that it could read by our popup window

  27:             url = string.Format("{0}&value={1}",

  28:                 url, value);

  29:         }

  30:

  31:         //Show our popup dialog

  32:         SheerResponse.ShowModalDialog(url, "800", "600", "", true);

  33:

  34:         //Wait popup dialog for postback

  35:         args.WaitForPostBack();

  36:     }

  37: }

  38:

  39: protected void ClearLocation(ClientPipelineArgs args)

  40: {

  41:     //set empty value

  42:     SetValue(string.Empty);

  43:

  44:     //tell content editor that value in field is modified

  45:     SetModified();

  46: }

The next thing is, we created our custom Sitecore XAML (Sheer UI) dialog that will be used for select location from Google Map. Here’s a very good article bout Sheer UI by Brian Pedersen.

Basically, Sheer UI has a same concept with ASPX. Whether it has UI file and code beside file. Here’s a sitecore documentation about Sheer UI.

Here’s our basic Sheer UI for location picker dialog windows

   1: <?xml version="1.0" encoding="utf-8" ?>

   2: <control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense">

   3:   <GmapLocationPickerDialog>

   4:     <FormDialog ID="Dialog" Icon="Network/32x32/environment_view.png"

   5:                 Header="Google Map Location Picker"

   6:                 Text="Select desired location"

   7:                 OKButton="Select">

   8:

   9:       <!--Define Code Beside-->

  10:       <CodeBeside Type="MyApp.SitecoreCustom.GMapField.Dialog.GmapLocationPickerDialog, MyApp.SitecoreCustom"/>

  11:

  12:       <Groupbox Header="Search Location based on address" Style="height:50px;">

  13:         <table width="100%">

  14:           <tr valign="center">

  15:             <td width="70%">

  16:               <div class="form-element">

  17:                 <Edit ID="TextBox_SearchAddress"/>

  18:               </div>

  19:             </td>

  20:             <td>

  21:               <a href="#" id="Link_UpdateMap">Point To Address</a>

  22:             </td>

  23:           </tr>

  24:         </table>

  25:       </Groupbox>

  26:       <Groupbox Header="Map" Style="height:85%; overflow:hidden;">

  27:         <Border class="mapCanvas" ID="mapCanvas"/>

  28:       </Groupbox>

  29:

  30:       <Edit ID="TextBox_SelectedLocation" style="display:none;" />

  31:     </FormDialog>

  32:   </GmapLocationPickerDialog>

  33: </control>

  • GmapLocationPickerDialog: our dialog control name.
  • FormDialog:identifies that this control is a dialog.
  • CodeBeside: identifies our logic for dialog control.

Since we will use google map as our source of location, we need to add reference for google api javascript in our Sheer UI. Add these line of codes after <GmapLocationPickerDialog>.

   1: <!--Init Script-->

   2: <Script Src="http://maps.googleapis.com/maps/api/js?sensor=true" Key="gmapAPI" Language="javascript"/>   1:

   3: <Script Src="Controls/Lib/jQuery/jquery.js" Key="gmapLocationPicker" Language="javascript"/>

   4: <Script Src="Applications/MyApp/GmapField/js/gmapLocationPicker.js" Key="gmapLocationPicker" Language="javascript"/>

   5:

   6: <Script Language="javascript">

   7:   var $J = jQuery.noConflict();

   8:

   9:   $J(document).ready(function(){

  10:     initializeMap();

  11:

  12:     $J('a#Link_UpdateMap').click(function(e){

  13:       e.preventDefault();

  14:

  15:       var addressTxtSelector = 'input#TextBox_SearchAddress';

  16:       if($J(addressTxtSelector).val() == ""){

  17:         $J(addressTxtSelector).closest('div.form-element').addClass('error');

  18:

  19:         return;

  20:       }

  21:

  22:     $J(addressTxtSelector).closest('div.form-element').removeClass('error');

  23:       updateMapLocation($J(addressTxtSelector).val());

  24:     } );

  25:   });

Notice that we have

   1: var $J = jQuery.noConflict();

It will prevent jquery from conflicting with Sitecore javascript helper, prototype.js file. It always doesn’t happy if we use jQuery with sitecore without using noconflict. You could refer to Alex Shyba article about this.

gmapLocationPicker.js is our javascript file helper to initalize google map. Here’s our javascript helper function that we put in separate file.

   1: var map;

   2: var geocoder;

   3: var marker;

   4:

   5: //return current position value by using Latitude Longitude Format

   6: function GetCurrentLocation() {

   7:     var currentLoc = $J('input#TextBox_SelectedLocation').val();

   8:

   9:     return currentLoc;

  10: }

  11:

  12: function initializeMap() {

  13:

  14:     var currentLoc = GetCurrentLocation();

  15:     var lat = -34.397;

  16:     var lng = 150.644;

  17:

  18:     if (currentLoc != "") {

  19:         var position = currentLoc.split(",");

  20:

  21:         if (position.length == 2) {

  22:             lat = parseFloat(position[0]);

  23:             lng = parseFloat(position[1]);

  24:         }

  25:     }

  26:

  27:     if (map == null) {

  28:         var mapOptions = {

  29:             zoom: 8,

  30:             center: new google.maps.LatLng(lat, lng),

  31:             mapTypeId: google.maps.MapTypeId.ROADMAP,

  32:             panControl: false,

  33:             zoomControl: true,

  34:             scaleControl: true,

  35:             streetViewControl: false

  36:         };

  37:

  38:         var mapDiv = $J('div.mapCanvas').get(0);

  39:

  40:         map = new google.maps.Map(mapDiv, mapOptions);

  41:         placeMarker(mapOptions.center);

  42:

  43:         //add event listener

  44:         google.maps.event.addListener(map, 'click', function (event) {

  45:

  46:             if (marker != null) {

  47:                 placeMarker(event.latLng);

  48:             }

  49:         });

  50:

  51:         google.maps.event.addListener(marker, 'dragend', function (event) {

  52:             map.panTo(event.latLng);

  53:

  54:             updateLocation(event.latLng);

  55:         });

  56:     }

  57: }

  58:

  59: function updateMapLocation(address) {

  60:     if (address != '') {

  61:

  62:         if (geocoder == null) {

  63:             geocoder = new google.maps.Geocoder();

  64:         }

  65:

  66:         geocoder.geocode({ 'address': address }, function (result, status) {

  67:             if (status == google.maps.GeocoderStatus.OK) {

  68:                 //map.setCenter(result[0].geometry.location);

  69:

  70:                 placeMarker(result[0].geometry.location);

  71:             } else {

  72:                 alert("Geocode was not successfull for the following reason: " + status);

  73:             }

  74:         });

  75:     }

  76: }

  77:

  78: function placeMarker(latLng) {

  79:     if (marker == null) {

  80:         marker = new google.maps.Marker({

  81:             map: map,

  82:             draggable: true

  83:         });

  84:     }

  85:

  86:     marker.setPosition(latLng);

  87:     marker.setAnimation(google.maps.Animation.DROP);

  88:

  89:     map.panTo(latLng);

  90:     updateLocation(latLng);

  91: }

  92:

  93: function updateLocation(latLng) {

  94:     //document.getElementById('TextBox_SelectedLocation').value = latLng.Xa.toString() + "," + latLng.Ya.toString();

  95:     $J('input#TextBox_SelectedLocation').val(latLng.Xa.toString() + "," + latLng.Ya.toString());

  96: }

And here’s our code beside for GmapLocationPickerDialog Sheer UI.

   1: using Sitecore.Web;

   2: using Sitecore.Web.UI.HtmlControls;

   3: using Sitecore.Web.UI.Pages;

   4: using Sitecore.Web.UI.Sheer;

   5:

   6: namespace MyApp.SitecoreCustom.GMapField.Dialog

   7: {

   8:     public class GmapLocationPickerDialog : DialogForm

   9:     {

  10:         #region Control

  11:

  12:         public Edit TextBox_SelectedLocation;

  13:

  14:         #endregion

  15:

  16:         #region Control Event

  17:

  18:         protected override void OnLoad(System.EventArgs e)

  19:         {

  20:             base.OnLoad(e);

  21:

  22:             var value = WebUtil.GetQueryString("value");

  23:             if (!string.IsNullOrWhiteSpace(value) && string.IsNullOrWhiteSpace(TextBox_SelectedLocation.Value))

  24:             {

  25:                 TextBox_SelectedLocation.Value = value;

  26:             }

  27:         }

  28:

  29:         protected override void OnOK(object sender, System.EventArgs args)

  30:         {

  31:             SheerResponse.SetDialogValue(TextBox_SelectedLocation.Value);

  32:             base.OnOK(sender, args);

  33:         }

  34:

  35:         #endregion

  36:     }

  37: }

How about the installation itself?

  • You could use visual studio build eventthat will automatically copy your file to your sitecore site.
  • Manually copy your configuration, dll, javascript and Sheer UI xml file.
    • Sitecore Configuration: copy this file under [Your Sitecore Website]/App_Config/Include
    • Dll: copy to [Your Sitecore Website]/bin
    • Sheer UI xml file and javascript file: copy to [Your Sitecore Website]/sitecore/shell/Applications/[Your Custom Folder]/

How we use it?

You could create your new template / update the exsiting one.

image

And you could use it from content editor, and when you click Set Location you should see like this

image

And when you set select it should be updated your field value with your selected location.

9

And that’s it folks. Hopefully you find something useful in here Smile

Update

Credit to Lars Vistrup Skov for find solution to make it work on sitecore 8.

Happy Coding, Cheers.

Advertisements

14 thoughts on “Sitecore Custom Google Map Field

  1. Thank you! It is really useful post, but I didn’t see the implementation of showing the map image under the coordinates. So, if someone wants to have this image could add the following code in the class GoogleMapField :

    protected override void OnLoad(System.EventArgs e)
    {
    if (!Sitecore.Context.ClientPage.IsEvent)
    {
    if (!string.IsNullOrEmpty(this.Value))
    {
    HtmlGenericControl mapImage = new HtmlGenericControl(“img”);
    mapImage.ID = “mapImage”;
    mapImage.Attributes[“src”] = string.Format(“http://maps.googleapis.com/maps/api/staticmap?center={0}&zoom=11&size=800×300&sensor=false&markers={0}”, this.Value);

    this.Controls.Add(mapImage);
    }
    }

    base.OnLoad(e);
    }

  2. Another point is that the current tutorial may NOT WORK in SITECORE v7.0.. If you want to use it with this version of Sitecore you should make some changes in gmapLocationPicker.js file :

    – In function updateLocation(latLng) CHANGE “latLng.Xa” to “latLng.ob” and “latLng.Ya” to “latLng.pb”.

    Also in the style shet (it should be named “[your project directory]\Website\sitecore\shell\Themes\Standard\Default\Default.css” and if not work try with “Dialogs.css”) add this:

    .gm-style
    {
    position: relative !important;
    top: 0 !important;
    left: 0 !important;
    }

    These steps work to me.

  3. Is there a way to make this work in page editor? I created a new custom experience button that launches the dialog from page editor but the current code of the dialog does not work this way?

  4. Hi there,

    Thanks for the location picker field. I cant get it to work in sitecore 8.
    I install the package v1.1 and set the field in my template. When i go to the page, all i see is a textbox. There is no “Set Location” or “Clear Location” button on that field. Am i missing a step?

    Thanks

  5. I found the fix for Sitecore 8!

    In the Core database, change the template of the node:

    /sitecore/system/Field types/Custom Types

    from

    /sitecore/templates/System/Node

    to

    /sitecore/templates/Common/Folder

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s