If you want to follow this little tutorial then download the
current V5 source by following
my previous post, and get yourself set up and the source
running. This post will focus more on the Umbraco
fundamentals/code than the underlying JS code that makes the Google
Map Geocode and Marker Move etc.. If you want more info on that
then
see the Google Map Post.
As with most blog posts, I will explain things in my own way so
the terminology may not be 100% correct, but I hope at the end of
this post you should be able to easily create your property
editor. also before I start if you are coming from Umbraco v4
and want to know what the hell a property editor is… Well its sort
of the new name for DataTypes.
References
Before I start there are some good references, albeit things are
still changing so they may need to be updated
Wiki post
http://jupiter.umbraco.org/Developing%20Property%20Editors.ashx
Tim's Post
http://www.nibble.be/?p=104
MVC validation data annotations
Lastly I would highly recommend you read up about MVC validation
data annotations, as you use these for the EditorModel and
prevalueModel model
http://www.asp.net/mvc/tutorials/validation-with-the-data-annotation-validators-cs
Simple Example
When I started messing around, I found the simplest example of a
property editor was to open up the v5 source and go to the
'Umbraco.Cms.Web.PropertyEditors' project and open up the TrueFalse
property editor. As you can see this is a very, very simple
property editor and is made of two files 'TrueFalseEditor' and
'TrueFalseEditorModel'. Notice the naming convention of
PropertyNameEditor and
PropertyNameEditorModel, this is kept
throughout.


The only file that's really doing anything here is the
'TrueFalseEditorModel', if you open it up you can see its a simple
class with a public bool property (Decorated with data annotations
- See link in references above) and it inherits from 'EditorModel'
- That's it, which is why its called the 'Model' (I look at this
like scaffolding in MVC, I guess its probably the something similar
under the hood?).
It literally is the model of your property editor, in this case
with are just trying to display something which enables a user to
choose and save a true/false value. So when this bool
property in your model is picked up by Umbraco, it will render it
as a checkbox in the view. The second file 'TrueFalseEditor' in
this case is just used to return your EditorModel to Umbraco and
also has some identifiers in the decoration, it has a unique GUID
which is the ID, an alias and a name - But in short all you need to
know for this example is that it returns your EditorModel which is
rendered as a checkbox.
So if we changed the property named 'Value' in the Model from a
bool to say a string, then in the admin it would get rendered as
textbox… Same if we changed it from a bool to say an int, it would
get rendered as a textbox that only accepts an integer! Yes
validation is all handled for you! Pretty cool hey…
TIPS:
1) I'm not sure on the terminology here, but Property editors
are fully compiled meaning that if you have any assets (Custom
Views, JS & CSS files etc…) they need to be marked as embedded
resource so they are compiled into the DLL. We'll cover this again
but to do this you just 'right click' on the files and go to
'properties' and change build action to embedded resource (see
below)

