Umbraco V8 – Bye bye ApplicationEventHandler, Hello Umbraco Components!

Hello all,
OK I wanted to get the ball rolling on trying to get some bits on Umbraco V8 out into the wild and for the tinkerers, hackers and early adopters to play & experiment.

So here is something NEW in Umbraco V8 that you most likely will be using a lot. If you ever used one of Umbraco Services event handlers such as ContentService.Saved or similar, then you would most likely set this up using a class that inherits ApplicationEventHandler.

Show me an example!

I will show a brief example of the old approach using ApplicationEventHandler and then doing the exact same with a shiny new Umbraco Component approach.

Old approach – ApplicationEventHandler

using Umbraco.Core;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;

namespace MyProject.EventHandlers
{
    public class RegisterEvents : ApplicationEventHandler
    {
        protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
        {
            //Listen for when content is being saved
            ContentService.Saving += ContentService_Saving;     
        }
        
        /// <summary>
        /// Listen for when content is being saved, check if it is a new item and fill in some
        /// default data.
        /// </summary>
        private void ContentService_Saving(IContentService sender, SaveEventArgs e)
        {                
            foreach (var content in e.SavedEntities
                //Check if the content item type has a specific alias
                .Where(c => c.Alias.InvariantEquals("MyContentType"))
                //Check if it is a new item
                .Where(c => c.IsNewEntity()))
            {
                //check if the item has a property called 'richText'
                if (content.HasProperty("richText"))
                {
                    //get the rich text value
                    var val = c.GetValue("richText");
                    
                    //if there is a rich text value, set a default value in a 
                    // field called 'excerpt' that is the first
                    // 200 characters of the rich text value
                    c.SetValue("excerpt", val == null
                        ? string.Empty 
                        : string.Join("", val.StripHtml().StripNewLines().Take(200)));
                }
            }
        }
    }
}

New approach – Umbraco Components

using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Components;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;

namespace MyProject.Components
{
    /// <summary>
    /// All IUmbracoUserComponents run after all IUmbracoCoreComponent have run
    /// This way we can ensure Umbraco is all setup and stuff is available for us to use
    /// </summary>
    public class ExampleComponent : UmbracoComponentBase, IUmbracoUserComponent
    {
        public void Initialize()
        {
            ContentService.Saving += ContentService_Saving;
        }

        /// <summary>
        /// Listen for when content is being saved, check if it is a 
        /// new item and fill in some default data.
        /// </summary>
        private void ContentService_Saving(IContentService sender, SaveEventArgs e)
        {
            foreach (var content in e.SavedEntities
                //Check if the content item type has a specific alias
                .Where(c => c.ContentType.Alias.InvariantEquals("MyContentType"))
                //Check if it is a new item
                .Where(c => c.IsNewEntity()))
            {
                //check if the item has a property called 'richText'
                if (content.HasProperty("richText"))
                {
                    //get the rich text value
                    var val = content.GetValue("richText");

                    //if there is a rich text value, set a default value in a 
                    // field called 'excerpt' that is the first
                    // 200 characters of the rich text value
                    content.SetValue("excerpt", val == null
                        ? string.Empty
                        : string.Join("", val.StripHtml().StripNewLines().Take(200)));
                }
            }
        }
    }
}

You can see very little has changed to the code, where we inherit from UmbracoComponentBase and IUmbracoUserComponentinstead instead of ApplicationEventHandler.

Why has it changed?

I will try my best to explain this, after I chatted with the brains behind it aka Stephan. This is mostly to help with the future and to avoid some confusion.

because app event handler means… “handles events” and although it’s true, it’s misleading. a component is a part of the whole app, so you add components to “compose” your ideal application – your component can handle events if they want – it’s just a more general notion

What benefits does it have over the old approach?

Again after a quick chat with Stephan on trying to get to know the in’s & out’s of how this works.

For ex when you wanted to change how things where composed (register a new finder etc) you would have to remember to do it in the proper “event” – and people were always confused – now there’s the Composemethod explicitly for this usage – same for Initialize, more explicit + manages dependencies & injection

Also components can depend on each other and this ensures they run one after another, whereas ApplicationEventHandlers were in random order and are easy to enable/disable components, which could not be done with app handlers.

