GUI Extensions: Experimenting with Extended Areas

A little back I posted Event System code on Inheriting the Page Process setting on Structure Groups. I mentioned the missing piece: an “Inherit Page Process” like check box to have full control over the inheriting process. I decided to investigate completing this little extension and started looking at Site Edit 2012′s code where they added check boxes for “Enable Inline Editing”. This brought me to experiment with the <ext:extendedareas /> section of the GUI Extension configuration, and I thought I’d share what I learned in case anyone was wondering what these areas were for or how to use them.  NOTE that this is just my experimentation, and if you see anything stated incorrectly (or done in an inefficient way) please do comment!

The Good News

Extended Areas allows you to add your custom controls and code to existing areas in Views. Since I was looking for a way to add a “Inherit Page Process” check box right below the “Associated Page Process” dropdown in the Workflow Tab of the StructureGroup view, I thought that this would be the perfect way of adding my little extension.

How It Works

The extendedarea section contains another section where you get to target the view and the ExtendableArea control. The following comes from the configuration in the SiteEdit 2012 GUI Extension.

<ext:extension assignid="EnableSEForPublicationTarget" name="{Resources: Tridion.Web.UI.Editors.SiteEdit.Strings, SE}">
    <ext:control>~/Extensions/PublicationTarget/EnableSiteEdit.ascx</ext:control>
    <ext:pagetype></ext:pagetype>
    <ext:renderinblock>false</ext:renderinblock>
    <ext:apply>
        <ext:view name="PublicationTargetView">
            <ext:control id="PublicationTargetTab_ExtendableArea" />
        </ext:view>
    </ext:apply>
</ext:extension>

The above example looks for the Control <c:ExtendableArea id=”PublicationTargetTab_ExtendableArea” runat=”server” RenderInBlock=”false” /> on the PublicationTarget View (PublicationTarget.aspx). It will inject a custom control you define in the <ext:control>~/YourControl.ascx</ext:control> into the ExtendableArea control. Sounds good, right?

The Bad News

