Friday, December 21, 2012

Automatic Script/CSS Compression/Rollup in Umbraco

Note: This code is specific to .NET Master Page templates.  Here are instructions for using MVC Views available in Umbraco 4.10+.

When using Umbraco you may have noticed that the Backoffice Administrative interface uses the Client Dependency Framework to roll up and compress all the different scripts and css resources into two calls.  This leads to a big improvement in responsiveness as the server doesn't need to download as much data nor complete as many requests to render a web page in the admin area.

The good new here is that you can use this same system in your actual site with very little setup.  A few simple step to configure your main site templates to use the Client Dependency Framework also.

First of all add the following block to the top of evert template in your site just after the first line, Master Tag.

<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %>

Next for all of you sites master templates add the following code someplace in the HEAD block.  Note that you don't want to put this on every templates, just those that are the top level templates.  The golden rule is that every page should call this one and only once.

<umb:ClientDependencyLoader runat="server" ID="ClientLoader">
  <Paths>
      <umb:ClientDependencyPath Name="Styles" Path="/Css" />
       <umb:ClientDependencyPath Name="Scripts" Path="/Scripts" />
  </Paths>
</umb:ClientDependencyLoader>

Now go through all your templates and change your Script and Css includes to the following sample blocks.

<umb:CssInclude runat="server" FilePath="jquery-ui-1.8.css" PathNameAlias="Styles" Priority="0" />
<umb:JsInclude runat="server" FilePath="jquery-1.8.3.js" PathNameAlias="Scripts" Priority="0" />
<umb:JsInclude runat="server" FilePath="jquery-ui-1.9.js" PathNameAlias="Scripts" />

Note that the Priority property is not required.  If set it will load those first in ascending numerical order then load any thing without a priority.  Also the system will detect duplicate inclusions of the same script/css file on the page and only include each one a single time.  You also won't see errors if a script is missing so watch for that as it can be hard to realize if you make a typo.

If you view the site now you will see two lines like this and if you inspect them they will contain all the compressed code.

<link href="/DependencyHandler.axd?s=RandmData&amp;t=Css&amp;cdv=41" type="text/css" rel="stylesheet">
<script src="/DependencyHandler.axd?s=RandmData&amp;t=Javascript&amp;cdv=41" type="text/javascript"></script>

A few other notes  This will not work when compilation/debug is set to True in the web.config.  You will need to disable debug mode to test it and also on the production environment.  Also when the code is compressed it is very hard to debug.  You will want to turn on debug mode on the development system when debugging CSS and Javascript issues or doing development.  The resources will just be loaded with individual script calls in this case.

Also once this is set up there is no reason to use minified scripts in your site anymore.  I suggest replacing them with the full versions for easier code review/editing.

On a final note if you want to include Scripts and CSS files from within macros you can do so still using the  Client Dependency Framework but it is a bit more complex.  I sugest adding the following helper methods in your App_Code directory.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI;
using System.Web;
using ClientDependency.Core.Controls;
using ClientDependency.Core;

namespace JSP
{
    /// <summary>
    /// Client Dependency Loader helpers.
    /// </summary>
    public static class CDL
    {
        /// <summary>
        /// Custom version of the AddRange method that accepts no arguments and does not error out.
        /// </summary>
        /// <typeparam name="T">type</typeparam>
        /// <param name="self">parent collection</param>
        /// <param name="collection">new collection to add or null</param>
        public static void AddRangeOrNone<T>(this List<T> self, IEnumerable<T> collection)
        {
            if (collection != null)
                self.AddRange(collection);
        }

        #region Control Lookup
        /// <summary>
        /// Search recursivle down a control tree for all controls with the passed in Id
        /// </summary>
        /// <param name="self">Root control to search form.</param>
        /// <param name="id">Id to look for</param>
        /// <returns>List of all matched Controls.</returns>
        public static List<Control> SearchControl(this Control self, string id)
        {
            List<Control> results = new List<Control>();

            Control control = self.FindControl(id);

            if (control != null)
                results.Add(control);

            foreach (Control childControl in self.Controls)
            {
                results.AddRangeOrNone(childControl.SearchControl(id));
            }

            return results;
        }

        /// <summary>
        /// Search recursivle down a control tree for all controls of the passed in type
        /// </summary>
        /// <param name="self">Root control to search form.</param>
        /// <param name="type">Type to look for</param>
        /// <returns>List of all matched Controls.</returns>
        public static List<Control> SearchControl(this Control self, Type type)
        {
            List<Control> results = new List<Control>();

            foreach (Control childControl in self.Controls)
            {
                if (childControl.GetType() == type)
                    results.Add(childControl);

                results.AddRangeOrNone(childControl.SearchControl(type));
            }

            return results;
        }
        #endregion