oh and components also terminate meaning they are notified when Umbraco stops

So an example that Stephan explained to me could be

so you create a component, and in Compose you tell Umbraco that it should use MyCache as a content cache. In Initialize you load your cache from wherever you want. In Terminate you flush changes to disk.

Further Examples

Here is an example of a component registering my own custom LastChanceFinder

using Umbraco.Core.Components;

namespace MyProject.Components
{
    /// <summary>
    /// All IUmbracoUserComponents run after all IUmbracoCoreComponent have run
    /// This way we can ensure Umbraco is all setup and stuff is available for us to use
    /// </summary>
    public class MyLastChanceFinderComponent : UmbracoComponentBase, IUmbracoUserComponent
    {
        public override void Compose(Composition composition)
        {
            base.Compose(composition);
            composition.SetContentLastChanceFinder();
        }
    }
}

So that’s it for now and I will share more insights into Umbraco V8 when I can or that I know that code for a piece of functionality is unlikely to change after checking with the brains of the project aka Stephan or Shannon 🙂

Thanks again Stephan for the 101 questions I have had on this!

Until next time, happy hacking & exploring !

What on earth is Umbraco ServerVariables Parsing?!

What on earth is Umbraco ServerVariables Parsing?!

Hello all this is another ‘What on Earth‘ blog post detailing a specific feature or functionality of Umbraco that may be hidden away a little & this blog post is here to help describe & detail it.

Continue reading “What on earth is Umbraco ServerVariables Parsing?!”

Umbraco MVC: Regionalisation of Error Messages

Hello, Bonjour, Hej
However you say hello in whatever language, at The CogWorks where I work we built a lot of Umbraco Sites that are multi regional for various large brands. The common problem I have noticed we have is that when building say a simple form such as a contact form the validation & error messages that are reported back are in English rather than in the current language of the site you are in, obviously if you are using Contour then this is not a problem and can regionalise labels & error messages for you, but there are some times when you need to or want to build a custom form using the typical MVC pattern and use ASP.NETs native Model validators such as [Required], [EmailAddress], [Range] etc…

Continue reading “Umbraco MVC: Regionalisation of Error Messages”

Umbraco 7 – Creating a generic Settings editor with WebAPI & AngularJS

Hello all,
So my adventure for creating a new Umbraco 7 package for viewing Google Analytics information. I have created a generic way to edit settings from an XML file for this new Analytic section inside Umbraco.

So if I add any new elements to my XML config file, they will automatically appear in the Umbraco backoffice for it to be edited.

So I will run you through the parts such as the WebAPI Controller, and Angular to wire this all up nicely.

Firstly here is the XML config file I want to edit & if I add any new nodes it will automatically show in Umbraco to be edited:

<?xml version="1.0" encoding="utf-8" ?>
<Analytics>
  <ClientId label="Client ID" description="This is the Client ID key from the Google API">1234567</ClientId>
  <Username label="Username" description="The username for the Google account">warren</Username>
</Analytics>

So let’s take a look at the WebAPI controller I created to Get & Post the settings to and from the xml.config file.

using System.Collections.Generic;
using System.Linq;
using System.Web.Hosting;
using System.Xml;
using Analytics.Models;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;

namespace Analytics.Controllers
{
    /// <summary>
    /// Web API Controller for Fetching & Saving values from settings.config file
    /// 
    /// Example route URL to API
    /// http://localhost:62315/umbraco/Analytics/SettingsApi/GetSettings
    /// </summary>
    [PluginController("Analytics")]
    public class SettingsApiController : UmbracoAuthorizedApiController
    {
        /// <summary>
        /// Does what it says on the tin - the path to the settings.config file
        /// </summary>
        private const string ConfigPath = "~/App_Plugins/Analytics/settings.config";