Unfortunately, not every view and area contains an ExtendableArea control (hopefully this changes in the future!). And unfortunately for me, the WorkflowTab on StructureGroups is one of those unlucky areas that does NOT contain an ExtendableArea control. :(

With the real extension I plan on posting once its finished up, I’ll probably be doing a lil JavaScript injection to get my customization showing up. But for the sake of science and still wanting to complete my experiment using extended areas, I committed an abomination and a no no for this little experiment…

Adding the ExtendedableArea Control Manually

To see my check box get added using an extended area, I added my own ExtendableArea Control to Tridion’s WorkflowStructureGroup.ascx (\WebUI\Editors\CME\Tabs\Workflow\).

<%@ Import Namespace="Tridion.Web.UI"%>
<%@ Control Language="C#" AutoEventWireup="true" Inherits="Tridion.Web.UI.Editors.CME.Tabs.WorkflowStructureGroup" ClassName="WorkflowStructureGroup" %>

<div id="ItemWorkflow">
    <div id="Workflow_StructureGroup">
        <fieldset>
            <div class="field">
                <label for="PageProcess">
                    <asp:Literal runat="server" Text="<%$ Resources: Tridion.Web.UI.Strings, TextPageProcess %>" />
                </label>
                <c:Dropdown id="PageProcess" runat="server" NullText="<%$ Resources: Tridion.Web.UI.Strings, None %>" disabled="true"/>
            </div>
        </fieldset>
        <c:ExtendableArea id="WorkflowStructureGroup_ExtendableArea" runat="server" RenderInBlock="false" />
    </div>
</div>

InheritFromParent.ascx

The following is just the markup for my custom checkbox.

<%@ Control Language="C#" AutoEventWireup="true" %>
<div class="form stack-elem fieldgroup">
    <div class="field">
        <label for="InheritPageProcess">Inherit Page Process</label>
        <div>
            <input type="checkbox" id="InheritPageProcess" />
        </div>
    </div>
</div>
<div class="stack-elem hr"></div>

InheritFromParent.ascx.js

Again, much thanks to the Tridion UI 2012 code for the PublicationTarget extension for me to look at as a refererence. The following JavaScript was used just for some quick testing.

Type.registerNamespace("Tahzoo.Extensions.StructureGroup");

Tahzoo.Extensions.StructureGroup.InheritFromParent = function Tahzoo$Extensions$InheritFromParent() {
    Tridion.OO.enableInterface(this, "Tahzoo.Extensions.StructureGroup.InheritFromParent");
    this.addInterface("Tridion.DisposableObject");

    this.properties.controls = {};
};

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype.initialize = function Tahzoo$Extensions$InheritFromParent$initialize() {
    var c = this.properties.controls;
    console.log($display.getItem().getTitle() + " Initialized!");
    c.inheritPageProcess = $("#InheritPageProcess");
    c.inheritPageProcess.disabled = true;

    $evt.addEventHandler(c.inheritPageProcess, "click", this.getDelegate(this._onInheritPageProcessClicked));

    var item = $display.getItem();
    if (item) {
        $evt.addEventHandler(item, "load", this.getDelegate(this._onItemLoaded));
        $evt.addEventHandler(item, "change", this.getDelegate(this._onItemChanged));
        $evt.addEventHandler(item, "undocheckoutfailed", this.getDelegate(this._onItemChanged));
        $evt.addEventHandler(item, "checkinfailed", this.getDelegate(this._onItemChanged));

        if (item.isLoaded()) {
            this._onItemLoaded();
        }
    }

};

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype.disposeInterface = Tridion.OO.nonInheritable(function Tahzoo$Extensions$InheritFromParent$disposeInterface() {
    var item = $display.getItem();
    if (item) {
        $evt.removeEventHandler(item, "load", this.getDelegate(this._onItemLoaded));
        $evt.removeEventHandler(item, "change", this.getDelegate(this._onItemChanged));
        $evt.removeEventHandler(item, "undocheckoutfailed", this.getDelegate(this._onItemChanged));
        $evt.removeEventHandler(item, "checkinfailed", this.getDelegate(this._onItemChanged));
    }
});

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype._onItemLoaded = function Tahzoo$Extensions$InheritFromParent$_onItemLoaded() {
    console.log("Item Loaded");
    var item = $display.getItem();

    this._updateCheckbox(item);
};

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype._onItemChanged = function Tahzoo$Extensions$InheritFromParent$_onItemChanged(event) {
    console.log("Item Changed");
    this._updateCheckbox($display.getItem());
};

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype._onInheritPageProcessClicked = function Tahzoo$Extensions$InheritFromParent$_onInheritPageProcessClicked(event) {
    var c = this.properties.controls;
    console.log("Clicked To: " + c.inheritPageProcess.checked.toString());
};

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype._updateCheckbox = function Tahzoo$Extensions$InheritFromParent$_updateCheckbox(item) {
    this.properties.controls.inheritPageProcess.disabled = item.isReadOnly() || item.isLoading();
};

Tridion.Controls.Deck.registerInitializeExtender("WorkflowTab", Tahzoo.Extensions.StructureGroup.InheritFromParent);

The Extension Configuration (InheritedWFSG.config)

And finally we have our extension’s actual configuration file which pieces everything together.

<?xml version="1.0"?>
<Configuration xmlns="http://www.sdltridion.com/2009/GUI/Configuration/Merge" xmlns:cfg="http://www.sdltridion.com/2009/GUI/Configuration" xmlns:ext="http://www.sdltridion.com/2009/GUI/extensions" xmlns:cmenu="http://www.sdltridion.com/2009/GUI/extensions/ContextMenu">
    <resources cache="true">
        <cfg:filters/>
        <cfg:groups>
            <cfg:group name="Tridion.Extensions.GUI.InheritedWFSG.StructureGroup">
                <cfg:fileset>
                    <cfg:file type="script">/InheritFromParent.ascx.js</cfg:file>
                </cfg:fileset>
            </cfg:group>
        </cfg:groups>
    </resources>
    <definitionfiles/>
    <extensions>
        <ext:editorextensions>
        <ext:editorextension target="CME">
        <ext:editurls />
        <ext:listdefinitions />
        <ext:itemicons />
        <ext:taskbars />
        <ext:commands />
        <ext:commandextensions />
        <ext:contextmenus />
        <ext:lists />
        <ext:tabpages />
        <ext:toolbars />
        <ext:ribbontoolbars />
        <ext:extendedareas>
            <ext:add>
                <ext:extension assignid="InheritPageProcessFromParent" name="InheritPageProcessFromParent">
                    <ext:control>~/InheritFromParent.ascx</ext:control>
                    <ext:pagetype></ext:pagetype>
                    <ext:renderinblock>false</ext:renderinblock>
                    <ext:dependencies>
                        <cfg:dependency>Tridion.Extensions.GUI.InheritedWFSG.StructureGroup</cfg:dependency>
                    </ext:dependencies>
                    <ext:apply>
                        <ext:view name="StructureGroupView">
                            <ext:control id="WorkflowStructureGroup_ExtendableArea" />
                        </ext:view>
                    </ext:apply>

                </ext:extension>
            </ext:add>
        </ext:extendedareas>
        </ext:editorextension>
        </ext:editorextensions>
        <ext:dataextenders />
    </extensions>
    <commands/>
    <contextmenus/>
    <localization/>
    <settings>
        <defaultpage />
        <navigatorurl />
        <editurls/>
        <listdefinitions/>
        <itemicons/>
        <theme>
            <path/>
        </theme>
        <customconfiguration/>
    </settings>
</Configuration>

And there you have it! If you need help understanding how to deploy the above, check out this tutorial here.

Razor Mediator Version 1.3 Released

Its taken much longer than I had originally anticipated, but the next version of the Razor Mediator has just been submitted to the SDL team. As usual, you can find the installer for this package here and the updated documentation for this version here. So what exactly is new in this package?

Working Where Used Functionality

Have a lot of Razor Imports and sad because its hard to track down where exactly they are used? Or have the blues from Content Porting since you have to make sure you CP your import files first before the rest of your Razor Templates? The great news is that with Version 1.3, including razor imports either through importRazor(“path”) statements or via the razor.mediator configuration setting will now finally be set as a reference so that these imports show up as Where Used! Just in case for some odd reason you absolutely do not like this features, there is also a new element in the config called “importSettings”, which allows you to turn on/off this feature. You can even turn on/off imports from importRazor statements and imports from the configuration separately.

<importSettings includeConfigWhereUsed="true" includeImportWhereUsed="true" replaceRelativePaths="false" />

The Where Used won’t kick in automatically once you upgrade to this version… you’ll have to Save your Razor Templates for the references to take place.

Relative WebDav URLs Supported!

Thanks’s to Will Price for suggesting this one! Import statements in version 1.2 only accepted TcmUri’s and full lengthy WebDav URL’s. As of Version 1.3, you can now supply relative WebDav URL paths! These paths to the imports are relative to the Razor Template that’s calling them.

@importRazor("Same Level Helper Functions.cshtml")
@importRazor("./Also Same Level Helper Functions.cshtml")
@importRazor("My Helper Functions/Global Helper Functions.cshtml")
@importRazor("My Helper Functions/Nav Functions/Main Navigation Functions.cshtml")
@importRazor("../Previous Level Functions.cshtml")
@importRazor("../../Even More Previous Level Functions.cshtml")

You probably noticed that “replaceRelativePaths” attribute in the previous importSettings config example? When set to “false” (out of the box setting), your relative import paths will stay relative. But if for some reason you would like these to automatically be transformed into the full WebDav URL, set this to true and upon saving your templates, the paths will be turned into the full paths.

Site Edit Enabled!

Razor Mediator 1.3 now includes a property named “IsSiteEditEnabled”. As the name may suggest, this property will check the Publication Target you are publishing to to see whether or not SiteEdit (inline editing) is enabled. This is useful for when you want to render specific HTML (like regions!) only for your editable staging site. This only works for Tridion UI 2012, and not previous versions of SiteEdit.

@if (IsSiteEditEnabled) {
    <strong>This page is SiteEdit Enabled!</strong>
}

Also added by default to version 1.3 is an enhancement request that came in for the RenderComponentField methods. Prior to Version 1.3, these methods would throw an error if the field was empty. Now these methods will spit out an empty tcdl tag. You can also have it spit out an empty string by using the new last argument to this field. The following is assuming that “Fields.FieldName” is empty.

@RenderComponentField("Fields.FieldName", 0)
@RenderComponentField("Fields.FieldName", 0, false)

In the first example, an empty tcdl tag like <tcdl:ComponentField name=”Fields.FieldName” index=”0″></tcdl:ComponentField> will get output so that you still have a section on your staging page for adding text to this area. The second example will just output an empty string and no tcdl tag.

Template Models

Version 1.3 now includes Template Models for the quick and easy access to template metadata that you have grown use to! @ComponentTemplate (accessible only from Component Templates), @PageTemplate and @Page.PageTemplate (accessible only when there’s a page that’s accessible), and @RazorTemplate (the Razor Template Building Block itself).

@if (IsComponentTemplate) {
    <span>@ComponentTemplate.Metadata.FieldName</span>
}

@if (Page != null) {
    <text>The following can be accessed from both Page Templates and Component Templates (only when a Page object is available though)</text>
    <span>@PageTemplate.Metadata.FieldName or @Page.PageTemplate.Metadata.FieldName</span>
}

@if (IsPageTemplate) {
    <text>While this example will only work when its a Page Template.</text>
    <span>@PageTemplate.Metadata.FieldName or @Page.PageTemplate.Metadata.FieldName</span>
}

<span>@RazorTemplate.Metadata.FieldName</span>

Other Changes and Fixes

For a full list of updates and fixes that were made in this version, please check out the Change Log.

Thanks To You

And I just wanted to personally thank everyone who has supported this project through kind comments, reporting issues, bugs and suggestions, and for testing the many features. This project definitely would not have come this far without you guys. :)