        #region Client Dependencies
        /// <summary>
        /// Add another script to the dependency loader list to be compressed and combined with other resources.
        /// </summary>
        /// <param name="path">Path of the script to load from the pathNameAlias folder.</param>
        /// <param name="pathNameAlias">Folder alias to look for the script in.</param>
        public static void AddScript(string path, string pathNameAlias = "Scripts", int priority = 10)
        {
            Page page = (Page)HttpContext.Current.Handler;
            IEnumerable<ClientDependencyLoader> cdl = page.SearchControl("ClientLoader").Cast<ClientDependencyLoader>();

            if (cdl.Count() > 0)
            {
                cdl.First().RegisterDependency(priority, path, pathNameAlias, ClientDependencyType.Javascript);
            }
        }

        /// <summary>
        /// Add another stylesheet to the dependency loader list to be compressed and combined with other resources.
        /// </summary>
        /// <param name="path">Path of the stylesheet to load from the pathNameAlias folder.</param>
        /// <param name="pathNameAlias">Folder alias to look for the stylesheet in.</param>
        public static void AddStyle(string path, string pathNameAlias = "Styles")
        {
            Page page = (Page)HttpContext.Current.Handler;
            IEnumerable<ClientDependencyLoader> cdl = page.SearchControl("ClientLoader").Cast<ClientDependencyLoader>();

            if (cdl.Count() > 0)
            {
                cdl.First().RegisterDependency(10, path, pathNameAlias, ClientDependencyType.Css);
            }
        }
        #endregion
    }
}

Then you can use the following in a macro to add a resource. Note that this will not work on macros that use Cacheing, in those cases the scripts would need to be added to the templates instead.

@using JSP
@{
  CDL.AddScript("jquery-ui-1.9.js");
  CDL.AddCss("ContentTabbed.css");
}

Tuesday, December 11, 2012

Umbraco Using Courier to Deploy Multi-Node Tree Pickers

The same problem that I mentioned relating to the Deploying MultiType data occurs with other uComponets data types as well.  The Multi-Node Tree Picker for example has the same problem if you user XML as the storage method.  You could get around this by using CSV but that is harder to work with in macro code.  The following is another sample data provider to add support for deploying MNTP-XML Data(Data Editor GUID: 7e062c13-7c41-4ad9-b389-41d88aeef87c).


using Umbraco.Courier.Core;
using Umbraco.Courier.Core.Enums;
using Umbraco.Courier.Core.Helpers;
using Umbraco.Courier.DataResolvers;
using Umbraco.Courier.ItemProviders;

using System.Collections.Generic;
using System.Linq;

namespace JSP {
    /// <summary>
    /// Custm Data Resolver for use with Multi-Node Tree Picker data objects.  Will convers a list of xml nodes to guids for deployment and back again.
    /// </summary>
 public class MNTPXml : PropertyDataResolverProvider
 {
  /// <summary>
        /// The datatype guid of the multitype item we are running on.
        /// </summary>
        public override Guid DataTypeId
        {
            get { return new Guid("7e062c13-7c41-4ad9-b389-41d88aeef87c"); }
        }

        /// <summary>
        /// Run when a property of the specified type is packaged up.
        /// </summary>
        public override void PackagingProperty(Item item, ContentProperty propertyData)
        {
            string nodeXpath = "//nodeId";

            // Document References
            List<string> replacedIds = new List<string>();
            propertyData.Value = XmlDependencies.ReplaceIds(propertyData.Value.ToString(), nodeXpath, IdentifierReplaceDirection.FromNodeIdToGuid, out replacedIds);

            // List all id's found and make them dependencies.
            foreach (string guid in replacedIds)
            {
                // Add as a dependency. (working?)
                item.Dependencies.Add(guid, ProviderIDCollection.documentItemProviderGuid);
            }
        }

        /// <summary>
        /// Run when a property of the specified type is extracted.
        /// </summary>
        public override void ExtractingProperty(Item item, ContentProperty propertyData)
        {
            string nodeXpath = "//nodeId";

            // Document References
            List<string> replacedIds = new List<string>();
            propertyData.Value = XmlDependencies.ReplaceIds(propertyData.Value.ToString(), nodeXpath, IdentifierReplaceDirection.FromGuidToNodeId, out replacedIds);
        }
}

Thursday, October 4, 2012

Load Umbraco nodes by Guid and print Guid's in Razor

Umbraco internals provide methods for accessing objects by Guid instead of ID.  This is necessary when using multiple environments and moving files around with Courier.  The reason being that ID's are not unique across instances and as items are moved around they will get assigned new ID's.  The following for example will not work as expected when deployed with Courier.

@Model.NodeById("Some static pageId").myVariable

To do this properly the Guid is needed instead. The problem though is that the Razor DynamicNode object doesn't expose or let you load nodes by the Guid.  The included class can be added to your App_Code directory to allow the following code samples to work in Razor.

using JSP;
// Get a object by the Guid
@Model.NodeByGuid("someGuid").myVariable
// Print a object's Guid
@Model.UniqueId()


GuidExtensions.cs
using System;
using umbraco;
using umbraco.MacroEngines;
using umbraco.cms.businesslogic;

namespace JSP
{
    /// <summary>
    /// Extensions to the umbraco framework.
    /// </summary>
    public static class GuidExtensions
    {