        /// <summary>
        /// Gets Settings from the XML settings.config
        /// </summary>
        /// <returns>Serializes settings from XML file into a nice list of objects</returns>
        public List<SettingsValue> GetSettings()
        {
            //A list to store our settings
            var settings = new List<SettingsValue>();

            //Path to the settings.config file
            var configPath = HostingEnvironment.MapPath(ConfigPath);

            //Load settings.config XML file
            XmlDocument settingsXml = new XmlDocument();
            settingsXml.Load(configPath);

            //Get all child nodes of <Analytics> node
            XmlNodeList analayticsNode = settingsXml.SelectNodes("//Analytics");

            //Ensure we found the <Analytics> node in the config
            if (analayticsNode != null)
            {
                //Loop over child nodes inside <Analytics> node
                foreach (XmlNode settingNode in analayticsNode)
                {
                    foreach (XmlNode setting in settingNode.ChildNodes)
                    {
                        //Go & populate our model from each item in the XML file
                        var settingToAdd            = new SettingsValue();
                        settingToAdd.Key            = setting.Name;
                        settingToAdd.Label          = setting.Attributes["label"].Value;
                        settingToAdd.Description    = setting.Attributes["description"].Value;
                        settingToAdd.Value          = setting.InnerText;

                        //Add the item to the list
                        settings.Add(settingToAdd);
                    }
                }
            }

            //Return the list
            return settings;
        }

        public List<SettingsValue> PostSettings(List<SettingsValue> settings)
        {
            //Update the XML config file on disk

            //Path to the settings.config file
            var configPath = HostingEnvironment.MapPath(ConfigPath);

            //Load settings.config XML file
            XmlDocument settingsXml = new XmlDocument();
            settingsXml.Load(configPath);

            //Get all child nodes of <Analytics> node
            XmlNodeList analayticsNode = settingsXml.SelectNodes("//Analytics");

            //Loop over child nodes inside <Analytics> node
            foreach (XmlNode settingNode in analayticsNode)
            {
                foreach (XmlNode setting in settingNode.ChildNodes)
                {
                    //Go & populate our model from each item in the XML file
                    setting.InnerText = settings.SingleOrDefault(x => x.Key == setting.Name).Value;
                }
            }

            //Save the XML file back down to disk
            settingsXml.Save(configPath);

            //Return the same settings that passed in
            return settings;
        }
    }
}

You can see the controller is nicely commented and should be easy to follow along in what it is doing, you will notice I have a custom model/object to store the list of items so it can be serialised easily to JSON for the GET & POST requests. The model is as follows:

namespace Analytics.Models
{
    public class SettingsValue
    {
        public string Key { get; set; }

        public string Label { get; set; }

        public string Description { get; set; }

        public string Value { get; set; }
    }
}

So with these parts in place we have a way with a WebAPI to fetch & post values to our XML config file. Next we need to create an Angular View, Angular Controller & Angular Resource file to consume this WebAPI.

<div ng-controller="Analytics.SettingsController">
    <div class="umb-pane">
        <umb-control-group ng-repeat="setting in settings" label="{{ setting.Label }}" description="{{ setting.Description }}">
            <input type="text" name="{{ setting.Key }}" id="{{ setting.Key }}" value="{{ setting.Value }}" ng-model="setting.Value" />
        </umb-control-group>

        <div class="umb-tab-buttons">
            <div class="btn-group">
                <button ng-click="save(settings)" data-hotkey="ctrl+s" class="btn btn-success">
                    <localize key="buttons_save">Save</localize>
                </button>
            </div>
        </div>

    </div>
</div>

This Angular HTML view loops over the settings JSON object and repeats the markup & input field for each setting to edit within the XML file with the ng-repeat Angular data attribute.

The next part is to look at the Angular Controller file that is wired up to this view.

angular.module("umbraco").controller("Analytics.SettingsController",

function($scope, settingsResource, notificationsService) {

    //Get all settings via settingsResource - does WebAPI GET call
    settingsResource.getall().then(function(response) {
        $scope.settings = response.data;
    });

    //Save - click...
    $scope.save = function(settings) {

        //Save settings resource - does a WebAPI POST call
        settingsResource.save(settings).then(function(response) {
            $scope.settings = response.data;

            //Display Success message
            notificationsService.success("Success settings have been saved");
        });
    };

});

You can see we have created a Resource to get the settings and to save the settings, which the resource file handles the GET & POST to the WebAPI controller. When the save button is clicked it POSTs the settings to the WebAPI controller and shows a nice success message with the notification service.