Inherited Page Workflow Process Settings On Structure Groups

If you’ve worked with Tridion Workflows, you know that the process of assigning Component workflows differs greatly from the process of assigning Page workflows.  With components, you just add the Component Process to the Schema (and of course make sure you check the “Enable Workflow Process Associations in Shared Schemas and Structure Groups” from your Content Publication’s properties’s Workflow tab).  However, Page Processes are attached to Structure Groups.  And unfortunately Structure Groups do not inherit this setting, so if you wanted every location where pages get created to be affected, you’ll have to add this setting to each and every Structure Group.

Being inspired by Nuno’s post on inheriting metadata schemas on folders, I thought I’d write up a quick Event System example of how to mock inheritance of Page Processes on Structure Groups.

First lets write an event handler that will set the Associated Page Process of new Structure Groups to that of their parent Structure Group.

public WorkflowStructureGroupHandler()
{
    EventSystem.Subscribe<StructureGroup, LoadEventArgs>(SetNewStructureGroupProcessDefinition, EventPhases.Processed);
}

private void SetNewStructureGroupProcessDefinition(StructureGroup sg, LoadEventArgs args, EventPhases phase)
{
    if (sg.Id.IsUriNull)
    {
        StructureGroup parentSG = sg.OrganizationalItem as StructureGroup;
        sg.PageProcess = parentSG.PageProcess;
    }
}

Fairly easy right? This takes care of the automatic setting of Page Processes for new Structure Groups, but what about existing Structure Groups? Normally by the time we start attaching process definitions, there could be a ton of existing Structure Groups already. What if we wanted to modify a parent Structure Group, and have all the children automatically updated? Lets add a Save event to the Structure Groups.

public WorkflowStructureGroupHandler()
{
    EventSystem.Subscribe<StructureGroup, LoadEventArgs>(SetNewStructureGroupProcessDefinition, EventPhases.Processed);
    EventSystem.Subscribe<StructureGroup, SaveEventArgs>(SetInheritedStructureGroupProcessDefinitions, EventPhases.TransactionCommitted);
}

private void SetInheritedStructureGroupProcessDefinitions(StructureGroup sg, SaveEventArgs args, EventPhases phase)
{
    if (args.EventStack.Count() > 1)
    {
        return;
    }

    ProcessDefinition pageProcess = sg.PageProcess;

    OrganizationalItemItemsFilter filter = new OrganizationalItemItemsFilter(sg.Session);
    filter.ItemTypes = new List<ItemType> { ItemType.StructureGroup };

    SetChildrenStructureGroups(sg, filter, pageProcess);
}

private void SetChildrenStructureGroups(StructureGroup sg, OrganizationalItemItemsFilter sgFilter, ProcessDefinition pageProcess)
{
    IEnumerable<RepositoryLocalObject> children = sg.GetItems(sgFilter);

    foreach (StructureGroup structureGroup in children)
    {
        if (structureGroup.PageProcess != pageProcess)
        {
            structureGroup.PageProcess = pageProcess;
            structureGroup.Save();
        }

        SetChildrenStructureGroups(structureGroup, sgFilter, pageProcess);
    }
}

And there you have it, modifying a parent Structure Group will not automatically set its children to have the same Associated Page Process, and creating a new SG will now automatically set its Page Process to that of its parent. The entire code will now look like:

using System.Collections.Generic;
using System.Linq;
using Tridion.ContentManager;
using Tridion.ContentManager.CommunicationManagement;
using Tridion.ContentManager.ContentManagement;
using Tridion.ContentManager.Extensibility;
using Tridion.ContentManager.Extensibility.Events;
using Tridion.ContentManager.Workflow;

namespace Tahzoo.EventSystem.Samples
{
    [TcmExtension("WorkflowStructureGroupHandlerExtension")]
    public class WorkflowStructureGroupHandler : TcmExtension
    {
        public WorkflowStructureGroupHandler()
        {
            EventSystem.Subscribe<StructureGroup, LoadEventArgs>(SetNewStructureGroupProcessDefinition, EventPhases.Processed);
            EventSystem.Subscribe<StructureGroup, SaveEventArgs>(SetInheritedStructureGroupProcessDefinitions, EventPhases.TransactionCommitted);
        }

        private void SetNewStructureGroupProcessDefinition(StructureGroup sg, LoadEventArgs args, EventPhases phase)
        {
            if (sg.Id.IsUriNull)
            {
                StructureGroup parentSG = sg.OrganizationalItem as StructureGroup;
                sg.PageProcess = parentSG.PageProcess;
            }
        }

        private void SetInheritedStructureGroupProcessDefinitions(StructureGroup sg, SaveEventArgs args, EventPhases phase)
        {
            if (args.EventStack.Count() > 1)
            {
                return;
            }

            ProcessDefinition pageProcess = sg.PageProcess;

            OrganizationalItemItemsFilter filter = new OrganizationalItemItemsFilter(sg.Session);
            filter.ItemTypes = new List<ItemType> { ItemType.StructureGroup };

            SetChildrenStructureGroups(sg, filter, pageProcess);
        }

