Nibble

Archive for July, 2011

Branch/shop/… locator for Umbraco Razor style 8

A while ago I created a locator package for umbraco that made it pretty easy to add find nearest branch/shop/dealer/… functionality to your umbraco site.

To sharpen my razor skills I thought it would be great to try to port this from xslt to razor and turns out this was pretty easy (feel the power of the dark side).

Another update is that it now uses the google maps api v3 so no more need for api keys

It works in combination with the google maps datatype so you’ll need to make sure that your documents have coordinates stored before you are able to search (so a property of the type google maps using the google maps datatype).

image

Once the documents you want to search through have a long/lat stored you can use the locator macro.

image

The macro has 4 parameters

  • Document type alias of the documents you want to search through
  • Property alias of the property containing the coordinates
  • If you want the units in miles instead of km
  • Number of results to display

If you have the macro on a page/template you’ll need to provide it with a search location. So posting to that page with a querystring variable s will do the trick like ?s=gentbrugge

The default output is pretty simple,

  • Search location
  • Map with markers
  • Unordered list of results ordered by distance from search location

image

But that can be updated to your needs. There is some stuff going on in the locator assembly but the output can be completely customized by updating the razor script.

@inherits umbraco.MacroEngines.DynamicNodeContext
@using Locator
@using Locator.GeoCoding
@using Locator.GeoCoding.Services.Google
@using umbraco.MacroEngines;
 
@{
 
   @* Select the nodes you want to search through, defaults to children *@
   var nodesToSearchThrough = string.IsNullOrEmpty(Parameter.DocTypeAlias) ?
        Model.Children.Where("Visible"): Model.AncestorOrSelf(1).Descendants(Parameter.DocTypeAlias).Where("Visible");
   
   @* Alias of the property containing the long/lat, defaults to location *@
   string locationPropAlias = string.IsNullOrEmpty(Parameter.LocationPropAlias) ? 
        "location" : Parameter.locationPropAlias;
   
   @* Number of results, defaults to 5 *@
   int numberOfSearchResults = string.IsNullOrEmpty(Parameter.NumberOfResults)|| Parameter.NumberOfResults == "0" ? 
        5 : int.Parse(Parameter.NumberOfResults);
  
   @* Distance unit, defaults to km *@
   DistanceUnits distanceunit = Parameter.UnitInMiles == "1" ? 
        DistanceUnits.Miles : DistanceUnits.Kilometers;
  
   List<GeoItem> items = new List<GeoItem>();
   Location searchLocation = new Location();
  
   if(string.IsNullOrEmpty(Request["s"]))
   {
       @* Search term not provided *@ 
       <p>Please provide a search term</p>
   }
   else 
   {
      @* Lookup search location coordinates *@ 
      GeoResponse r = GoogleGeoCoder.CallGeoWS(Request["s"]);
    
      if(r.Results.Length == 0)
      {
        @* Location not found *@ 
        <p>Location not found:  @Request["s"]</p>
      }
      else
      {
        @* build up list of results *@       
        searchLocation = new Location(r.Results[0].Geometry.Location.Lat, r.Results[0].Geometry.Location.Lng);
       
        foreach (var node in nodesToSearchThrough) {
                         
              Location itemLocation = new Location(
                    Convert.ToDouble(node.GetProperty(locationPropAlias).ToString().Split(‘,’)[0], Utility.NumberFormatInfo),
                    Convert.ToDouble(node.GetProperty(locationPropAlias).ToString().Split(‘,’)[1], Utility.NumberFormatInfo));  
              
              items.Add(new GeoItem(node,itemLocation,searchLocation.DistanceBetween(itemLocation,distanceunit)));
                                                                                       
        }
        @* sort based on distance *@
        items.Sort(new GeoItemComparer());
       
      }
   }
}
@if(items.Count > 0)
{
@* Output Results *@ 
 
<p>Result for: @Request["s"]</p>
  
@* Output Map *@
 
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false">
</script>
 
<div id="map_canvas" style="width:500px; height:500px"></div>
 
<script type="text/javascript">
  function initialize() {
    var latlng = new google.maps.LatLng(
                    @searchLocation.Latitude.ToString(Utility.NumberFormatInfo), 
                    @searchLocation.Longitude.ToString(Utility.NumberFormatInfo));
    var myOptions = {
      zoom: 8,
      center: latlng,
      mapTypeId: google.maps.MapTypeId.ROADMAP
    };
    var map = new google.maps.Map(document.getElementById("map_canvas"),
        myOptions);
    @{int c = 1;}                      
    @foreach(GeoItem geo in items.Take(numberOfSearchResults)){
        dynamic node = ((DynamicNode)geo.Node);    
                                                          
        <text>
          var mLatlng@(c) = new google.maps.LatLng(
                                      @geo.Location.Latitude.ToString(Utility.NumberFormatInfo),
                                      @geo.Location.Longitude.ToString(Utility.NumberFormatInfo));
          var marker@(c) = new google.maps.Marker({
              position: mLatlng@(c) , 
              map: map, 
              title:"@node.Name"
          });                                                              
         </text>
        c++;                                                  
   }
  }
  initialize();
</script>
 
 
@* Output list *@
<ul>
@foreach(GeoItem geo in items.Take(numberOfSearchResults)){ 
  dynamic node = ((DynamicNode)geo.Node);                                                
  <li><a href="@node.Url">@node.Name - @geo.Distance.ToString("0.00") km</a></li>                               
}
</ul>
}