angular.module("umbraco.resources")
    .factory("settingsResource", function($http) {
    return {

        getall: function() {
            return $http.get("Analytics/SettingsApi/GetSettings");
        },

        save: function(settings) {
            return $http.post("Analytics/SettingsApi/PostSettings", angular.toJson(settings));
        }
    };
}); 

With all these parts in place we now have a nice generic way of displaying & saving values to an XML config file and allowing easy update to the UI if we decide to add in more settings at a later date.

I hope someone finds this useful & can take something away to learn from it.

Cheers,
Warren 🙂

Quick Tip: Add a new custom section to Umbraco 7+

Hello all,
I have just come across a quick, simple & neat way to add a section to your Umbraco website/application be it for your own use or as a package developer.

So rather than talk about it for ages I will just jump straight into it.

    public class UmbracoStartup : ApplicationEventHandler
    {
        protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
        {
            //Get SectionService
            var sectionService = applicationContext.Services.SectionService;

            //Try & find a section with the alias of "mySection"
            var mySection = sectionService.GetSections().SingleOrDefault(x => x.Alias == "mySection");

            //If we can't find the section - doesn't exist
            if (mySection == null)
            {
                //So let's create it the section
                sectionService.MakeNew("My Section", "mySection ", "icon-users");
            }
        }
    }

So what this is doing on an application startup of Umbraco using the new Section Service checking to see if there a section with the alias of mySection already created, if not then using the section service it goes and creates it.

That’s it, it really is that simple.
Enjoy 🙂

Using Umbraco Pipeline for Member Profile URLs

Hello all,
I wanted to share with you all a new approach I have learnt after reading up on the Umbraco Pipeline.

I originally asked the question over on our.umbraco.org on how best to achieve this and first came up with URL Rewriting to solve my solution, then the thread evolved and I was able to use a custom controller & route, but now I have found a much simpler & elegant way to deal with it, which is to use an iContentFinder class.

First lets start off with creating an custom iContentFinder which is used in the pipeline for trying to find an Umbraco node that matches the URL, if it doesn’t find it it gets passed onto the next iContentFinder and so on until it gets to the bottom which is the 404 iContentFinder.

using System;
using System.Linq;
using System.Web;
using umbraco.cms.businesslogic.member;
using Umbraco.Core;
using Umbraco.Web.Routing;

namespace MySite.BusinessLogic
{
    public class MemberProfileContentFinder : IContentFinder
    {
        public bool TryFindContent(PublishedContentRequest contentRequest)
        {
            var urlParts = contentRequest.Uri.GetAbsolutePathDecoded().Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
            
            //Check if the Url Parts
            // Starts with /member/*
            if (urlParts.Length > 1 && urlParts[0].ToLower() == "member")
            {
                //Lets try & find the member
                var memberName = urlParts[1];

                //Try and find a member where the property matches the memberName
                var tryFindMember = Member.GetAll.SingleOrDefault(x => x.getProperty("profileURL").Value.ToString() == memberName);

                //Need to set the member ID or pass member object to published content
                //Will pass a null to view if not found & can display not found member message
                HttpContext.Current.Items["memberProfile"] = tryFindMember;

                //Add in string to items - for profile name user was looking for
                HttpContext.Current.Items["memberName"] = memberName;

                //Set the Published Content Node to be the /Profile node - can get properties off it & my member profile in the view
                contentRequest.PublishedContent = contentRequest.RoutingContext.UmbracoContext.ContentCache.GetByRoute("/view-profile");

                //Return true to say found something & stop pipeline & other contentFinder's from running
                return true;
            }

            //Not found any content node to display/match - so run next ContentFinder in Pipeline
            return false;
        }
    }
}

In this code you can see it is quite straight forward. It is getting the URL from the request and splitting the string on the URL parts such as http://mysite.co.uk/member/john into a string array so the variable urlParts will have two items in the array which are member and john.

We then next check that we have at least one URL part and that the first URL part in our array is equal to members, as we don’t want to run our logic if it does not match this URL fragment.

Next part is for us to get the second item in the array which is the member profile URL we want to look up. Using the Umbraco Member API we fetch all members and using a SingleOrDefault() Linq query we check to see if we can find a single member where the property memberProfileURL on the member matches that to our variable which contains the member URL to lookup.