2) Whether this will be sorted on full release or not I don't
know, because I don't know if its an MVC or Umbraco thing. But if
you make some changes to your property editor, or add some more
resource files it doesn't always show up when Umbraco is run.
To kick the changes into effect you need to delete you ASP.NET temp
files and then rebuild the solution (Bit of a pain but not a show
stopper).
Structure
As you will see from other Property Editors in the v5 source the
file/folder structure is pretty simple - Two folders:
Views
Resources
Which hold what the say on the tin… any custom views go into the
Views folder and your resources (CSS, JS, Images) go into the
Resources folder. all you classes then go in the root of the
editor folder, using the naming convention I mentioned above. Again
don't forget any views/resources added have to marked as 'embedded
resource' or your property editor won't work.
The gMap Model
I have to say, (and I hope you'll agree after reading the above)
making a quick, simple'ish custom datatype in v5 really is a breeze
- I love the way you can just create your model class and don't
have to worry about creating the UI or worry about how to save the
values… It just works. But for our Google Map property editor
we need to add some custom code to the view, so we can show the
map, autocomplete textbox for locations and a few other things.
But again, the v5 guys have made doing this SUPER easy!
Before we do that we still need to create our base model and editor
class. So just like the other property editors, open up the
v5 source and in the 'Umbraco.Cms.Web.PropertyEditors' project
create a folder called 'GoogleMap' and create the following
files
Views (Folder)
Resources (Folder)
GoogleMapEditor.cs
GoogleMapEditorModel.cs

Now we just need to add the properties to our Model - Lets work
our what we need, and then we can relate that to the model - All I
want to store is:
- The Location The Users Types In (string)
- The longitude from the gMaps GeoCoding (User doesn't use
these, so they can ben hidden)
- The latitude from the gMaps GeoCoding (User doesn't use
these, so they can ben hidden)
Based on this I'll create both the editor and editorModel just
like its been done in the simple TrueFalse example above:
Open GoogleMapEditorModel.cs and add
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using Umbraco.Cms.Web.EmbeddedViewEngine;
using Umbraco.Cms.Web.Model.BackOffice.PropertyEditors;
using Umbraco.Cms.Web.PropertyEditors.Tags;
namespace Umbraco.Cms.Web.PropertyEditors.GoogleMap
{
public class GoogleMapEditorModel : EditorModel
{
[Required(ErrorMessage = "Postcode/location is required")]
public string Postcode { get; set; }
[HiddenInput(DisplayValue = false)]
public string Long { get; set; }
[HiddenInput(DisplayValue = false)]
public string Lat { get; set; }
}
}
I'm not going to go into detail about the attributes I have
decorated the properties with, as I mentioned above you need to go
and read up about 'MVC validation data annotations' as they are an
MVC thing not a specific Umbraco thing. But I hope just be
looking at the code you get the idea of what they are doing.
Open GoogleMapEditor.cs and add
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using Umbraco.Cms.Web.Model.BackOffice.PropertyEditors;
namespace Umbraco.Cms.Web.PropertyEditors.GoogleMap
{
[PropertyEditor("E8FA2010-D72E-4F13-9C88-9B79F8466934", "GoogleMap", "GoogleMap")]
public class GoogleMapEditor : PropertyEditor<GoogleMapEditorModel>
{
public override GoogleMapEditorModel CreateEditorModel()
{
return new GoogleMapEditorModel();
}
}
}
As you can see a very simple bit of code… And you could now
build this code, and it would work (So to speak) but you would see
a textbox that required some text in the admin and it would have
two hidden inputs which would do nothing! Awesome hey! What we need
to do now is add a custom view to start making this work.
Adding A Custom View
To add a custom view for our property editor we just need to
create the view in the 'Views' folder and mark the Editor Model as
an 'EmbeddedView' with the path to the view. So lets do that,
create your View and add it into the views folder keeping the same
naming convention as the actual editor.
GoogleMapEditor.cshtml
And copy and paste the following in and save it
@inherits WebViewPage<Umbraco.Cms.Web.PropertyEditors.GoogleMap.GoogleMapEditorModel>
@using Umbraco.Cms.Web;
@using ClientDependency.Core;
@using ClientDependency.Core.Mvc;
@using System.Web.Helpers;
@using System.Web.Mvc;
@using System.Web.Mvc.Ajax;
@using System.Web.Mvc.Html;
@using System.Web.Routing;
@using System.Web.WebPages;
@using Microsoft.Web.Mvc;
@using Umbraco.Cms.Web.PropertyEditors.GoogleMap
Now in your EditorModel class you just need to decorate the
class with the following (NAMESPACES ARE CASE SENSITIVE, so
make sure you double check them - This has caught me out
before)
[EmbeddedView("Umbraco.Cms.Web.PropertyEditors.GoogleMap.Views.GoogleMapEditor.cshtml", "Umbraco.Cms.Web.PropertyEditors")]
public class GoogleMapEditorModel : EditorModel
Hopefully you can see the two main things we have done here, in
the view we have added the '@inherits WebViewPage<>' and on
the EditorModel added '[EmbeddedView()]' I think these are pretty
self explanatory so won't go into anymore detail on these.
But in short, the your telling the view to use the EditorModel as
the 'model' in the view so you have access to all the properties
and also just making sure Umbraco knows that the EditorModel is now
an EmbeddedView with the path to it.
Now things are going to move a little faster here because we are
just going to add the HTML, JS & CSS… Again I mentioned at the
beginning of the blog I wasn't going to go into much detail about
the Google Map stuff as its all in my previous post. So with
that, open up your new custom view and replace it with the
following and save it:
@inherits WebViewPage<Umbraco.Cms.Web.PropertyEditors.GoogleMap.GoogleMapEditorModel>
@using Umbraco.Cms.Web;
@using ClientDependency.Core;
@using ClientDependency.Core.Mvc;
@using System.Web.Helpers;
@using System.Web.Mvc;
@using System.Web.Mvc.Ajax;
@using System.Web.Mvc.Html;
@using System.Web.Routing;
@using System.Web.WebPages;
@using Microsoft.Web.Mvc;
@using Umbraco.Cms.Web.PropertyEditors.GoogleMap
<script type="text/javascript">
var PostCodeid = "#@Html.IdFor(x => Model.Postcode)";
var longval = "#@Html.IdFor(x => Model.Long)";
var latval = "#@Html.IdFor(x => Model.Lat)";
</script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
@{
Html
.RequiresJsResource(typeof(GoogleMapEditor), "Umbraco.Cms.Web.PropertyEditors.GoogleMap.Resources.gMap.js")
.RequiresCssResource(typeof(GoogleMapEditor), "Umbraco.Cms.Web.PropertyEditors.GoogleMap.Resources.gMap.css");
}
<p>@Html.TextBoxFor(x => Model.Postcode, new { @class = "postcode" }) <input type="submit" id="findbutton" value="Find" /></p>
<p>@Html.TextBox("latTextBox", Model.Lat, new { @class = "latTextBox", @readonly = "readonly" }) @Html.TextBox("longTextBox", Model.Long, new { @class = "longTextBox", @readonly = "readonly" })</p>
<div id="geomap" style="width: 350px; height: 350px;">
<p>
Loading Please Wait...</p>
</div>
@Html.HiddenFor(x => Model.Lat)
@Html.HiddenFor(x => Model.Long)
As this is a simple view, you should be able to see what
everything is - I'll break down what's on the page starting from
the top
- We have some global JS variables which get the ID's of elements
we use in our JS code
- Reference the Google maps API
- Reference the two asset files which we'll add in the next
section (JS & CSS)
- TIP: Whenever adding resources, note that the
namespace IS case sensitive
- HTML for the view (Showing the textbox, a find button, two
general textboxes to show the Long & Lat, Div for the map and
the two hidden fields to store the Long & Lat)
REMEMBER: Now we are done, mark this view as an
embedded resource (Right click, properties, build action = embedded
resource)
Adding Resources
In this property editor with have two asset/resource files - One
JavaScript file with all the logic to interact with GoogleMaps and
a very simple CSS file just so you can see the autocomplete
dropdown and a bit of other styling. Add the JS & CSS
file in your 'Resources' folder like so 'gMap.css &
gMap.js'

Now add the contents of them
gMap.css
.ui-autocomplete { background-color: white; width: 300px; border: 1px solid #cfcfcf; list-style-type: none; padding-left: 0px; font-family: Arial, Helvetica, sans-serif; cursor: pointer; font-size: 12px; }
.ui-menu-item { padding: 4px 0; }
#geomap { border: 1px #ccc solid;}
input.latTextBox, input.longTextBox { width: 120px !Important;}
gMap.js
var geocoder;
var map;
var marker;
function initialize() {
//MAP
var initialLat = $(latval).val();
var initialLong = $(longval).val();
if (initialLat == '') {
initialLat = "51.773071843208115";
initialLong = "-1.6568558468750325";
}
var latlng = new google.maps.LatLng(initialLat, initialLong);
var options = {
zoom: 16,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("geomap"), options);
geocoder = new google.maps.Geocoder();
marker = new google.maps.Marker({
map: map,
draggable: true,
position: latlng
});
google.maps.event.addListener(marker, "dragend", function (event) {
var point = marker.getPosition();
map.panTo(point);
});
};
$(document).ready(function () {
initialize();
$(function () {
$(PostCodeid).autocomplete({
//This bit uses the geocoder to fetch address values
source: function (request, response) {
geocoder.geocode({ 'address': request.term }, function (results, status) {
response($.map(results, function (item) {
return {
label: item.formatted_address,
value: item.formatted_address
};
}));
});
}
});
});
$('#findbutton').click(function (e) {
var address = $(PostCodeid).val();
geocoder.geocode({ 'address': address }, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setCenter(results[0].geometry.location);
marker.setPosition(results[0].geometry.location);
$(latval).val(marker.getPosition().lat());
$(longval).val(marker.getPosition().lng());
$("input.latTextBox").val(marker.getPosition().lat());
$("input.longTextBox").val(marker.getPosition().lng());
} else {
alert("Geocode was not successful for the following reason: " + status);
}
});
e.preventDefault();
});
//Add listener to marker for reverse geocoding
google.maps.event.addListener(marker, 'drag', function () {
geocoder.geocode({ 'latLng': marker.getPosition() }, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
if (results[0]) {
//$('#address').val(results[0].formatted_address);
$(latval).val(marker.getPosition().lat());
$(longval).val(marker.getPosition().lng());
$("input.latTextBox").val(marker.getPosition().lat());
$("input.longTextBox").val(marker.getPosition().lng());
}
}
});
});
});
Lastly…
After adding any resources you need mark them in the editor as a
WebResource so they get included in the binary, add the following
two lines above the 'namespace
Umbraco.Cms.Web.PropertyEditors.GoogleMap' line in your
'GoogleMapEditor.cs' file
[assembly: WebResource("Umbraco.Cms.Web.PropertyEditors.GoogleMap.Resources.gMap.js", "application/x-javascript")]
[assembly: WebResource("Umbraco.Cms.Web.PropertyEditors.GoogleMap.Resources.gMap.css", "text/css", PerformSubstitution = true)]
AGAIN REMEMBER: Now we are done, always mark
ALL your resources (And custom views) as an embedded resource
(Right click, properties, build action = embedded resource)
See It In Action
Ok now we have our Property Editor pretty much done, you just
need to build the site (Actually just click the run button to fire
it up) add it to your site as you would do a datatype in a normal
Umbraco site (In the developer section). Add the GoogleMap
Property Editor to the 'Homepage' document type and name it 'gMap',
save and when you view the admin UI you should see the
following:

Now type and address or postcode and you should start to see the
autocomplete kick in, once you have selected an address click find
to geoCode it - And to fine tune the position drag the marker
around to suit. Then save and publish.
Using In the Front End
Getting the values out is very easy in the front end, you just
use the following extension method
Model.Field<string>("PropertyAlias", "ValueAlias")
Where property Alias is the actual alias of the property
we added to the DocType (In our case 'gMap') and
'ValueAlias' is the name of the properties you added in your
EditorModel (Ours are 'Postcode, Long & Lat').
So to use these in the front end, we'll just throw the map in the
'Homepage' template.
Open up the template and at the top, under
'ViewBag.Title' add the following
var Long = Model.Field<string>("gMap", "Long");
var Lat = Model.Field<string>("gMap", "Lat");
And now above the first H2 title add the following:
<h2>Google Map</h2>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
function initialize() {
var myLatlng = new google.maps.LatLng(@Html.Raw(Lat.ToString()), @Html.Raw(Long.ToString()));
var myOptions = {
zoom: 13,
center: myLatlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
var map = new google.maps.Map(document.getElementById("contactmap"), myOptions);
var homestring = 'Your Location';
var homeinfowindow = new google.maps.InfoWindow({ content: homestring });
var homemarker = new google.maps.Marker({
position: myLatlng,
map: map
});
google.maps.event.addListener(homemarker, 'click', function () {
homeinfowindow.open(map, homemarker);
});
}
$(document).ready(function () {
initialize();
});
</script>
<div id="contactmap" style="width:400px; height: 300px; border:2px #ccc solid;"></div>
Tada..

As you can see its very simple, and maybe not how you would do
it in a real site but it shows you how to get your values back out
in a view - I hope this blog post helps you get started playing
with property editors, and please let me know your comments below,
and if I have missed or got something wrong.
Extending Further…
In the next post we'll be extending this property editor by
adding pre-values, manipulating the content before we save it and a
few other things.
Happy Coding 