Package can be downloaded on the project page: http://our.umbraco.org/projects/developer-tools/locator-razor-style

A First look at Property Editors in Umbraco v5 0

Umbraco v5 introduces a new definition: Property Editor

From the wiki: A Property Editor is the editor that a Data Type references. In Umbraco versions previous to version 5, there was no official name given to the editor that a Data Type referenced and it was also known as a Data Type. In version 5 we differentiate between the 2 aspects of Umbraco. A Data Type is defined by the Administrator of Umbraco which references a Property Editor. A Property Editor on the other hand is defined by a developer and compiled into a DLL.

So where in previous umbraco versions we talked about creating custom datatypes this will be know as property editors in v5.

On this blog I always used the same example when creating custom datatypes, a textarea where you can set a limit on the amount of characters (like in this post).

image

So as a first attempt at creating a property editor for umbraco v5 I created the same control. Of course this is build against the CTP release so there might be changes by the time v5 is released (if so I’ll do some follow up posts)

To get started with creating property editors, take a look at the v5 Wiki: Developing Property Editors

We’ll need to implement 3 classes, PropertyEditor, EditorModel and PrevalueModel (and in this case I’ll also have a razor view for my content editor control).

image

 

Property Editor

Core class which defines the Property Editor

using Umbraco.Cms.Model.BackOffice.PropertyEditors;
 
namespace Umbraco.Addons.PropertyEditors.CharLimit
{
    [PropertyEditor("E3FAF03B-C3A5-4630-B917-9B6D9F645833", 
        "CharLimit", "Text Area with character limit")]
    public class CharLimitEditor : PropertyEditor<CharLimitEditorModel, CharLimitPreValueModel>
    {
        public CharLimitEditor()
        {       
        }
 
        public override CharLimitEditorModel CreateEditorModel(CharLimitPreValueModel preValues)
        {
            return new CharLimitEditorModel(preValues);
        }
 
        public override CharLimitPreValueModel CreatePreValueEditorModel(string preValues)
        {
            return new CharLimitPreValueModel(preValues);
        }
    }
}

 

PreValueModel

Used to provide configuration settings for the Property Editor

using Umbraco.Cms.Model.BackOffice.PropertyEditors;
using Umbraco.Cms.Model.BackOffice.Editors;
 