Moving on we add the found member (even if it returns null, aka not found a member that has that profile URL) & the member profile URL to the HTTPContext Items so that we can retrieve them from our view.

The magic part of this is the following line:


//Set the Published Content Node to be the /Profile node - can get properties off it & my member profile in the view
contentRequest.PublishedContent = contentRequest.RoutingContext.UmbracoContext.ContentCache.GetByRoute("/view-profile");

By setting PublishedContent on the contentRequest object, we can choose what node in the Umbraco content tree we want to render. In our case I am using the routing context to help get me the node based from it’s URL, so in this case I am fetching the node that matches the URL, /view-profile

The final part is that we return true, so that we let the pipeline know that we have a found a node and to stop running any further iContentFinders from being processed. By doing so we will load the view profile node and it’s template.

The next part of this is to now register our content finder in the application starting event with this lovely code snippet so it can run in our site.

using Umbraco.Core;
using Umbraco.Core.Persistence;
using Umbraco.Web.Routing;

namespace MySite.BusinessLogic
{
    public class UmbracoStartup : ApplicationEventHandler
    {
        protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
        {
            // Insert my MemberProfileContentFinder before ContentFinderByNiceUrl
            ContentFinderResolver.Current.InsertTypeBefore<ContentFinderByNiceUrl, MemberProfileContentFinder>();
        }
    }
}

Now that we have our custom iContentFinder registered by Umbraco, we just need to ensure our template/view for our view profile node uses the values in the HTTPContext.Items collection that we set inside the iContentFinder.

@using Member = umbraco.cms.businesslogic.member.Member
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@{
    Layout = "Master.cshtml";

    //Get the values out of the HTTPContext that we set in the iContentFinder
    var memberProfile   = (Member)HttpContext.Current.Items["memberProfile"];
    var memberName      = (string)HttpContext.Current.Items["memberName"];

    //Bit of string replacment on the noUserFound property on the View Profile node
    //Replace it with the member name the user was trying to look for
    var notFoundMessage = Model.Content.GetProperty("noUserFound").Value.ToString();
    notFoundMessage     = notFoundMessage.Replace("##user##", memberName);
}

<h2>View Profile</h2>

@if (memberProfile == null)
{
    //Display User Not Found Message
    //Do string replacement on copy ##user## with memberName
    @Html.Raw(notFoundMessage)
}
else
{
    //We found the member - so pass the member object to our partial
    //To display the member's details etc 🙂
    @Html.Partial("MemberProfile", memberProfile)
}

In this template/view you can see I am fetching the items out of the HTTPContext and casting it to a string for the profile URL name and the other as an Umbraco Member object so we can get intellisense.

The next part is that I am doing some string replacement on an field that I am fetching off the View Profile node for a user not found message.

I then simply display the not found message if the member is null, because the iContentFinder did not set a value for it. Otherwise we pass the member into our partial view where we can display specific information about our member, like member name, email, profile image & any other custom fields set on the member type.

So the final part of the puzzle is the Partial View

@inherits Umbraco.Web.Mvc.UmbracoViewPage <umbraco.cms.businesslogic.member.Member>
<h2>Member Profile Partial</h2>

<h3>Name: @Model.Text</h3>
<h3>Email: @Model.Email</h3>
<h3>Custom Field: @Model.getProperty("customField").Value</h3>

The only important thing to know here is that the first line is that I am setting the object type to be an Umbraco Member, which lets the model know its an Umbraco Member and gives us intellisense in Visual Studio when writing our partial view to display public information about our member.

That’s it, it’s really as simple as that.

Thanks to the Umbraco community & Stephane Gay for some help with the iContentFinder Pipeline stuff.

Restrict Umbraco Media Filesize with JS

Hello all,
This is a very quick & brief blog post for a quick code snippet. I had a requirement today to at The CogWorks restrict file size upload for media items in Umbraco and to display a friendly error message.

For this to work easily, I created a .NET usercontrol with the following in:


<script type="text/javascript">
 //Go get file size from .config value in bytes
 var fileSizeToCheck = <%= ((HttpRuntimeSection) ConfigurationManager.GetSection("system.web/httpRuntime")).MaxRequestLength %>
</script>
<script type="text/javascript" src="/usercontrols/MediaFileSize/MediaFileSizeValidation.js"></script>

