Tuesday 17 February 2015

The rich page/experience editor

As we move down the path of personalisation in Sitecore we are finding that our editors are spending more and more time in the "Page/Experience Editor" and less time in the "Content Editor". This means that editors are starting to expect to be able to do all of their editing in the "Experience Editor", and why shouldn't they? We are rendering the page in edit mode, we have full control, lets use it.

Below is a simple example of what can be done using a carousel as an example. Now put aside your concerns about carousels (http://shouldiuseacarousel.com/), this is designed as a thought provoking exercise aimed at getting you to think about how you can make life better for you content editors within your own project domain, not as a way of building a carousel.

The reason that a carousel is useful as a thought provoker is that it has some characteristics that don't make it feel like an easy fit for the experience editor. These include
  • Unknown number of slides
  • The order of content in a carousel is important - the first slide gets more views then the last
So without further adieu lets show you what the experience editor experience could be like


Notice the new buttons? This style of functionality has typically been implemented using edit frames and there is a lot of content there around it (e.g. http://www.cmsbestpractices.com/how-to-properly-use-sitecore-edit-frames/), but  we can also include these buttons directly into your site and and simplify the editing experience.

Now what have I actually done to get this running, well there are two main things, the first being accept that you are going to need to do different things in edit mode to delivery mode. This is as simple as adding an if(or multiple ifs) to your view layer to check what the current page mode is.


The next step is to understand what commands are available to you and how you can additional commands. As mentioned before, traditionally these commands would be implemented using the edit frames functionality and the commands that I am using are exactly the same. 

If you were to configure an edit frame or sub layout with a series of commands and inspect it you would see something like the following.


Essentially each edit frame button/command is a JavaScript click event with a command, webedit:new, and an item id associated with it. Knowing that Sitecore uses this approach to triggering commands we can build our own renderings taking this into account. So going back to the buttons in my carousel, each button essentially has the same JavaScript rendered from an MVC helper as demonstrated below.

namespace Website.Helpers
{
    using System.Web;
    using Sitecore.Data.Items;

    public class PageEditorHelpers
    {
        public HtmlString NewItemButton(Item parentItem, string buttonText)
        {
            return new HtmlString(string.Format(@"<a href=""#"" class=""button"" onclick=""javascript:Sitecore.PageModes.PageEditor.postRequest('webedit:new(id={1})')"">{0}</a>", buttonText, parentItem.ID));
        }        
        
        public HtmlString DeleteItemButton(Item parentItem, string buttonText)
        {
            return new HtmlString(string.Format(@"<a href=""#"" class=""button"" onclick=""javascript:Sitecore.PageModes.PageEditor.postRequest('webedit:delete(id={1})')"">{0}</a>", buttonText, parentItem.ID));
        }        
        
        public HtmlString MoveItemUpButton(Item parentItem, string buttonText)
        {
            return new HtmlString(string.Format(@"<a href=""#"" class=""button"" onclick=""javascript:Sitecore.PageModes.PageEditor.postRequest('item:moveup(id={1})')"">{0}</a>", buttonText, parentItem.ID));
        }        
        
        public HtmlString MoveItemDownButton(Item parentItem, string buttonText)
        {
            return new HtmlString(string.Format(@"<a href=""#"" class=""button"" onclick=""javascript:Sitecore.PageModes.PageEditor.postRequest('item:movedown(id={1})')"">{0}</a>", buttonText, parentItem.ID));
        }

        public HtmlString SortItemsButton(Item parentItem, string buttonText)
        {
            return new HtmlString(string.Format(@"<a href=""#"" class=""button"" onclick=""javascript:Sitecore.PageModes.PageEditor.postRequest('webedit:sortcontent(id={1})')"">{0}</a>", buttonText, parentItem.ID));
        }
    }
}

Once we have the helper setup it's easy to include them in the markup and customise the experience for our editors.

Now keep in mind that this was a thought provoking exercise and you can create any custom command you like, or add any code you like. What can you do for your editors? The ideas become endless and all of those custom UI's you build for you public users can now also be used to enhance the experience for you editors.

What happens when you cast an IQueryable to an IEnumerable

Recently I was digging through some code and came across an issue where the "Where" expressions I was applying in code were not being run against the database. After a bit of digging it turned out to be related to an implicit cast of an IQueryable<T> to and IEnumerable<T>. This cast affects the query generation process with every clause added after the cast being run in memory.

So how about an example to demonstrate it and make it clearer. Looking at the two unit tests below, what would you expect the resulting SQL query to be? Keep in mind the IQueryable inherits from IEnumerable, so the casting is valid.

    public class UnitTest1
    {
        [TestMethod]
        public void RunQueryAsIQueryable()
        {
            IQueryable<Record> records = new ModelContext().Records;
            var result = records.Take(10).ToArray();
        }

        [TestMethod]
        public void RunQueryAsIEnumerable()
        {
            IQueryable<Record> records = new ModelContext().Records;
            var result = ((IEnumerable<Record>)records).Take(10).ToArray();
        }
    }

Here are the results from SQL profiler

As IQueryable
    SELECT TOP (10) 
    [c].[Id] AS [Id], 
    [c].[Name] AS [Name]
    FROM [dbo].[Records] AS [c]

As IEnumerable
    SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name]
    FROM [dbo].[Records] AS [Extent1]

As you can see the IQueryable "Take/Top" clause is run against the database, where as the IEnumerable "Take" clause is not. Now consider the following example.

    public class UnitTest1
    {
        [TestMethod]
        public void RunHybridQuery()
        {
            IQueryable<Record> records = new ModelContext().Records;
            var result = ((IEnumerable<Record>)records.Where(record => record.Name.StartsWith("something"))).Take(10).ToArray();
        }   
    }

    SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name]
    FROM [dbo].[Records] AS [Extent1]
    WHERE [Extent1].[Name] LIKE N'something%'

As you can see with this example everything up until the cast(note the top 10 is missing) runs against the database.

This "unexpected" behavior is actually by design and relates to the reason why we have the two interfaces, as opposed to just IEnumerable. An IEnumerable is something that is enumerated, i.e. looped over. An IQueryable on the other hand represents something that can be queried, generally by a query language.

Seems pretty self explanatory, the confusion that comes in is when we use the LINQ syntax to mix these two concepts into a single statement. Because of the fluent nature of the LINQ API it can often be hard to know exactly what the parameter and return types of each method in your chain are and what the actual query being run is(without profiling). So the lesson here, be careful and don't rely on LINQ to do all of your work for you, it is only as smart as the person who wrote it.

How to disable "Add Users" in Sitecore's Platform DXP

 I have seen a few posts going around the community asking how to disable the "Add Users" button in Sitecore Platform DXP.   This ...