        /// <summary>
        /// Get a node by the unique Guid.  This is needed for sites where courier is in use.
        /// </summary>
        /// <param name="self">Current Page</param>
        /// <param name="guid">Guid to load.</param>
        /// <returns>DynamicNode</returns>
        public static DynamicNode NodeByGuid(this DynamicNode self, string guid)
        {
            return self.NodeByGuid(new Guid(guid));
        }

        /// <summary>
        /// Get a node by the unique Guid.  This is needed for sites where courier is in use.
        /// </summary>
        /// <param name="self">Current Page</param>
        /// <param name="guid">Guid to load.</param>
        /// <returns>DynamicNode</returns>
        public static DynamicNode NodeByGuid(this DynamicNode self, Guid guid)
        {
            return self.NodeById(new CMSNode(guid).Id);
        }

        /// <summary>
        /// Get the unique ID for a selected node.
        /// </summary>
        /// <param name="self">Node</param>
        /// <returns>guid</returns>
        public static string UniqueId(this DynamicNode self)
        {
            return new CMSNode(self.Id).UniqueId.ToString();
        }
    }
}

Edit Macros that are Cached in Umbraco

One of the really annoying problems in Umbraco is that is when editing Macro Scripting Files for Macros that are cached your changes will not appear until the cache expires and is rebuilt.  To get around this you have to go and turn off caching on the macro that you are working on and then remember to turn it back on when done.

A much nicer solution to this problem is simply to clear out the cached versions of the macro whenever its script file is saved.  In this way the new version of the macro will always be displayed when refreshing the page after any changes.  This code can be added to the App_Code directory of an umbraco install to do jus that.

ClearMacroCache.cs
using System.Web;
using umbraco.BusinessLogic;
using umbraco.IO;
using System.IO;
using System.Web.Hosting;
using ClientDependency.Core.Config;
using System.Text.RegularExpressions;
using System.Linq;
using umbraco.cms.businesslogic.macro;
using umbraco.cms.businesslogic.cache;

namespace JSP
{
    public class ClearMacroCache : ApplicationBase
    {
        public ClearMacroCache()
        {
            // Get the macro folder.
            var macroPath = HttpContext.Current.Server.MapPath(SystemDirectories.MacroScripts);

            // Watch for file changes ot any macros.
            HttpContext.Current.Application.Add("macroWatcher", new FileSystemWatcher(macroPath));
            var macroFsw = (FileSystemWatcher)HttpContext.Current.Application["macroWatcher"];
            macroFsw.EnableRaisingEvents = true;
            macroFsw.IncludeSubdirectories = true;

            // Trigger an event on any change.
            macroFsw.Changed += new FileSystemEventHandler(expireClientDependency);
            macroFsw.Created += new FileSystemEventHandler(expireClientDependency);
            macroFsw.Deleted += new FileSystemEventHandler(expireClientDependency);
        }

        /// <summary>
        /// This method is called each time a macro file is saved/updated and will clear out and cached versions 
        ///     of the macro to eliminate cacheing problems when developing macros.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void expireClientDependency(object sender, FileSystemEventArgs e)
        {
            // If this is a real macro file.
            Regex reg = new Regex(@"\d+_");
            if(!reg.Match(e.Name).Success)
            {
                var macro = Macro.GetAll().SingleOrDefault(i => i.ScriptingFile.ToLower() == e.Name.ToLower());

                if (macro != null)
                    Cache.ClearCacheByKeySearch("macroHtml_" + macro.Alias);
            }
        }
    }
}

Monday, October 1, 2012

Automatic Unobtrusive Binding of Labels to Form Elements

One commonly forgotten feaure in HTML is that when creating web forms the labels related to input elements should be related to them.  In the simplest terms it is done like so:

<label for="myItem">My Label</label><input id="myItem">
The benifit to doing this is that browser will automatically focus the input element when the label is selected.  A little thing I know but it's not use in most sites doe to inconvenience.

Thus I have the following bit of javascript to automatically do the same thing.  It looks for any unbound  labels and binds them to the next input, text area, or select item down the DOM tree from them.  That is the next matching element within the same parent that the label is in.  This method will also work if the input fields don't have ID's associated with them as an added bonus.