<div id="fileSize"></div>

The next part was to create a custom document type in Umbraco using the UserControl Wrapper method and pointing the datatype to my newly created usercontrol.

The next step was to assign this document type as a property on my image media type. I gave it name like FileSize Check, however this name does not matter as I do not use the property value at all, it’s only purpose is to run the custom Javascript inside it, which is as follows:

//When DOM ready
$(function () {
    
    //Get the File Size DOM element & hide it's label & property area
    $('#fileSize').closest('.propertypane').hide();

    //On form submit
    $("form").on("submit", function (event) {

        //Get the file input type
        var fileInput = $("input[type=file]")[0];

        console.log(fileInput);
        console.log(fileSizeToCheck);

        //Validate filesize
        if (!validateFileSize(fileInput, fileSizeToCheck)) {
            
            //Show validtion message
            UmbClientMgr.mainWindow().UmbSpeechBubble.ShowMessage('error', 'File Size', 'The file chosen is too big, please choose a smaller file than ' + fileSizeToCheck/1024 + 'kb');
            
            //Cancel form event
            event.preventDefault();
        }        
    });
});

///http://deepeshdarshan.wordpress.com/2012/06/20/how-to-validate-upload-file-size-and-file-extension-using-javascript/
function validateFileSize(component, maxSize) {
    
    if (navigator.appName == "Microsoft Internet Explorer") {
        if (component.value) {
            var oas = new ActiveXObject("Scripting.FileSystemObject");
            var e = oas.getFile(component.value);
            var size = e.size;
        }
    }
    else {
        if (component.files[0] != undefined) {
            size = component.files[0].size;
        }
    }
    
    if (size != undefined && size > maxSize) {
        component.value = "";
        component.focus();
        
        return false;
    }
    else {
        return true;
    }
}

As you can see from the JavaScript I first hide the property from the Umbraco backoffice UI, as we don’t need to show a label and an emtpy box for the media UI, but the main part of the code hooks into the form on submit event and then grabs the file input type and passes it to the validateFileSize function(). If the file is too big then we display an error message using the Umbraco message notification bubble in the bottom right hand corner.

Well that’s all there is to it.
Hopefully this little snippet will have helped you out.

Cheers,
Warren 🙂

How I built my first property editor for Umbraco Belle

After recently building my first AngularJS project, Diagnostics for Umbraco so I can learn the framework as Umbraco Belle is built with Angular.

So my next step was to learn how to build an Umbraco property editor for Belle, before me and my colleagues at The CogWorks along with lead Belle developer Per Ploug to host a Belle hands on workshop.

Currently Per has written a fantastic tutorial for creating your first property editor in where you create a markdown property editor which is a great tutorial that I followed.

But for me to ensure I learnt the skills properly then I decided to create my first property editor by myself.

If you remember when I was working at the HQ I wrote a blog tutorial on creating a property editor which was a RGBA colour picker. In this tutorial I will be recreating the same property editor for Belle.

So first thing first is to create a folder called RGBAEditor in App_Plugins in an Umbraco Belle installation. This is where we store the HTML, JS and package manifest file for our property editor.

{
    propertyEditors: [      
        {
            id: "B2FE9CFE-50FC-42A5-9DFE-35DE84953C67",
            name: "(CWS) RGBA Colour Picker",
            editor: {
                view: "~/App_Plugins/RGBAEditor/rgba-editor.html"
            }
        }
    ]
    ,
    javascript: [
        '~/App_Plugins/RGBAEditor/rgba.controller.js'
    ]
}

In this JSON, we give our property a unique GUID, a friendly name for our property editor so we can choose it from the back office and then we define the HTML Angular JS view for our property editor in this case it’s rgba-editor.html and finally we define our Javascript we need to load for this property editor which in this case is our AngularJS controller file – rgba.controller.js

Next we need to our HTML for our property editor, in this case it will contain three sliders and an element to show a live preview of the RGBA colour. So in rgba-editor.html file we need the following.