        private void SetChildrenStructureGroups(StructureGroup sg, OrganizationalItemItemsFilter sgFilter, ProcessDefinition pageProcess)
        {
            IEnumerable<RepositoryLocalObject> children = sg.GetItems(sgFilter);

            foreach (StructureGroup structureGroup in children)
            {
                if (structureGroup.PageProcess != pageProcess)
                {
                    structureGroup.PageProcess = pageProcess;
                    structureGroup.Save();
                }

                SetChildrenStructureGroups(structureGroup, sgFilter, pageProcess);
            }
        }
    }
}

The solution above is not perfect and there’s still some work that would need to go into this before I’d put it into production (unless of course your project is a one process fits all and everything needs to be affected the same). For example, you’ve configured your Structure Groups that have a combination of different processes and even some without. Edit the root Structure Group, and say good-bye to all of your work! An “Inherit from Parent” eXtension would be perfect here, so you can then tell Structure Groups to not inherit (and thus not change when one of the parent Structure Groups gets saved!) Unfortunately this story is coming to a close, and the GUI eXtension’s story is a tale for another day.

Razor Helpers and Functions

Now that the latest version of the Razor Mediator for Tridion (v 1.2) allows you to import, I thought perhaps it would be helpful if I go over the Razor syntax for creating Razor Helper functions, or just adding normal functions in general.  Using these in your Razor Templates greatly enhances the functionality that you can perform in your templates.  On my latest project at a client, we developed some pretty advanced templates, and I found myself thinking (and shuddering) of how it would have to have been done if done using Dreamweaver templating.

Normal Functions

You can declare functions in your Razor code and call it just like you were calling any of the built in functions using @functions. Remember, you can have as many @functions sections as you want in your templates, you are not just limited to one.

@functions {
    public string HelloWorld(string name) {
        return "Hello " + name;
    }
}

The above defines a function called “HelloWorld” that you can start using in your template code (or callable in other functions).

<strong>@HelloWorld("John")</strong>

Helper Functions

Helper Functions are also callable via your template code, however Helper Functions allows for Razor syntax and HTML markup from within the function itself.

@helper HelloWorld(string name) {
    <div>Hello <em>@name</em>!</div>
}

The Helper Function would then work exactly the same as the non-Helper Function created above.

<strong>@HelloWorld("John")</strong>

You can mix and match your functions as well.  Following is just a couple more quick examples.

@functions {
    public string GetExtraAttributes(Models.ComponentModel component) {
        string attributes = String.Empty;
        if (component.Metadata.KeyValuePairs != null) {
            foreach (var values in component.Metadata.KeyValuePairs) {
                attributes += String.Format("{0}="{1}"", values.Key, values.Value);
            }
        }
        return attributes;
    }
}

@helper RenderComponentLink(Models.ComponentModel component) {
    <a href="@component.ID" @GetExtraAttributes(component)>@component.Title</a>
}

@helper WrapInParagraphTags(string input) {
    if (input != null && !input.StartsWith("<p")) {
        <p>@input</p>
    } else {
        @input
    }
}

Razor Mediator 4 Tridion Version 1.2 Released

Hi readers! My apologies for the lack of blog writing over the past month, but things have been extremely busy on the development front. After much feedback and help with testing from colleagues, I am pleased to announce the release of the Razor Mediator Version 1.2. I put it up for submission to SDL Tridion World yesterday, but if you can’t wait, you can also grab it at its Google Code Project Site. There are a lot of fixes, updates, and new features added to this version, so you can probably expect some more posts coming soon with even more examples and tutorials than what is in the new updated documentation. So, what are some of these new updates and features you ask?

Ability to Import!

That’s right, you heard correctly. You can now import other Razor Template Building Blocks or even external Razor Templates stored in text files on the CMS Server to your templates. You can do this globally via the configuration to allow you to import templates to all of your Razor Templates (or even to all of your Razor Templates in one publication), or at the Template level via the @importRazor(“PathToYourTemplate”) command.

Updated GetComponentPresentationsByTemplate and GetComponentPresentationsBySchema

These methods have been updated to accept multiple parameters. So, that means you can now do something like:

@foreach (var cp in GetComponentPresentationsByTemplate("Template One Name", "Template Two Name", "Template Three Name")) {

}

RenderComponentPresentations and RenderComponentPresentationsByTemplate

Two new utility methods have been added to the base template that allows you to just render templates quickly. RenderComponentPresentations() will render all ComponentPresentations on the Page, while RenderComponentPresentationsByTemplate(param string[] templateNames) will only render the ComponentPresentations given the passed template names.

<div id="allTemplates">
    @RenderComponentPresentations()
</div>
<div id="someTemplates">
    @RenderComponentPresentationsByTemplate("Template One Name", "Template Two Name")
</div>

Better Compile Error Message

When you receive a compile error upon saving your Razor Template, you will now be shown a more informative message that also displays the line of code in question. Remember though, that the line of code that is shown is the generated C# code, and not your actual Razor code. This means that “<span>@blasadsfdsaf</span>” would show the line as being “Write(blasadsfdsaf);”. Also, the error displayed to the user in Tridion will no longer include Warning messages.

Index, IsFirst, and IsLast Properties

To further help you write cleaner code, the properties “Index”, “IsFirst” and “IsLast” has been added to the ComponentPresentationModel, ComponentModel, KeywordModel, and DynamicItemFields classes. For ComponentPresentationModels, these properties are automatically set when accessing the ComponentPresentations via the ComponentPresentations property of the base template, or by either of the GetComponentPresentationsByTemplate() or GetComponentPresentationsBySchema() methods.

@foreach (var cp in ComponentPresentations) {
    @if (cp.IsFirst) {
        <div>@cp.Component.Title is the first item.
    } else if (cp.IsLast) {
        <div>@cp.Component.Title is the last item.
    }
}

For ComponentModel and KeywordModel, these properties are automatically set when accessing them via the DynamicItemFields (that is, when they are set as a multi-valued field of course).