 To use just include this in a global script running on your site and it will do the work with no noticeable overhead.  This does require jQuery to work.
$(document).ready(function () {
  $("form label").each(function() {
    if (typeof $(this).attr("for") == "undefined" || $("#" + $(this).attr("for")).length == 0) {
      nextControl = $(this).next("input,textarea,select");
      if (nextControl.length > 0) {
        if (typeof nextControl.attr("id") != "undefined") {
          $(this).attr("for", nextControl.attr("id"));
        } else {
          $(this).removeAttr("for");
          $(this).click(function() {
            $(this).next("input,textarea,select").focus()
          });
        }
      }
    }
  });
});

Monday, September 24, 2012

LINQ OrderBy sorting without a Strong Typed parameter expression.

When setting up a simple ASPX page with a DataGrid to quickly display a LINQ to SQL result without having to write a bunch of paging and sorting code I ran into a bit of an issue. The loading and paging worked perfectly with the following sample but I could not get sorting to work and a clean and concise manner.

<asp:DataGrid ID="Data" runat="server" AllowPaging="True" AllowSorting="True" 
    onpageindexchanged="Data_PageIndexChanged" onsortcommand="Data_SortCommand">
</asp:DataGrid>
FormLandingPageRepository repo = new FormLandingPageRepository();

protected void Page_Load(object sender, EventArgs e)
{
    Data.DataSource = repo.Find().OrderByDescending(i => i.dateAdded);
    Data.DataBind();
}

protected void Data_PageIndexChanged(object source, DataGridPageChangedEventArgs e)
{
    Data.CurrentPageIndex = e.NewPageIndex;
    
    Data.DataSource = repo.Find().OrderByDescending(i => i.dateAdded);
    Data.DataBind();
}

protected void Data_SortCommand(object source, DataGridSortCommandEventArgs e)
{     
    repo.Find().OrderBy(e.SortExpression);
    Data.DataBind();
}

The problem turned out to be that the OrderBy operations in LINQ require strong types clauses in Expressions but my DataGrid is returning a simple string as the order by expression.  I could get around this by creating a switch statment and converting to a Strong Typed expression but that would require a lot of hard coded logic for every table.

Instead I found this Extensions after much searching and testing that adds the ability to do OrderBy statements in LINQ with a string value instead.  Thus after adding the Extension code the above sort by works as exacted. (Note that the sort expression will need to be stored in the ViewState and sorting logic applied in PageIndexChanged as well for Paging+Sorting to work.)

SortExstensions.cs
using System;
using System.Text.RegularExpressions;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace JSP
{
    public static class SortExtensions
    {
        /// <summary>
        /// Sort Directions.
        /// </summary>
        enum SortMode
        {
            OrderBy,
            OrderByDescending,
            ThenBy,
            ThenByDescending
        }

        /// <summary>
        /// Sorts the elements of a sequence in ascending order according to a key.
        /// </summary>
        /// <typeparam name="T">The type of the elements of source.</typeparam>
        /// <param name="source">A sequence of values to order.</param>
        /// <param name="property">Property name to sort on.</param>
        /// <returns>An System.Linq.IOrderedQueryable<T> whose elements are sorted according to a key.</returns>
        public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string property)
        {
            // If this is a sort list then OrderBy the first element and then ThenBy the rest.
            if (property.Contains(','))
            {
                string[] parts = property.Split(new char[] {','}, 2);
                return ApplyOrder<T>(source, parts[0], SortMode.OrderBy).ThenBy(parts[1]);
            }
                
            return ApplyOrder<T>(source, property, SortMode.OrderBy);
        }

        /// <summary>
        /// Sorts the elements of a sequence in descending order according to a key.
        /// </summary>
        /// <typeparam name="T">The type of the elements of source.</typeparam>
        /// <param name="source">A sequence of values to order.</param>
        /// <param name="property">Property name to sort on.</param>
        /// <returns>An System.Linq.IOrderedQueryable<T> whose elements are sorted according to a key.</returns>
        public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string property)
        {
            // If this is a sort list then OrderBy the first element and then ThenBy the rest.
            if (property.Contains(','))
            {
                string[] parts = property.Split(new char[] { ',' }, 2);
                return ApplyOrder<T>(source, parts[0], SortMode.OrderByDescending).ThenByDescending(parts[1]);
            }

            return ApplyOrder<T>(source, property, SortMode.OrderByDescending);
        }