<div ng-controller="CWS.RGBAController">

    <div class="row">
        <!-- SLIDERS -->
        <div class="sliders span8">
            <!-- RED -->
            <div class="control-group">
                 <label class="control-label" for="rgba-red">Red</label>
                <div class="controls">
                     <input type="text" name="rgba-red" ng-model="renderModel.red" class="slider red" data-slider-min="0" data-slider-max="255" data-slider-step="1" data-slider-value="255" data-slider-orientation="horizontal" data-slider-selection="after" data-slider-tooltip="show"/>
                </div>
            </div>

            <!-- GREEN -->
            <div class="control-group">
                 <label class="control-label" for="rgba-green">Green</label>
                <div class="controls">
                     <input type="text" name="rgba-green" ng-model="renderModel.green" class="slider green" data-slider-min="0" data-slider-max="255" data-slider-step="1" data-slider-value="0" data-slider-orientation="horizontal" data-slider-selection="after" data-slider-tooltip="show"/>
                </div>
            </div>

            <!-- BLUE -->
            <div class="control-group">
                 <label class="control-label" for="rgba-blue">Blue</label>
                <div class="controls">
                     <input type="text" name="rgba-blue" ng-model="renderModel.blue" class="slider blue" data-slider-min="0" data-slider-max="255" data-slider-step="1" data-slider-value="0" data-slider-orientation="horizontal" data-slider-selection="after" data-slider-tooltip="show"/>
                </div>
            </div>

            <!-- ALPHA -->
            <div class="control-group">
                 <label class="control-label" for="rgba-alpha">Alpha (%)</label>
                <div class="controls">
                     <input type="text" name="rgba-alpha" ng-model="renderModel.alpha" class="slider alpha" data-slider-min="0" data-slider-max="100" data-slider-step="1" data-slider-value="100" data-slider-orientation="horizontal" data-slider-selection="after" data-slider-tooltip="show"/>
                </div>
            </div>
        </div>

        <!-- PREVIEW -->
        <div class="preview span4">
            <!-- This will show a live preview of the RGBA colour based on jQuery UI sliders below -->
            <div id="rgba-preview-{{model.alias }}"><span ng-style="{background: preview}"></span></div>
            {{ renderModel.red }} {{ renderModel.green }} {{ renderModel.blue }} {{ renderModel.alpha }}
        </div>
    </div>

    <hr/>

    <h6>Debug JSON</h6>
    {{ model | json }}

</div>

In this HTML I am using the Bootstrap CSS styles to help create a simple layout as Belle uses Twitter Bootstrap for it’s styling. The ng-controller attribute on the opening div is used to map our AngularJS controller to our view. We then have lots of data attributes on our inputs, this is because we are using a jQuery plugin for Bootstrap styled sliders that require these attributes to be present on the text input field.

The slider plugin that I am using for this is the Eyecon.ro Bootstrap Slider. In addition in this view you will see we use some custom properties that we store on the scope such as red, green, blue etc so that when the slider is updated our AngularJS controller knows about it and finally we use the ng-style attribute to update the CSS background property on the element from the concatenated value of preview stored on our scope in our controller.

Lets now move onto our JS controller to see the final piece of the puzzle.

angular.module("umbraco").controller("CWS.RGBAController", function ($scope, $log, assetsService) {

    //tell the assetsService to load the bootstrap slider
    //libs from the plugin folder
    assetsService
        .loadJs("/app_plugins/RGBAEditor/lib/bootstrap-slider/bootstrap-slider.js")
        .then(function () {

            //Initiate sliders
            $('.slider').slider();

            //Update the preview DIV function...
            var RGBChange = function () {
                //Log...
                $log.log("Fired RGBCHange()");

                $scope.$apply(function () {
                    //Divide by 100 to get decimal needed for RGBA css
                    var alpha = a.getValue() / 100;

                    //Store the value as a CSV
                    $scope.model.value = [r.getValue(), g.getValue(), b.getValue(), alpha].join();

                    //Set the preview value, so we can update the view
                    $scope.preview = 'rgba(' + $scope.model.value + ')';
                });
            };

            //Slider values stored in data object
            var r = $('.red').slider().on('slideStop', RGBChange).data('slider');
            var g = $('.green').slider().on('slideStop', RGBChange).data('slider');
            var b = $('.blue').slider().on('slideStop', RGBChange).data('slider');
            var a = $('.alpha').slider().on('slideStop', RGBChange).data('slider');

            //this will be comma delimited 'r,g,b,a'
            if ($scope.model.value && (typeof $scope.model.value === "string")) {
                var splitVals = $scope.model.value.split(",");

                //Get values from split and set the sliders
                r.setValue(splitVals[0].trim());
                g.setValue(splitVals[1].trim());
                b.setValue(splitVals[2].trim());

                //Alpha convert back to full %
                var alphaConvert = splitVals[3].trim() * 100;
                a.setValue(alphaConvert);

                //set the preview background value
                $scope.preview = 'rgba(' + $scope.model.value + ')';
            }
        });

    //load the seperate css for the editor to avoid it blocking our js loading
    assetsService.loadCss("/app_plugins/RGBAEditor/lib/CWS-rgba.css");
    assetsService.loadCss("/app_plugins/RGBAEditor/lib/bootstrap-slider/slider.css");

});