@foreach (var kw in Component.Fields.SomeKeywords) {
    <div class="@(kw.Index % 2 == 0 ? "alt1" : "alt2")">@kw.Title</div>
}
@foreach (var comp in Fields.SomeComponents) {
    @if (comp.IsLast) {
        <span>We only wanted the last ComponentLink item!</span>
    }
}

These properties are automatically set for DynamicItemFields when it is accessed via the DynamicItemFields as a multi-valued EmbeddedSchemaField.

@foreach (var embeddedFields in Fields.SomeEmbeddedFields) {
    <div class="@(embeddedFields.IsLast ? "last" : String.Empty)">@embeddedFields.Address (@embeddedFields.ZipCode)</div>
}

Quick Access to Debug Writing

Pre version 1.2, the Razor Mediator documentation said that you could write logging statements like @Log.Debug(“Your Message”). This was actually incorrect and would have thrown an error… you would of had to write them as @{ Log.Debug(“Your Message”); }. As of version 1.2, you can now do @Debug(“Your Debug”), @Info(“Your Info”), @Warning(“Your Warning”), and @Error(“Your Error”).

Of Fixes and More

There are a couple other minor goodies that have been added, as well as some critical fixes and updates that include caching and thread safety (for publishing) fixes. If you are already using a previous version of the Razor Mediator, I would definitely recommend updating to version 1.2. You can view the Change Log on the Google Code project for a full list of all the updates and fixes made in this release.

Version 1.3?

Yes, a Version 1.3 is now in the works, with even more helpers/utilities to make your templating life a bit less difficult, and some more features to help empower your abilities. And of course any more bugs or issues that you report to me. Much thanks to you for your feedback!

Back From Bootcamp

I’ve just returned from a two day bootcamp hosted by Nuno regarding the new upcoming tool formerly known as SiteEdit. That’s right, I’m talking about SDL Tridion UI 2012 (which will probably still be referenced as “SiteEdit 2012″ for years to come by all of us who have used it up until this point), and if you haven’t heard any news at all about it, you can check out Nuno’s post on it here. Nuno did an excellent job walking us through the new features and dynamics of this completely revamped tool, and I would like to share some of these new features with you!

Not a proxy anymore!
The days of SiteEdit running as a proxy site is no more. Tridion UI runs directly from your Staging site!

Live editing… from the CME?
Clicking the Tridion button (previously the button labeled “SiteEdit”) loads the Staging page into an iframe… within the CME! Remember though, the Tridion UI Dashboard is not equal to the Tridion CMS Dashbard, and has options and settings directly related to SiteEdit only. Also, because of how Tridion UI works, this means that you now have the option to build GUI extensions for Tridion UI!

Built in options and settings
No more will we have to find workarounds to configuring our SiteEdit settings. Where once we’d have to add every setting into a TBB or create Page Types in a special location within Tridion, we now have built in options! You can enable/disable live editing directly from the Publication Target. You can enable/disable fields for inline editing directly from the Schema. You can change all the normal settings (like borders, color, etc) right from a Settings section within the Tridion UI dashboard. You can mark items as being Page Types right from the Page, and can set the icons displayed for templates in Tridion UI right from the Template itself. You can even configure Content Types right from the Publication properties!

You just mentioned… Content Types?
That’s right! While the concept of Page Types existed, it is now possible to define Content Types as well! Think of Content Types as the mixture of a Component and a Component Template, that you can set rules to.

Dynamic Editing
The new Tridion UI 2012 allows you to use a oData webservice and a session preview. What does this mean for you content authors? This means no more waiting for the publishing queue to see your changes! And for those of you who don’t wish to use this new feature, you absolutely don’t have to! Tridion UI will behave similar to the way it did before (via publishing).

Regions!!!
As the title suggests… Regions!!!! Not to be confused with a Template Repeat area of your DWT. A Region in Tridion UI is an area that you can just drag and drop content to. For example, if you have a right column, you can define a Region in that area and give it rules that only Right Column Templates can go there, as well as the minimum number of items and maximum number.

Of context menus…
You can even enable/disable the Tridion UI context menu. Very useful when you need to do a Right Click -> Inspect Element!

And so much more…
Tridion has done an awesome job with revamping SiteEdit. Two days was definitely not enough time to get to play with it, and I personally can’t wait for its release to start playing with it again (and perhaps even blogging a tutorial on creating an eXtension for it!).

Tridion PublishEngine – Of Transactions and Publish Information

Today I thought I would give some examples of querying publish transactions (publishing queue) as well as getting publish information from specific items, like seeing which Publication Targets a page has been published to and when, or just seeing if an item has been published in general. This post is inspired by a recent question asked in the forums, but I have noticed it come up from time to time. Luckily this task is easy using the TOM.NET API and the static PublishEngine class.

IsPublished

One task you might need to attempt in your Tridion development day to day activities is to check to see if a given item has been published, or published to a particular Publication Target.  PublishEngine contains the following overloaded methods.

bool IsPublished(IdentifiableObject item)
bool IsPublished(IdentifiableObject item, PublicationTarget publicationTarget)
bool IsPublished(IdentifiableObject item, PublicationTarget publicationTarget, bool isPublishedInContext)

The first IsPublished method is useful if you just want to see if an item is published, regardless of which Publication Target it has been published to.

if (PublishEngine.IsPublished(page))
{
    // This page has been published. Do cool stuff here.
}

The second IsPublished method will return true only if the item has been published to the Publication Target passed in the 2nd argument. If the 2nd argument is null, then it’ll act just like the first method and return true if the item has been published to any Publication Target.

if (PublishEngine.IsPublished(page, pubTarget))
{
    // This page has been published to a specific publication target. Do cool stuff here.
}

The third method allows for even finer control. I haven’t personally played with this one as of yet, but the documentation for the isPublishedInContext argument states “Indicates if state should be returned regardless of the context Publication. true only check if item is published in the context Publication; otherwise, false.” I’m assuming this means that, if this argument is set to true, the method will only pass if the item is published to a particular Publication Target, and only if the item is published in it’s own context Publication.

GetPublishInfo

What if you need to get more information about an item? Like, what if you not only wanted to see what Publication Targets it was published to, but at what time too? That’s where the following method comes in hand.

foreach (PublishInfo info in PublishEngine.GetPublishInfo(page))
{
    Console.WriteLine("Published To: " + info.PublicationTarget.Title);
    Console.WriteLine("Published At: " + info.PublishedAt); // The time the page was published
    Console.WriteLine("Published By: " + info.PublishedBy.Title); // The user who published
    Console.WriteLine("Rendered With: " + info.RenderedWith.Title); // The title of the template used
}

