Page editor and structuring related items

Since Sitecore 6.4.1 I’ve been working alot with the Page Editor in Sitecore and how to make the editor experience as good as possible. I’ve found that it’s hard to keep the editor mainly editing in the Page Editor due to various limitations. One of those are structure.

My latest Sitecore solution, using Sitecore 7.1, utilizes the designer in the Page Editor to it’s full potential where I allow the editor to completely reorganize the content the way they see fit using rows, columns and blocks. As you can imagine this puts a higher demand on structure as one page can consist of several sub layouts which each have it’s own item and then add up that you have loads of pages constructed this way.

Idealy the editor could manage the structure at the same time as he/she is adding the content to the page in the page editor. However this is not possible out of the box in Sitecore.

Many of you might at this time say Item Buckets. However this requires the editor to search for the content and my experience with our customers is that many of them aren’t ready for this because they like to manage their own structure.

Well the solution to my problem ended up being quit simple. All I wanted was to have the context menu you have in the content editor. I also remebered that this context menu was available in the media browser when selecting media, so I started out looking there.

I found this attribute on the TreeviewEx in the media browser xml file (\sitecore\shell\Applications\Media\MediaBrowser\MediaBrowser.xml):

ContextMenu='Treeview.GetContextMenu("contextmenu")'

I made a copy of the SelectRenderingDatasource.xml (\sitecore\shell\Applications\Dialogs\SelectRenderingDatasource\SelectRenderingDatasource.xml) file to put in the override folder and added that attribute to the TreeviewEx in that file and the Context Menu was there. However it ended up opening a little off as you can see in this video http://screencast.com/t/FkwhxHcM. So I had to make some adjustments to the code opening the context menu.

I ended up altering the attribute to this:

ContextMenu='Treeview.GetContextMenu("dialoguemenu")'

And make the following changes to the \sitecore\shell\Controls\Browser.js, I replaced this part:

if (dimensions.width > 0) {
  switch (data.where) {
    case "contextmenu":
      x = evt.pageX || evt.x;
      y = evt.pageY || evt.y;
    break;

With this:

if (dimensions.width > 0) {
  switch (data.where) {
    case "contextmenu":
    case "dialoguemenu":
      x = evt.pageX || evt.x;
      y = evt.pageY || evt.y;
    break;

And wrapped this part:

var vp = ctl.viewportOffset();
x += vp.left;
y += vp.top;

With this if case:

if (data.where != "dialoguemenu") {
  var vp = ctl.viewportOffset();
  x += vp.left;
  y += vp.top;
}

This made the context menu pop up where the mouse was when right clicking.

I know this might not be the optimal solution. I would prefer that you could configure the rendering with a folder template like you do with a datasource template which you could have a dedicated button for like the “Create new content” button and not relying on a context menu. This would increase the experience with the Page Editor it think. Unfortunatly I didn’t have more time to look into this, maybe next time 🙂

Global Conditional Rendering rules with Sitecore MVC

So I was playing around the other day with Sitecore MVC and some View Renderings. As I’m more familiar with Web Forms than MVC I started asking my self how I translate some thing I do in Web Forms to MVC like for example how would I go about hiding a view rendering. In Web Forms with Sub Layouts it’s easily done by changing visiblity like:

this.Visible = false;

So for example if the Item that the datasource would point to, didn’t have any published version in the current language, I could just hide it using that small piece of code.

Now how would I go about doing this with MVC. The only solution I found what to used jquery to hide the rendering which wasn’t good enough. So I started looking at the rules engine and conditional renderings and found that you can define global conditional rendering rules. However only with Web Forms and not MVC. According to documentation global conditional rendering rules does not work with MVC, no reason why, it just don’t… or does it?

Weeeeeell, regular conditional rendering works with MVC. So wouldn’t it be possible to extend this to include the global rules as well? After some fiddling with dotPeak on Sitecore assemblies it was quite easy to implement.

Looking at how it was done for Web Forms in Sitecore.Pipelines.InsertRenderings.Processors.EvaluateConditions which is run for every rendering when using Web Forms. And also investigate where and when Sitecore handled conditional renderings for MVC. It was quite straight forward to implement a processor processor that would evaluate the global conditional rendering rules for the MVC renderings.

Once I was done with the processor I implemented a condition that used the ConditionalRenderingsRuleContext and check if the datasource of the rendering had any versions published in the current language. Just to see of I could hide all renderings with a datasource pointing at items with no version. Guess what… worked like a charm.

You can find the source code here

Please do comment other ways to hide renderings in MVC 🙂

Follow up on: Hacking the layout rendering

In my previous post I wrote about fetching the layout definitions of sub pages and add them to a top page and making Sitecore think they belong to the top page. This was done on Sitecore.NET 6.5.0 (rev. 111123).

This week we tried implementing the same function on a Sitecore 6.6.0 (rev. 130404). While the base function worked, with it displaying the sub pages layouts on the top page, the DMS didn’t handle if there were any conditional renderings. After some looking around in the pipelines I found out that the evaluations of the conditions were done in the insertRenderings pipeline which in turn was run before the renderLayout pipeline where my pipeline to fetch the layout definitions were. So I was adding the layout definitions after Sitecore had evaluated the conditions.

<insertRenderings>
        <processor type="Sitecore.Pipelines.InsertRenderings.Processors.GetItem, Sitecore.Kernel"/>
        <processor type="Sitecore.Pipelines.InsertRenderings.Processors.AddPageDesignerRenderings, Sitecore.Kernel, Version=6, Culture=neutral"/>
        <processor type="Sitecore.Pipelines.InsertRenderings.Processors.AddRenderings, Sitecore.Kernel"/>        
        <processor type="Sitecore.Pipelines.InsertRenderings.Processors.EvaluateConditions, Sitecore.Kernel"/>
</insertRenderings>

The insertRenderings pipeline

To solve this problem I had to add the layout definitions before the conditions were evaluated which was done in the EvaluateConditions processor. What we did was to create a pipeline which basically did the same as before and add it just after the AddRenderings processor, looking like this.

public class AddSubPageRenderings : InsertRenderingsProcessor
{
    public override void Process(InsertRenderingsArgs args)
    {
        Assert.ArgumentNotNull((object)args, "args");
        if (args.ContextItem == null)
            return;
        DeviceItem device = Context.Device;
        if (device == null)return;
        if (!args.ContextItem.TemplateID.Equals(new ID("{D42E1DBF-D9F5-4DC8-A8B7-0F8B5BC5758C}"))) return;
        if (!Sitecore.Context.PageMode.IsNormal) return;
        var referenses = new List();
        foreach (var child in args.ContextItem.Children.Where(i => i.IsPublished()))
        {
            var renderings = child.Visualization.GetRenderings(device, true);
            foreach (var renderingReference in renderings)
            {
                if (!renderingReference.Placeholder.Equals("content", StringComparison.InvariantCultureIgnoreCase) && !renderingReference.Placeholder.StartsWith("/main/content", StringComparison.InvariantCultureIgnoreCase)) continue;
                renderingReference.Settings.DataSource = string.IsNullOrEmpty(renderingReference.Settings.DataSource) ? child.Paths.FullPath : renderingReference.Settings.DataSource;
                referenses.Add(renderingReference);
            }
         }
        args.Renderings.AddRange(referenses);
        args.HasRenderings = args.Renderings.Count > 0;
    }
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <insertRenderings>
        <processor patch:after="*[@type='Sitecore.Pipelines.InsertRenderings.Processors.AddRenderings, Sitecore.Kernel']" type="Test.Pipelines.AddSubPageRenderings, Test"/>
      </insertRenderings>
    </pipelines>    
  </sitecore>
</configuration>

Now all the conditional renderings from the sub pages are evaluated accordingly.

Hacking the layout rendering

I recently worked on our own company web page where the criteria is that all Sub Pages to a page in the top navigations (Top Page) should be rendered as sections on that page. The Sub Pages should be individually browsable. You should be able to edit them using the Page Editor. And as a personal goal it should support nested placeholders. Extra if the DMS personalisation could work.

Previously I had done a similar solution. But as the developer I am, I wanted to try doing it differently. The previous solution was to have a rendering go through all child items and extract all layout definitions in a specific placeholder and load the control using Page.LoadControl(“~/path”) and add the control to a regular asp placeholder on Page_Load. This didn’t allow nested placeholders and off course not the DMS features.

The plan

I knew that Sitecore at some point had to load all the renderings for the item and my plan was to add the renderings of the child items at the same time so that Sitecore would think that the layouts of the child items actually belonged to the Top Page.

So using .NET Reflector we set out to find the best place to do our magic. And after a short search it was quite obvious that we should look at the renderLayout pipeline and in the InsertRenderings processor. This processor takes all the Items layout definitions and adds them to the Sitecore.Context.Page which is what will be rendered for the current request.

Creating the Pipeline

We created our own processor and added it right after the InsertRenderings processor which looks like this.

public class FetchChildLayoutDefinition : RenderLayoutProcessor
    {
        public override void Process(RenderLayoutArgs args)
        {

            var item = Sitecore.Context.Item;
            if(item==null) return;
            if (!item.TemplateID.Equals(new ID("{99F41F1F-47B2-422A-82A9-0650B102989D}"))) return;
            if (!Sitecore.Context.PageMode.IsNormal) return;

            using (new ProfileSection("Insert child renderings into page."))
            {
                DeviceItem device = Sitecore.Context.Device;

                foreach (var child in item.Children.Where(ItemTools.IsPublished))
                {
                    var renderings = child.Visualization.GetRenderings(device, true);
                    foreach (var renderingReference in renderings)
                    {
                        if (!renderingReference.Placeholder.Equals("content", StringComparison.InvariantCultureIgnoreCase) && !renderingReference.Placeholder.StartsWith("/main/content", StringComparison.InvariantCultureIgnoreCase)) continue;
                        renderingReference.Settings.DataSource = string.IsNullOrEmpty(renderingReference.Settings.DataSource) ? child.Paths.FullPath : renderingReference.Settings.DataSource;
                        Sitecore.Context.Page.AddRendering(renderingReference);
                    }

                }
            }
        }
    }

 

A quick explanations of the processor. We only wanted to do this for a specific page on the site which has its own Templates. So we check if the current item is based on that Template. Since we actually add all the renderings so that Sitecore think they belong to the current page, this also applied for the Page Editor, so when we saved the Top Page with all the renderings gathered from the child items it ended up saving all these renderings as well which wasn’t desirable. So we hade to check that we weren’t in any other mode than normal. After that we iterate all published children and extract all renderings using the current device. Since the sub pages should be browsable it contains elements such as a header, footer navigations and so on, these renderings were already added to the Top Page, so we wanted to ignore these common renderings. To solve this we assigned a placeholder “content” that we used as a base and only used renderings that were added inside this placeholder or nested within. There are two ways you can add a rendering to a placeholder, you can either write the placeholders name like “content” or you can write the nested path of the placeholder like “/main/content” which the Page Editor does when adding a rendering, so we had to look for both of them. And when we found a rendering that fulfilled the placeholder criteria, we add that to Sitecore.Context.Page. And that’s it. Right…?

Multiple placeholder problem

Well this actually worked, somewhat… when we only had renderings in the content placeholder. When we started nesting placeholders and renderings within the “content” placeholder the problem started. It was kind of an obvious problem that I was counting on. If a rendering with the placeholder “subpageprecontent” would be added to the “content” placeholder on multiple sub pages and this rendering would be collected and added to the Top Page, all these “subpageprecontent” placeholder would have the same adress “/main/content/subpageprecontent”. And if something would have been added to that placeholder, it all would end up in the first occurrence of the “subpageprecontent” placeholder since there isn’t anything unique about them. So to solve this, we would have to make the placeholders unique. This got me thinking of a prototype module I’ve previously used called DynamicKeyPlaceholder which basically adds something to the key of the placeholder to make it unique. When adding a rendering to an item a new Guid is generated for that rendering and added as the attribute UniqueID in the layout definition xml. We ended up using that guid and concatenated it with the key of the placeholders in the renderings added to the “content” placeholder resulting in a unique placeholder path looking something like this “/main/content/subpageprecontent{108127af-7f86-44e6-9fce-14baf564faed}”. Below you can see a screen shot of how it looks like in Sitecore.

Now we had unique placeholders even if it was the same placeholder in the same rendering because of the UniqueID generated for the rendering.

Did it work with the DMS?

As a bonus, since we add the entire rendering to with all conditions for the rendering to the Top Page so that Sitecore thinks it belongs to that page. It actually worked with the DMS out of the box. We didn’t have to do anything else to get this working. Pretty cool huh 😀