        /// <summary>
        ///  Performs a subsequent ordering of the elements in a sequence in ascending order according to a key.
        /// </summary>
        /// <typeparam name="T">The type of the elements of source.</typeparam>
        /// <param name="source">A sequence of values to order.</param>
        /// <param name="property">Property name to sort on.</param>
        /// <returns>An System.Linq.IOrderedQueryable<T> whose elements are sorted according to a key.</returns>
        public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string property)
        {
            // If ther are multiple sort items then sort by each in order.
            if (property.Contains(','))
            {
                string[] parts = property.Split(new char[] { ',' }, 2);
                return ApplyOrder<T>(source, parts[0], SortMode.ThenBy).ThenBy(parts[1]);
            }

            return ApplyOrder<T>(source, property, SortMode.ThenBy);
        }

        /// <summary>
        ///  Performs a subsequent ordering of the elements in a sequence in descending order, according to a key.
        /// </summary>
        /// <typeparam name="T">The type of the elements of source.</typeparam>
        /// <param name="source">A sequence of values to order.</param>
        /// <param name="property">Property name to sort on.</param>
        /// <returns>An System.Linq.IOrderedQueryable<T> whose elements are sorted according to a key.</returns>
        public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string property)
        {
            // If ther are multiple sort items then sort by each in order.
            if (property.Contains(','))
            {
                string[] parts = property.Split(new char[] { ',' }, 2);
                return ApplyOrder<T>(source, parts[0], SortMode.ThenByDescending).ThenByDescending(parts[1]);
            }

            return ApplyOrder<T>(source, property, SortMode.ThenByDescending);
        }

        /// <summary>
        /// Apply a custom Order By statment to an IQueryable.
        /// </summary>
        /// <typeparam name="T">The type of the elements of source.</typeparam>
        /// <param name="source">A sequence of values to order.</param>
        /// <param name="property">Property name to sort on.</param>
        /// <param name="methodName">Sort method to use.</param>
        /// <returns>An System.Linq.IOrderedQueryable<T> whose elements are sorted according to a key.</returns>
        static IOrderedQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, SortMode methodName)
        {
            string[] props = property.Split('.');

            Type type = typeof(T);
            ParameterExpression arg = Expression.Parameter(type, "x");
            Expression expr = arg;

            foreach (string prop in props)
            {
                PropertyInfo pi = type.GetProperty(prop);
                expr = Expression.Property(expr, pi);
                type = pi.PropertyType;
            }

            Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
            LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);

            object result = typeof(Queryable).GetMethods().Single(
                    method => method.Name == methodName.ToString("g")
                            && method.IsGenericMethodDefinition
                            && method.GetGenericArguments().Length == 2
                            && method.GetParameters().Length == 2)
                    .MakeGenericMethod(typeof(T), type)
                    .Invoke(null, new object[] { source, lambda });
            return (IOrderedQueryable<T>)result;
        }
    }
}

Friday, September 7, 2012

Firefox Memory Leak on OSX

Coming back to work after a long weekend I found this on my iMac.  Lets just say it wasn't working so well.  50 GB worth of memory paged out to the swap file due to a Memory Leak in Firefox.  And they wonder why I prefer Safari.  By the way I only had three tabs open. :/


Thursday, August 30, 2012

Maximum File Upload Size in IIS 7

I have investigated and done a lot of testing as the information online is somewhat inaccurate/misleading.  In the end i Determined that the following setting all need to be configured to allow for large file uploads.  If not the default settings will stop uploads greater then about 20 MB.

The following change need made to the web.config file.
<system.web>
    <httpRuntime requestValidationMode="2.0" executionTimeout="600" maxRequestLength="2000000" />
  <system.web>
  <system.webServer>
    <security>
      <requestFiltering>
        <requestLimits maxAllowedContentLength="2000000000" />
      </requestFiltering>
    </security>
  </system.webServer>

The executionTimeout is extending the maximum script execution to 10 minutes witch will be required for larger uploads.  The maxRequestLength is the maximum size in kilobytes while the maxAllowedContentLength is the same maximum size but in bytes.  So maxAllowedContentLength should be 1000 times the maxRequestLength.

These numbers of course can be adjusted as needed.

On a final note you may want to check C:\Windows\System32\inetsrv\config\applicationHost.config and verify that the following line is present and set to Allow.
<section name="requestFiltering" overrideModeDefault="Allow" />

Umbraco Using Courier to Deploy MultiType Data

When using Umbraco and doing production deployments with the Courier package I ran into an issue.  We have had the process working fine with built in components but recently I deployed a new property that used the MultiType DataType which  allows for arrays of data fields grouped together.  For example we have the following grouping and multiple images can be added to the one parameter in Umbraco.

RotatingImages
  Item
    ImageSrc(Media Picker)
    MobileSrc(Media Picker)
    AltText(Text)
    LinkTo(Content Picker)

The problem comes in when we publish this item, the content and media pickers contain the node id's and the data is stored as XML.  As such Courier will not process the ID's when it does the deployment process.  To fix this a custom PropertyDataResolverProvider is needed.  The following code can be included in the App_Code directory to fix this problem.



using Umbraco.Courier.Core;
using Umbraco.Courier.Core.Enums;
using Umbraco.Courier.Core.Helpers;
using Umbraco.Courier.DataResolvers;
using Umbraco.Courier.ItemProviders;

using System.Collections.Generic;
using System.Linq;