The above will loop through each Publication Target that the item has been published to and give you some useful information about the publishing.

GetPublishTransactions

And what if you actually need to check the publishing queue to see if an item has recently been published in the past hour? The PublishEngine allows you to also query the publish transactions.

XmlElement GetListPublishTransactions(PublishTransactionsFilter filter)
IEnumerable<PublishTransaction> GetPublishTransactions(PublishTransactionsFilter filter)

Note that the above methods will throw an AccessDeniedException if the user is not a System Administrator or have PublishManagement rights in any publication. That means if your code relies on always needing to be able to check the transactions, regardless of rights, you’ll need to impersonate.

Session session = new Session("DOMAIN\username"); // Impersonate System Admin or at minimum user with PublishManagement rights.
 
PublishTransactionsFilter filter = new PublishTransactionsFilter(session);
filter.StartDate = DateTime.Now.AddHours(-1); // Add some criteria for when the publishing was started.
filter.PublishTransactionState = PublishTransactionState.Success;

IEnumerable<PublishTransaction> transactions = PublishEngine.GetPublishTransactions(filter);

foreach (PublishTransaction transaction in transactions)
{
    // Check cool stuff with the transaction, like the transaction.Items property.
}

The above will loop through all the successful transactions that has happened in the past hour. Notice the PublishTransactionState property… it allows you to only filter based on one state. But what if you need to grab anything that’s not Success or Failed perhaps, or any other combination of states? You’ll have to grab the items and filter programatically.

PublishTransactionsFilter filter = new PublishTransactionsFilter(session);
filter.StateDate = DateTime.Now.AddHours(-1);

IEnumerable<PublishTransaction> transactions = PublishEngine.GetPublishTransactions(filter)
    .Where(t => t.State != PublishTransactionState.Success && t.State != PublishTransactionState.Failed);

foreach (PublishTransaction transaction in transactions)
{
    // Do cool stuff with transactions that are not Success or Failed
}

And with that I leave. Happy developing everyone!

Razor Mediator Version 1.1 Released

I am happy to announce that the Razor Mediator version 1.1 has been submitted to SDL to be updated on SDL Tridion World. In the meantime, you can grab the v1.1 installer at its Google Code project site. So what exactly has been updated since version 1.0?

KeywordModel

Just like the other Tridion items that have been wrapped in Model classes, the Keyword class has finally been wrapped as well.  This means you can easily access a Keyword’s metadata just like the other objects as well.

@Fields.SomeKeywordField.Metadata.SomeMetaField

All fields that would normally return a KeywordField will now return a KeywordModel (or List<KeywordModel> for multi-valued fields).  The @Models.GetKeyword(string) method also returns a KeywordModel.

GetComponentPresentationsBySchema(string schemaName)

A new utility method has been added to the base template class to allow you to easily grab all Component Presentations filtered by the component’s schema title.

@foreach (var cp in GetComponentPresentationsBySchema("Article Schema")) {
    <text>@cp.RenderComponentPresentation()</text>
}

DynamicPackage

This feature was listed in version 1.0 but wasn’t actually used.  All calls to the @Package property was really just returning the Tridion Package instance.  So, what exactly can you do with DynamicPackage?  You can easily grab package items and parameter values in your templates using dot notation.  For backwards compatibility, GetByName(string name), GetByType(ContentType type), and GetValue(string name).  The following would all work assuming there was either a package item of a string type named “ItemName” or a parameter field with xml name of “ItemName”.

@Package.ItemName
@Package.GetByName("ItemName").GetAsString()
@Package.GetValue("ItemName")

And some fixes…

Besides the several new features, some fixes were also added.  For a complete list of fixes, feel free to check out the Change Log page.  If you come across more issues, please let me know so that I can get them fixed ASAP. And if you have some cool ideas for possible features that you want to see in the Razor Mediator, shoot them over as well.

What’s Next?

I’m planning on putting an easy way to extend your Razor templates using reusable helper methods and functions.  So look forward to version 1.2 coming out sometime in the hopefully near future, as well as another blog post to show you examples of creating helper methods in your current templates!

Tridion Dreamweaver Template Delimiter Escaping

This was a question that popped up in the forums recently, so I thought I would share on how to escape Dreamweaver delimiters. That is, actually outputting “@@someValue@@” or “${someValue}” in your DWT’s without having them parsed.

The following samples will all escape the delimiters properly.

@@"@" + "@" + "test" + "@" + "@"@@
@@"$" + "{" + "test" + "}"@@
@@"@" + "@test@" + "@"@@
@@"$" + "{test}"@@

And will output:

@@test@@
${test}
@@test@@
${test}