The code is heavily commented above and should make sense, but I will go over some key points in it. First we define our controller CWS.RGBAController which is the same as the ng-controller attribute in our view. Next key point is the assetsService which the Umbraco guys have made that allows us to load in external Javascript dependencies. In this case I am loading in the Eyecon.ro Bootstrap Slider library so that I can use it. Once it’s loaded we can work with it inside the .then function.

In here we initiate our sliders and then have a function for every time one of the sliders is updated we update our scope values, the most important being $scope.model.value as this is what will be saved back into Umbraco when the user clicks save on the document and in addition we update the preview as well on the scope so that we can allow the colour preview to be updated in the HTML view. We store the RGBA value in Umbraco as a CSV string r,g,b,a.

The final part to this is to get the values when the property editor is initially loaded and set the sliders to those values. Which we will need to do for when we come back to the node when the values have been saved by doing a simple split of the values and then setting the sliders back to those values from the CSV string stored in Umbraco.

It’s as easy as that to create a property editor for Umbraco Belle, no Visual Studio needed. No compiling down to DLLs and writing C# code. This is so easy to do that your front-end developer can start writing property editors for Umbraco. Which is fantastic news 🙂

Here’s to a bright and happy future with Umbraco Belle.

The new way to do a 404 Umbraco Handler

Hello all,
As you may or may not be aware I have been building a new Umbraco starter kit called CWS Start. In this package I wanted to have a 404 page setup for the site. Currently the way to setup a 404 page is to put a node ID in the umbracosettings.config file. My only concern with this approach is that if you delete the node and recreate the node then the 404 will stop working.

So I decided to ask the Umbraco community how they do 404’s and I got a fantastic response from the community.
http://our.umbraco.org/forum/developers/extending-umbraco/43866-Alternatives-to-404-in-umbracosettingsconfig

Stefan & Lee K gave me some fantastic answers and filled me on the new Request pipeline in Umbraco and a way on how to add a ContentFinder to Umbraco, aka the new way of doing 404 handlers in Umbraco 6.1 and any other type of content finders.

Show me the code!

    public class _404iLastChanceFinder : IContentFinder
    {
        public bool TryFindContent(PublishedContentRequest contentRequest)
        {
            //Check request is a 404
            if (contentRequest.Is404)
            {
                //Get the home node
                var home = contentRequest.RoutingContext.UmbracoContext.ContentCache.GetAtRoot().Single(x => x.DocumentTypeAlias == "CWS-Home");

                //Get the 404 node
                var notFoundNode = home.Children.Single(x => x.DocumentTypeAlias == "CWS-404");

                //Set Response Status to be HTTP 404
                contentRequest.SetResponseStatus(404, "404 Page Not Found");

                //Set the node to be the not found node
                contentRequest.PublishedContent = notFoundNode;
            }

            //Not sure about this line - copied from Lee K's GIST
            //https://gist.github.com/leekelleher/5966488
            return contentRequest.PublishedContent != null;
        }
    }

And then we need to register this in Umbraco on App Startup like so:

        protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
        {
            //On application starting event...
            //Add to the ContentFinder resolver collection our custom 404 Content Finder resolver
            ContentLastChanceFinderResolver.Current.SetFinder(new _404iLastChanceFinder());
        }

There are other use cases for IContentFinders and Lee K has a great little example he posted to Gist