namespace JSP {
    /// <summary>
    /// Custm Data Resolver for use with MultiType data objects.  Will convers a list of xml nodes to guids for deployment and back again.
    /// </summary>
    public class MultiType : PropertyDataResolverProvider
    {
        /// <summary>
        /// The datatype guid of the multitype item we are running on.
        /// </summary>
        public override Guid DataTypeId
        {
            get { return new Guid("f17bb230-3941-4813-a923-e7c3efd067d8"); }
        }

        /// <summary>
        /// Get the xpath elements from the data types that contain document or media id's.
        /// </summary>
        /// <param name="propertyData">Property to examin.</param>
        /// <param name="documentXPath">XPath string to select all fields with document ID's.</param>
        /// <param name="mediaXPath">XPath string to select all fields with media ID's.</param>
        private void GetXPath(ContentProperty propertyData, out string documentXPath, out string mediaXPath)
        {
            // Get the data definition for this item.
            var dataDefinition = new umbraco.cms.businesslogic.datatype.DataTypeDefinition(propertyData.DataType);
            
            // Get the item configureation and locate the preValue property.
            var preValue = umbraco.library.GetPreValues(dataDefinition.DataType.DataTypeDefinitionId);
            preValue.MoveNext();
            var preValueIterator = preValue.Current.SelectChildren("preValue", "");
            preValueIterator.MoveNext();

            // Deserialize the preValue data.
            dynamic data = new System.Web.Script.Serialization.JavaScriptSerializer().DeserializeObject(preValueIterator.Current.Value);
            // Get the MultiType Children properties.
            var mprop = ((object[])((Dictionary<string, object>)data)["MultiTypes"]).Cast<Dictionary<string, object>>();
            
            // Get all content picker properties and add the alias to the list.  Resutls: //Node1 | //Node2 | ...
            documentXPath = String.Join(" | ", mprop.Where(i => Convert.ToInt32(i["Type"]) == (int)_4Ben.DataTypes.MultiType.ControlType.ContentPicker).Select(i => "//" + i["Alias"].ToString()));
            // Get all media picker properties and add the alias to the list.  Resutls: //Node1 | //Node2 | ...
            mediaXPath = String.Join(" | ", mprop.Where(i => Convert.ToInt32(i["Type"]) == (int)_4Ben.DataTypes.MultiType.ControlType.MediaPicker).Select(i => "//" + i["Alias"].ToString()));
        }

        /// <summary>
        /// Run when a property of the specified type is packaged up.
        /// </summary>
        public override void PackagingProperty(Item item, ContentProperty propertyData)
        {
            string documentXpath;
            string mediaXpath;

            // Get the elements from this data type that need to be converted.  This could be hard coded but the queries are quick.
            GetXPath(propertyData, out documentXpath, out mediaXpath);

            // Document References
            List<string> replacedIds = new List<string>();
            propertyData.Value = XmlDependencies.ReplaceIds(propertyData.Value.ToString(), documentXpath, IdentifierReplaceDirection.FromNodeIdToGuid, out replacedIds);

            // List all id's found and make them dependencies.
            foreach (string guid in replacedIds)
            {
                // Add as a dependency. (working?)
                item.Dependencies.Add(guid, ProviderIDCollection.documentItemProviderGuid);
            }

            // Media References
            //dataXpath = ConfigurationManager.AppSettings["courierMultiTypeMediaNodes"].ToString();
            replacedIds = new List<string>();
            propertyData.Value = XmlDependencies.ReplaceIds(propertyData.Value.ToString(), mediaXpath, IdentifierReplaceDirection.FromNodeIdToGuid, out replacedIds);

            // List all id's found and make them dependencies.
            foreach (string guid in replacedIds)
            {
                // Add as a dependency. (working?)
                item.Dependencies.Add(guid, ProviderIDCollection.mediaItemProviderGuid);

                // Could add as a resource but in reality the entire node is needed witch wil transwer the resource so this line should not be run.
                //item.Resources.Add(new umbraco.cms.businesslogic.media.Media(new Guid(guid)).getProperty("umbracoFile").Value.ToString());
            }
        }

        /// <summary>
        /// Run when a property of the specified type is extracted.
        /// </summary>
        public override void ExtractingProperty(Item item, ContentProperty propertyData)
        {
            string documentXpath;
            string mediaXpath;

            // Get the elements from this data type that need to be converted.  This could be hard coded but the queries are quick.
            GetXPath(propertyData, out documentXpath, out mediaXpath);

            // Document References
            List<string> replacedIds = new List<string>();
            propertyData.Value = XmlDependencies.ReplaceIds(propertyData.Value.ToString(), documentXpath, IdentifierReplaceDirection.FromGuidToNodeId, out replacedIds);

            // Media References
            replacedIds = new List<string>();
            propertyData.Value = XmlDependencies.ReplaceIds(propertyData.Value.ToString(), mediaXpath, IdentifierReplaceDirection.FromGuidToNodeId, out replacedIds);

        }
    }
}