As a side note, trying to escape the delimiters using the “${ }” delimiter will not work the same. The closest I got to actually displaying the correct output was by using ${"$" + "{" +"test}}. Yep, the odd number of quotes was needed… if you add the last quote (as it looks like it should need), it’ll actually output the last quote. Odd, no?

I hope that helps anyone who’s in need to do this sort of escaping from their DWT’s!

Tridion Event System: Automated Page Creation After Creating A Component

It’s been a great week that I had visiting upstate New York and parading my son around for all the family to see. With that said, I think its due time for another post. Today I will be following up with yet another Tridion Event System tutorial, again using the power of Tridion’s API to add a bit more automation to your environment.  We’ll be automatically creating Pages for our newly created Article components.  As Alvin mentioned in my other post, when you are adding functionality that changes the behavior of Tridion (such as moving your content author’s content for them or creating pages for them), make sure that they are fully aware of these customizations, else they will be in for a world of confusion.  The reverse is also nice… if there’s a new member that comes in, let them know that these are customizations and not out of the box features.  I’ve been on projects where one of the authors had prior Tridion experience and swore a special feature existed, yet not knowing that the feature was custom developed for their project.  If you haven’t done so already, you should also first read the other Event System tutorial.  After today’s tutorial, you should be a little more comfortable with Tridion 2011′s Event System and working with the TOM.NET API to create new Pages, create Structure Groups, and perform a Where Using search.

Although we could easily combine this new functionality to the code we created before, we’ll go ahead and set up a new class with the following using statements and unique ID for the extension.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Tridion.ContentManager;
using Tridion.ContentManager.CommunicationManagement;
using Tridion.ContentManager.ContentManagement;
using Tridion.ContentManager.ContentManagement.Fields;
using Tridion.ContentManager.Extensibility;
using Tridion.ContentManager.Extensibility.Events;

namespace Tridion.Samples
{
    [TcmExtension("AutomatedPageEventHandlerExtension")]
    public class AutomatedPageEventHandler : TcmExtension
    {

    }
}

We will only want to create new Pages for our Articles that have not yet been added to a page. For this, we’ll perform a Where Using check to see if our component is being used on a page or not.

private bool DoesNotHavePage(Component component)
{
    UsingItemsFilter filter = new UsingItemsFilter(component.Session);
    filter.ItemTypes = new List<ItemType> { ItemType.Page };
    filter.IncludedVersions = VersionCondition.OnlyLatestVersions;

    return component.GetUsingItems(filter).Count() == 0;
}

We’ll want to use the Component’s title to generate the title for the page. But what about the filename? Lets go ahead and add a quick slugging algorithm to just use the title and make it filename friendly (thanks to John Roland at http://predicatet.blogspot.com/2009/04/improved-c-slug-generator-or-how-to.html for posting this script).

private string CreateSlugTitle(string title)
{
    byte[] bytes = System.Text.Encoding.GetEncoding("Cyrillic").GetBytes(title);
    title = System.Text.Encoding.ASCII.GetString(bytes).ToLower();

    title = Regex.Replace(title, @"[^a-z0-9\s-]", ""); // invalid chars
    title = Regex.Replace(title, @"\s+", " ")
        .Trim() // convert multiple spaces into one space
        .Substring(0, title.Length <= 45 ? title.Length : 45)
        .Trim(); // cut and trim it
    title = Regex.Replace(title, @"\s", "-"); // hyphens   

    return title;
}

Just like we auto-organized our Components in the previous Event System tutorial, lets write a couple of methods that will do the same for our generated pages. Remember, Titles and Directories for StructureGroup’s are required fields.

private StructureGroup GetStructureGroup(string title, string directory, StructureGroup parentSG, OrganizationalItemItemsFilter filter)
{
    StructureGroup sg = parentSG
        .GetItems(filter)
        .Where(f => f.Title.Equals(title, StringComparison.InvariantCultureIgnoreCase))
        .FirstOrDefault() as StructureGroup;

    if (sg == null)
    {
        sg = CreateStructureGroup(title, directory, parentSG);
    }

    return sg;
}

private StructureGroup CreateStructureGroup(string title, string directory, StructureGroup parentSG)
{
    StructureGroup newSG = parentSG.GetNewObject<StructureGroup>();
    newSG.Title = title;
    newSG.Directory = directory;
    newSG.Save();

    return newSG;
}

Next is the bread and butter where we put it all together. We’ll use a WebDav URL to open up items that we need from Tridion (and as mentioned before, I would recommend an extra step and keeping these in some configuration file). Pay attention to how we create the Page using the TOM.NET API as well as add ComponentPresentations to it. One gotcha that you might run into is (real easy to forget), if you had tried adding the Component that is passed via the Event Handler argument, you’ll get an error. This is because the Page is expecting the Component to exist in the same Publication as it. That’s why we open the component in the Page’s publication, and then add that newly opened component to the Page.

private void CreatePage(Component component)
{
    Session session = component.Session;

    ItemFields metadata = new ItemFields(component.Metadata, component.MetadataSchema);
    DateTime articleDate = ((DateField)metadata["ArticleDate"]).Value;

    string year = articleDate.Year.ToString();
    string month = articleDate.ToString("MM MMMM");
    string monthDirectory = articleDate.ToString("MM");

    StructureGroup baseStructureGroup = session.GetObject("/webdav/040 Website Master/Root/000 Articles") as StructureGroup;
    if (baseStructureGroup == null)
        throw new Exception("CreatePage - Unable to get baseStructureGroup");

    ComponentTemplate componentTemplate = session.GetObject("/webdav/040 Website Master/Building Blocks/System/Templates/Component Templates/Article CT.tctcmp") as ComponentTemplate;
    if (componentTemplate == null)
        throw new Exception("CreatePage - Unable to get componentTemplate");

    PageTemplate pageTemplate = session.GetObject("/webdav/040 Website Master/Building Blocks/System/Templates/Page Templates/Article PT.tptcmp") as PageTemplate;
    if (pageTemplate == null)
        throw new Exception("CreatePage - Unable to get pageTemplate");

    OrganizationalItemItemsFilter filter = new OrganizationalItemItemsFilter(session);
    filter.ItemTypes = new List<ItemType> { ItemType.StructureGroup };

    StructureGroup yearSG = GetStructureGroup(year, year, baseStructureGroup, filter);
    StructureGroup monthSG = GetStructureGroup(month, monthDirectory, yearSG, filter);

    Page newPage = monthSG.GetNewObject<Page>();
    newPage.PageTemplate = pageTemplate;
    newPage.Title = component.Title;
    newPage.FileName = CreateSlugTitle(component.Title);

    Component localComponent = session.GetObject(new TcmUri(component.Id.ItemId, ItemType.Component, baseStructureGroup.Id.PublicationId)) as Component;
    ComponentPresentation cp = new ComponentPresentation(localComponent, componentTemplate);
    newPage.ComponentPresentations.Add(cp);

    newPage.Save(true);
}

Finally we add our Constructor where we will subscribe our event and the handler method itself. In the handler method, we’ll make sure that only our Articles will trigger the Page Generation, and only Articles that don’t already exist on a page.

public AutomatedPageEventHandler()
{
    EventSystem.Subscribe<Component, CheckInEventArgs>(OnComponentCheckedInPost, EventPhases.TransactionCommitted);
}

private void OnComponentCheckedInPost(Component component, CheckInEventArgs args, EventPhases phase)
{
    if (component.Schema.Title.Equals("Article") && DoesNotHavePage(component))
    {
        CreatePage(component);
    }
}

Your class should now look like the following.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Tridion.ContentManager;
using Tridion.ContentManager.CommunicationManagement;
using Tridion.ContentManager.ContentManagement;
using Tridion.ContentManager.ContentManagement.Fields;
using Tridion.ContentManager.Extensibility;
using Tridion.ContentManager.Extensibility.Events;

namespace Tridion.Samples
{
    /// <summary>
    /// Component Event Handler
    /// </summary>
    [TcmExtension("AutomatedPageEventHandlerExtension")]
    public class AutomatedPageEventHandler : TcmExtension
    {
        /// <summary>
        /// Constructor - Subscribe the component events to handle.
        /// </summary>
    public AutomatedPageEventHandler()
    {
        EventSystem.Subscribe<Component, CheckInEventArgs>(OnComponentCheckedInPost, EventPhases.TransactionCommitted);
    }

        /// <summary>
        /// On Component Checked In Transaction Committed events.
        /// </summary>
        /// <param name="component">The component checked in.</param>
        /// <param name="args">The CheckInEventArgs instance.</param>
        /// <param name="phase">The EventPhase enum.</param>
        private void OnComponentCheckedInPost(Component component, CheckInEventArgs args, EventPhases phase)
        {
            if (component.Schema.Title.Equals("Article") && DoesNotHavePage(component))
            {
                CreatePage(component);
            }
        }

        /// <summary>
        /// Creates a page and adds the component to it.
        /// </summary>
        /// <param name="component">The component to create the page for.</param>
        private void CreatePage(Component component)
        {
            Session session = component.Session;

            ItemFields metadata = new ItemFields(component.Metadata, component.MetadataSchema);
            DateTime articleDate = ((DateField)metadata["ArticleDate"]).Value;

            string year = articleDate.Year.ToString();
            string month = articleDate.ToString("MM MMMM");
            string monthDirectory = articleDate.ToString("MM");

            StructureGroup baseStructureGroup = session.GetObject("/webdav/040 Website Master/Root/000 Articles") as StructureGroup;
            if (baseStructureGroup == null)
                throw new Exception("CreatePage - Unable to get baseStructureGroup");

            ComponentTemplate componentTemplate = session.GetObject("/webdav/040 Website Master/Building Blocks/System/Templates/Component Templates/Article CT.tctcmp") as ComponentTemplate;
            if (componentTemplate == null)
                throw new Exception("CreatePage - Unable to get componentTemplate");

            PageTemplate pageTemplate = session.GetObject("/webdav/040 Website Master/Building Blocks/System/Templates/Page Templates/Article PT.tptcmp") as PageTemplate;
            if (pageTemplate == null)
                throw new Exception("CreatePage - Unable to get pageTemplate");

            OrganizationalItemItemsFilter filter = new OrganizationalItemItemsFilter(session);
            filter.ItemTypes = new List<ItemType> { ItemType.StructureGroup };

            StructureGroup yearSG = GetStructureGroup(year, year, baseStructureGroup, filter);
            StructureGroup monthSG = GetStructureGroup(month, monthDirectory, yearSG, filter);

            Page newPage = monthSG.GetNewObject<Page>();
            newPage.PageTemplate = pageTemplate;
            newPage.Title = component.Title;
            newPage.FileName = CreateSlugTitle(component.Title);

            Component localComponent = session.GetObject(new TcmUri(component.Id.ItemId, ItemType.Component, baseStructureGroup.Id.PublicationId)) as Component;
            ComponentPresentation cp = new ComponentPresentation(localComponent, componentTemplate);
            newPage.ComponentPresentations.Add(cp);

            newPage.Save(true);
        }

        /// <summary>
        /// Creates a url friendly slug title.
        /// </summary>
        /// <param name="title">The title to slug.</param>
        /// <returns>A sluggified title.</returns>
        /// <remarks>http://predicatet.blogspot.com/2009/04/improved-c-slug-generator-or-how-to.html</remarks>
        private string CreateSlugTitle(string title)
        {
            byte[] bytes = System.Text.Encoding.GetEncoding("Cyrillic").GetBytes(title);
            title = System.Text.Encoding.ASCII.GetString(bytes).ToLower();

            title = Regex.Replace(title, @"[^a-z0-9\s-]", ""); // invalid chars
            title = Regex.Replace(title, @"\s+", " ")
                .Trim() // convert multiple spaces into one space
                .Substring(0, title.Length <= 45 ? title.Length : 45)
                .Trim(); // cut and trim it
            title = Regex.Replace(title, @"\s", "-"); // hyphens   

            return title;
        }

        /// <summary>
        /// Check whether or not the component exists on a page.
        /// </summary>
        /// <param name="component">The component to check.</param>
        /// <returns>True if no page is found.</returns>
        private bool DoesNotHavePage(Component component)
        {
            UsingItemsFilter filter = new UsingItemsFilter(component.Session);
            filter.ItemTypes = new List<ItemType> { ItemType.Page };
            filter.IncludedVersions = VersionCondition.OnlyLatestVersions;

            return component.GetUsingItems(filter).Count() == 0;
        }

        /// <summary>
        /// Gets a child StructureGroup with the correct title.
        /// </summary>
        /// <param name="title">The title of the StructureGroup to search for.</param>
        /// <param name="directory">The name of the SG's directory (if one needs to be created).</param>
        /// <param name="parentSG">The parent Structure Group.</param>
        /// <returns>The retrieved Structure Group.</returns>
        private StructureGroup GetStructureGroup(string title, string directory, StructureGroup parentSG, OrganizationalItemItemsFilter filter)
        {
            StructureGroup sg = parentSG
                .GetItems(filter)
                .Where(f => f.Title.Equals(title, StringComparison.InvariantCultureIgnoreCase))
                .FirstOrDefault() as StructureGroup;

            if (sg == null)
            {
                sg = CreateStructureGroup(title, directory, parentSG);
            }

            return sg;
        }

        /// <summary>
        /// Creates a StructureGroup using a given title.
        /// </summary>
        /// <param name="title">The title to give the StructureGroup.</param>
        /// <param name="directory">The directory for the StructureGroup.</param>
        /// <param name="parentSG">The SG to create the new folder in.</param>
        /// <returns>The newly created SG.</returns>
        private StructureGroup CreateStructureGroup(string title, string directory, StructureGroup parentSG)
        {
            StructureGroup newSG = parentSG.GetNewObject<StructureGroup>();
            newSG.Title = title;
            newSG.Directory = directory;
            newSG.Save();

            return newSG;
        }
    }
}

Go ahead and deploy your new code (instructions at bottom of previous tutorial) and enjoy!