namespace Umbraco.Addons.PropertyEditors.CharLimit
{
    public class CharLimitPreValueModel : PreValueModel
    {
        public CharLimitPreValueModel(string preValues)
            : base(preValues)
        { }
 
        public int CharacterLimit { get; set; }
 
        [AllowDocumentTypePropertyOverride]
        public bool IsRequired { get; set; }
 
        [AllowDocumentTypePropertyOverride]
        public string RegexValidationStatement { get; set; }
 
    }
}

image

An awesome improvement here is that you can mark settings with the AllowDocumentTypePropertyOverride, doing so will make them available on the add / edit property form of the document type

image

EditorModel

The EditorModel class is the class with the properties that a CMS editor will see

using System.Collections.Generic;
using Umbraco.Cms.Model.BackOffice.PropertyEditors;
using System.ComponentModel.DataAnnotations;
using Umbraco.Cms.Model.BackOffice;
using System.Text.RegularExpressions;
using Umbraco.Cms.Web.EmbeddedViewEngine;
 
namespace Umbraco.Addons.PropertyEditors.CharLimit
{
    [EmbeddedView("Umbraco.Addons.PropertyEditors.CharLimit.Views.CharLimitEditor.cshtml", 
        "Umbraco.Addons.PropertyEditors")]
    public class CharLimitEditorModel : EditorModel<CharLimitPreValueModel>, IValidatableObject
    {
 
        public CharLimitEditorModel(CharLimitPreValueModel preValueModel)
            : base(preValueModel)
        {
            
        }
 
        [DisplayFormat(ConvertEmptyStringToNull = false)]
        [ShowLabel(false)]
        public string Value { get; set; }
 
        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            //ensure the string is empty and never ‘null’
            if (string.IsNullOrEmpty(Value)) Value = string.Empty;
 
            if (PreValueModel.IsRequired && string.IsNullOrEmpty(Value))
            {
                yield return new ValidationResult(
                    "Value is required", new[] { "Value" });
            }
            else if (!string.IsNullOrEmpty(PreValueModel.RegexValidationStatement) && 
                !Regex.IsMatch(Value, PreValueModel.RegexValidationStatement))
            {
                yield return new ValidationResult(
                    "Value does not match the required pattern", new[] { "Value" });
            }
        }
    }
}

 

note here that you have much more control over the validation

View

@inherits System.Web.Mvc.WebViewPage<Umbraco.Addons.PropertyEditors.CharLimit.CharLimitEditorModel>
@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 ClientDependency.Core;
@using ClientDependency.Core.Mvc;
@using Umbraco.Cms.Web;
@using Umbraco.Cms.Web.Editors;
@using Umbraco.Addons.PropertyEditors.CharLimit;
 
@Html.ValidationMessageFor(x => Model.Value)
 
@Html.TextAreaFor(x => Model.Value)
 
@if (Model.PreValueModel.CharacterLimit > 0)
{     
    <br/><span class=’limitstatus’></span>
 
    <script type="text/javascript">
    (function ($) {
        $(document).ready(function () {  
            $(‘#@Html.IdFor(x => Model.Value)’).keyup(function () {
               
                var limit = @Model.PreValueModel.CharacterLimit.ToString();
                if ($(this).val().length > limit) {
                        $(‘.limitstatus’, $(this).parent()).html(‘You cannot write more then ‘ + limit + ‘ characters!’);
                    $(this).val($(this).val().substr(0, limit));
                }
                else {
                        $(‘.limitstatus’, $(this).parent()).html(‘You have ‘ + (limit - $(this).val().length) + ‘ characters left.’);
                }
            });
        });
    })(jQuery);
    </script>
}
 

So for the content editor control I created a razor view, since I’ll need to output a textarea but also some javascript

Want to see more examples of v5 property editors, make sure to take a look at the v5 sourcecode:

http://umbraco.codeplex.com/SourceControl/list/changesets