Tuesday, July 24, 2012

jQuery Plugin to Scroll an Element with the Page

Updated 11/7/2013: Added support for positioning via padding instead of margins.

This simple javascript plugin is a very simple bit of code that can be attached to any element on a web page.  It will make the element work similar of a position:fixed element in that it will cause the element to scroll with the browser window vertically as the user scrolls up and down.

The benefit of this over the css attribute is that this method will retain the items original placement and flow instead simply adjust the margin-top to replicate the scrolling logic.  Additional paddingTop and paddingBottom can be specified witch will act as minimum margins between the viewport and the floating item.

Sample Code:

$("#element").autoScroll({ paddingTop: 30 });

The query Plugin code is available on GitHub here.  You will need to include jQuery and then this file in your page header to use it.

Friday, July 6, 2012

Web Form Validation the Right/Simple Way.

Another topic that isn't all all new.  There are millions of forms out there and they all do user input validation differently.  The long and the short of this article is that there are many approaches but we want a best practice that meets a few requirements.  It must be quick, efficient, simple to configure, but able to do all the validation logic we may need.  There are lots of frameworks or custom code to do this but as I mentioned I am going for the most elegant solution here.  So here is goes, here is a sample form with the validation logic.

<script type='text/javascript' src='http://code.jquery.com/jquery-1.7.2.min.js'></script>
<script type='text/javascript' src='http://ajax.aspnetcdn.com/ajax/jquery.validate/1.9/jquery.validate.js'></script>
<script type='text/javascript'>
$(document).ready(function() {
 $("#myForm").validate();
}
</script>

<form action='?' method='post' id='myForm'>
 <p>
   <label for='firstName'>First Name:</label> <input name='firstName' id='firstName' class='required' type='text'>
 </p>
 <p>
   <label for='lastName'>Last Name:</label> <input name='lastName' id='lastName' class='required' type='text'>
 </p>
 <p>
  <label for="custType">Type:</label> <select name="custType" id="custType" class='required'>
   <option value=""></option>
   <option value="vendor">Vendor</option>
   <option value="salesrep">Sales Rep</option>
   <option value="supplier">Supplier</option>
   <select>
 </p>
 <p>
  <label for='emailAddress'>Email Address:</label> <input name='emailAddress' id='emailAddress' class='required email' type='text'>
 </p>
 <p>
  <label for='primaryPhone'>Phone Number:</label> <input name='primaryPhone' id='primaryPhone' class='required phoneUS' type='text'>
 </p>
 <p>
  <label for='zipCode'>Zip Code:</label> <input name='zipCode' id='zipCode' class='required zip' type='text'>
 </p>
 <p>
  <input type='submit' name='save' id='save' value='Save'>
 </p>
</form>

This might look quite impel but in reality is is doing nearly all the validation we need for this simple form.  I am using the jQuery Validation plugin to do all the heavy lifting here.  This is also a very elegant solution.  You will notice that there is only one javascript call on the page yet all the fields are being validated.  How you ask? While the documentation doesn't covert it very well but jquery.validation will acutely use the css class markup to apply validation rules.  See the form controls that have the classes required, email, phoneUS, and zip.  These are what trigger the validation logic.  In essence this is the same to the following script tag but this little know markup is much cleaner.

$("#myForm").validate({
   rules: {
     firstName: "required",
     lastName: "required",
     custType: "required",
     emailAddress: {
       required: true,
       email: true
     },
     primaryPhone: {
       required: true,
       phoneUS: true
     },
     zipCode: {
       required: true,
       digits: true,
       minlength: 5,
       maxlength: 5
     }
   }
})

Now you can see why the css markup trick is a lot cleaner.  It also keeps the rules along with the fields they are affecting to it is easier to track down problems with the validation.

On another point you may notice that the first sample I posted is not validating the phone number and zip code properly.  That is because I created custom class rules and methods that are not in the validation framework.  The following class rule, think alias or marco, and method need to be added to the script tag after the validation include or more simply just added to a site wide script file.  The first is a macro that applies multiple rules as a set.  This also allows you to apply rules with parameters witch you can't do in the css markup.  The second is an entirely new method that does some complex regular expression validation.

jQuery.validator.addClassRules({
  zip: {
    digits: true,
    minlength: 5,
    maxlength: 5
  }
});

jQuery.validator.addMethod("phoneUS", function(phone_number, element) {
    phone_number = phone_number.replace(/\s+/g, "");
 return this.optional(element) || phone_number.length > 9 &&
  phone_number.match(/^(1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/);
}, "Please specify a valid phone number");

This approve can now be used to add as complex of validation logic as needed.  The user will automatically see nice callouts when they try to submit the form with errors on it.  Also of note this solution will fail gracefully so if there is a script error or javacscript is just unavailable the page will still function and post to the server.  Now with that in mind it brings me to my final point.  The receiving server side script should always to its own validation to catch not only these rare cases where javacript is disabled or some other error occurred, but also to catch cases there some user is intentionally trying to submit invalid data.  And clint side validation like this can easily be disabled but the end user and a invalid form submitted.  Thus the onus is always on the server code/database for true validation.

Friday, June 29, 2012

Show prompt/Run script when navigating away from a web page.

In the world of web browsers there are some real limitations to responding to user actions.  Specifically when a user is viewing a web page they can navigate away from said page in a few different ways and it can be tricky to deal with and respond to these events.  This is not a new topic but I have found the details online to be somewhat lacking.

A user can leave a page in many different ways.  First of all they can simply click a web page link to another internal or external URL.  This is the simplest action and it triggers a click event which can be responded to with a script and even canceled entirely.  The following for example will show a message when the user click an external link(indicated by the presence of the class "external").

$(document).ready(function() 
  $("a.external").click(function() {
   alert("Leaving to " + $(this).attr("herf"));
 });
});

But how can we react to other types of navigation such as the browser back and forward buttons, page reload, url change, and page close events.  This can be done to a limited extent with the onbeforeunload event though we can't actually see what action the user preformed to leave the page.  There is a further problem here though.  Simply adding a event handler on the onbeforeunload will also run when the user clicks links inside the site witch we may not want.  With that in mind the following code will attach a event handler to the onbeforeunload but will discard it when it detects that a internal or external link was clicked witch can be captured normally.  Additinal link level logic can then be added to the link click events.

$(document).ready(function() {
    // Listen for the unload event and return a warning/run custom code.
    window.onbeforeunload = function (event) {
      if(true) {
        return "You have items in your cart.";
      }
    }
   // If disable event on link clicks that are not hash links, mailto, or javascript calls.
    $("a[href]:not([href^=#],[href^=mailto],[href^=javascript])").click(function(){
      window.onbeforeunload = null;
    });
});

This bit of code will allow for a script to run when the user tries to leave the page and can warn about unsaved changes, etc.  Be warned though that this is only a prompt and furthermore that in most cases this type of alert/warning to users may be irritating.  It should only be used to trigger warnings about unsaved changes, etc.  Also there are some other limits here.  For example you can't redirect the browser to a new url on page exit as the browser is already changing pages.

Monday, June 25, 2012

Cleanly replace links with jQuery UI Dialogs.

This but of code along with a bit of link decoration will automatically open all specified links in dialog popup windows instead of in new browser windows.  It benefit here is that this code is quite small, clean, and automatically applied to all matched elements on the page without having to write custom code or hooks for each link.

First of all make sure that you are including jQuery, jQuery UI and a jQuery UI Stylesheet.

<script src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<script src="http://code.jquery.com/ui/1.8.21/jquery-ui.min.js"></script>
<link href="http://code.jquery.com/ui/1.8.21/themes/base/jquery-ui.css" rel="stylesheet"></link>

Next add the following code to a external javascript file that is included on this page or into the page code itself. Not that this needs to be included/executed after the above scripts are included.


ConfigureDialogPopups.js
// make sure the page is fully loaded.
$(document).ready(function() {
  // For each link makred up with the popup attribute change the link to open the contents in a jquery dialog instead.
  $("a[popup]").each(function() {
    $(this).click(function() {
      dest = $(this).attr("popup");
      // Get the page contents with a background ajax query.
      $.get($(this).attr("href"), function(data) {
        // Push the results into a div tag with a custom id to spit out the script and style elements.
        $("<div id='"+dest+"' class='"+dest+"'>" + data + "</div>").each(function() {
          if($(this).is("div")) {
            // Add the div to the page as a popup.
            $(this).dialog({
              close: function() {
              $(this).remove();
              },
              modal: true,
              resizable: false
            });
            // Move the first p to the dialog title. 
            $("#"+dest).dialog( "option", "title", $("#"+dest+">p").first().html() );
            $("#"+dest+">p").first().remove();

            // Update the style of the header.
            $("span.ui-dialog-title").addClass("subhead14");
          } else {
            // Add all other elements to the body directly.
            $(this).appendTo("body");
          }
        });
      });
      // Return false so the link isn't clicked.
      return false;
    }); 
  });
});

The final step is to go through the page and decorate and links you want to open in dialog popups with the popup attribute. This should be added directly to the A link that will trigger the popup and the href will be be the location to retrieve and place into the popup. The value of the popup attribute needs to be assigned a unique id that will be assigned to the resulting dialog object.

 For example the following link can be make up as so to change it from a normal page link to a popup dialog.
<a href="page2.hmtl">View Page 2</a>
<a popup="page2dialog" href="page2.hmtl">View Page 2 (Popup)</a>

Furthermore the dialog can be accessed and manipulated as needed with the following jQuery selector.
$("#page2dialog")