Friday, September 18, 2015

Razor helper functions for enabling XPM in 2013 SP1 Tridion

Introduction


By default Tridion installation will let you enabling inline edit of component and pages. And there is documentation on how to output syntax in dreamweaver. Detail documentation on the same can be found at https://docs.sdl.com/LiveContent/web/pub.xql?action=home&pub=SDL%20Tridion%20full%20documentation-v1&lang=en-US#docid=GUID-E7D54121-70A2-4FA6-A516-8ADB8E45AC13&addHistory=true&query=&scope=&tid=&filename=GUID-E7D54121-70A2-4FA6-A516-8ADB8E45AC13.xml&resource=&inner_id=&toc=false&eventType=lcContent.loadDocGUID-E7D54121-70A2-4FA6-A516-8ADB8E45AC13
However, in case of razor templating we do not have straight forward functions.  Hence this article. Here you will find syntax for razor templating functions to enable inline editing of different fields

Details

The Tridion.ContentManager.config file, after Razor mendiator installation from https://code.google.com/p/razor-mediator-4-tridion/, will contain a configurable section for the Razor Mediator for further customization of your templates, such as adding references to other libraries to your templates, and including namespace imports globally to all of your templates.

Below is a sample of what this section can look like. The code that implements the helper functions for XPM is implemented in razor-helpers.txt.

<razor.mediator cacheTime="600" extractBinaries="true" adminUser="DOMAIN\Username">
    <namespaces>
        <add namespace="System.Linq" />
        <add namespace="Tridion.ContentManagement" />
    </namespaces>
    <assemblies>
        <add assembly="RazorSample.Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=60ad7434f03dfcdc" />
        <add assembly="C:\Program Files\Razor\Tests\Test.Sample.dll" />
    </assemblies>
    <imports>
        <add import=\"tcm:120-2233-2048\" />
        <add import=\"/wevdav/020 Design/Building Blocks/System/TBBs/Helpers/razor-helpers.cshtml\" />
        <add import=\"C:\\Program Files\\Razor Mediator\\razor-helpers.txt\" />
        <add import=\"tcm:120-2200-2048\" publications=\"020 Design Master,030 Another Web Design\" />
    </imports>
    <importSettings includeConfigWhereUsed="true" includeImportWhereUsed="true" replaceRelativePaths="false" />
</razor.mediator>

Code

Below is the code(razor-helpers.txt) which has functions to use in your razor template building blocks
@using System.Collections.Generic
@using System.Linq
@using System.Linq.Expressions
@using System.Web
@using Tridion.ContentManager
@using Tridion.Extensions.Mediators.Razor.Models
@using Tridion.ContentManager.Templating.Expression
@using Tridion.ContentManager.ContentManagement

@helper GetFieldValue(string fieldExpression = null, int index = 0)
{
    var fn = new BuiltInFunctions(TridionHelper.Engine, TridionHelper.Package);
    try {
        @fn.GetFieldValue(fieldExpression, index)
    } catch {
        Log.Debug("Unable to resolve: " + fieldExpression + " with index: " + index);
    }
}

@helper FieldStartMarker(string fieldExpression)
{
    var fn = new BuiltInFunctions(TridionHelper.Engine, TridionHelper.Package);
    @fn.FieldStartMarker(fieldExpression)
}

@helper FieldEndMarker()
{
    @:</tcdl:Field>
}

@helper FieldValueStartMarker(int index = 0)
{
    @:<tcdl:FieldValue index="@index">
}

@helper FieldValueEndMarker()
{
    @:</tcdl:FieldValue>
}

@helper StartField(string fieldExpression = null, int index = 0, bool singleValue = true, bool useTcdl = false)
{
    if (fieldExpression != null && singleValue)
    {
        @FieldStartMarker(fieldExpression)
    }
    if (fieldExpression != null || useTcdl)
    {
        @FieldValueStartMarker(index)
    }
}

@helper EndField(bool useTcdl = false, bool singleValue = true)
{
    if (useTcdl)
    {
        @FieldValueEndMarker()
    }
    if (useTcdl && singleValue)
    {
        @FieldEndMarker()
    }
}

@helper StartSingleTcdlField(string fieldExpression)
{
    @StartField(fieldExpression, 0, true, true)
}

@helper EndSingleTcdlField()
{
    @EndField(true, true)
}

@helper RenderSingleTcdlField(string fieldExpression)
{
    @StartField(fieldExpression, 0, true, true)
    @GetFieldValue(fieldExpression, 0)
    @EndField(true, true)
}

@helper RenderMultipleTcdlField(string fieldExpression, int index)
{
    @StartField(fieldExpression, index, true, true)
    @GetFieldValue(fieldExpression, index)
    @EndField(true, true)
}

@helper GetHrefOrTridionHref(dynamic linkComp)
{
Component comp= TridionHelper.Engine.GetObject(linkComp.ID) as Component;
if (linkComp != null && comp.ComponentType == ComponentType.Multimedia)
{
@: href="@linkComp.ID" 
}
else if (linkComp != null)
{
@: tridion:href="@linkComp.ID" 
}
}

@* --------------- COMMON FIELD TYPES ---------------------- *@
 
@helper Text(string text, int index, bool useTcdl = true)
{
    @Text(text, null, index, false, useTcdl)
}
 
@helper Text(string text, string fieldExpression = null, int index = 0, bool singleValue = true, bool useTcdl = false)
{
    @StartField(fieldExpression, index, singleValue, Equals(useTcdl))
    @HtmlEncode(text)
    @EndField(fieldExpression != null || useTcdl, singleValue)
}
 
@helper RichText(string text, int index, bool addParagraph = true, bool useTcdl=true)
{
    @RichText(text, null, addParagraph, index, false, useTcdl)
}
 
@helper RichText(string text, string fieldExpression = null, bool addParagraph = true, int index = 0, bool singleValue = true, bool useTcdl = false)
{
    @StartField(fieldExpression, index, singleValue, useTcdl)
    @((!addParagraph || text == null || text.Contains("</p>")) ? text : "<p>" + text + "</p>")
    @EndField(fieldExpression != null || useTcdl, singleValue)
}
 
@helper Img(ComponentModel imgComp, int index, string attributes="", bool useTcdl=true)
{
    @Img(imgComp, null, attributes, index, false, useTcdl)
}
 
@helper Img(ComponentModel imgComp, string fieldExpression = null, string attributes = "", int index = 0, bool singleValue = true, bool useTcdl = false)
{
    @StartField(fieldExpression, index, singleValue, useTcdl)
    
    if (imgComp != null)
    {
        var alt = imgComp.Title;
        if(imgComp.MetaData != null && imgComp.MetaData.HasValue("AltText"))
        {
            alt = imgComp.MetaData.AltText;
        }
        @:<img src="@imgComp.ID" alt="@alt" @attributes />
    }
    
    @EndField(fieldExpression!=null||useTcdl,singleValue)
}
 
@helper Date(DateTime date, int index, string attributes="", string format = null, bool useTcdl = true)
{
    @Date(date, null, attributes, format, index, false, useTcdl)
}
 
@helper Date(DateTime date, string fieldExpression = null, string attributes = "", string format = "dd MMM yyyy", int index = 0, bool singleValue = true, bool useTcdl = false)
{
    @StartField(fieldExpression, index, singleValue, useTcdl)
    
    if (date > DateTime.MinValue)
    {
        @:<time datetime="@date.ToString("s")" @attributes>@date.ToString(format)</time>
    }    
    
    @EndField(fieldExpression != null || useTcdl, singleValue)
}
 
@helper Link(dynamic linkComp, string linkText, int index, string attributes = "", bool useTcdl = true)
{
    @Link(linkComp, linkText, null, attributes, index, false, useTcdl)
}
 
@helper Link(dynamic linkComp, string linkText = "", string fieldExpression = null, string attributes = "", int index = 0, bool singleValue = true, bool useTcdl = false)
{
    @StartLink(linkComp, fieldExpression, attributes, index, singleValue, useTcdl)
    @linkText
    @EndLink(fieldExpression != null || useTcdl, singleValue)
}
 
@helper ExternalLink(string href, string linkText, int index, string attributes="", bool useTcdl = true)
{
    @ExternalLink(href, linkText, null, attributes, index, false, useTcdl)
}
 
@helper ExternalLink(string href, string linkText, string fieldExpression = null, string attributes = "", int index = 0, bool singleValue = true, bool useTcdl = false)
{
    @StartExternalLink(href, fieldExpression, attributes, index, singleValue, useTcdl)
    @linkText
    @EndLink(fieldExpression != null || useTcdl, singleValue)
}
 
@helper StartLink(dynamic linkComp, string fieldExpression = null, string attributes = "", int index = 0, bool singleValue = true, bool useTcdl = false)
{
    @StartField(fieldExpression, index, singleValue, useTcdl)
    Component comp= TridionHelper.Engine.GetObject(linkComp.ID) as Component;
    if (linkComp != null && comp.ComponentType == ComponentType.Multimedia)
    {
        @:<a href="@linkComp.ID" @attributes>
    }
    else if (linkComp != null)
    {
        @:<a tridion:href="@linkComp.ID" @attributes>
    }

}
 
@helper StartExternalLink(string href, string fieldExpression = null, string attributes = "", int index = 0, bool singleValue = true, bool useTcdl = false)
{
    @StartField(fieldExpression, index, singleValue, useTcdl)
    
    if (!String.IsNullOrEmpty(href))
    {
        @:<a href="@href" @attributes>
    }
}
 
@helper EndLink(dynamic value = null, bool useTcdl = false, bool singleValue = true)
{
    if (value!=null)
    {
        @:</a>
    }
    @EndField(useTcdl, singleValue)
}

Examples for Razor Template Building Blocks

The list of examples shows how to use the helper functions from above section for different types of tridion fields in your Razor template building block

Simple Text Field

@RenderSingleTcdlField("Component.Fields.Title")

Multivalue Text Field

//get the index of the field and pass on the function
@RenderMultipleTcdlField("Component.Fields.Title",index)

Multimedia Field

@Img(Component.Fields.Image, "Component.Fields.Image")

Component Link Field

@{
     var linkAttr = "";
     linkAttr = " target=\"_blank\"";
    }
}
@StartLink(Component.Fields.EmbeddLinkField.LinkInternal, "Component.Fields.EmbeddLinkField.LinkInternal", linkAttr)
 //SOME TEXT OR IMAGE IF NEED TO BE CLICKED TO THE INTERNAL LINK
@EndLink(Component.Fields.EmbeddLinkField.LinkInternal, true, true)

External Link Field

Eg1: If you need your link to be wrapped around some content <a href="google.com"> some other related content may have images/paragraphs etc </a>

@{
     var linkAttr = "";
     linkAttr = " target=\"_blank\"";
    }
}
   @StartExternalLink(Component.Fields.EmbeddLinkField.LinkExternal, "Component.Fields.EmbeddLinkField.LinkExternal", linkAttr)
 //SOME TEXT OR IMAGE 
  @EndLink(Component.Fields.EmbeddLinkField.LinkExternal, true, true)

Eg 2: If you need your link to be wrapped around LinkTitle/Any other single field
@ExternalLink(Component.Fields.EmbeddLinkField.LinkExternal, Component.Fields.LinkTitle, "Component.Fields.EmbeddLinkField.LinkExternal", linkAttr)

There are other helper functions for RichText/Date which can be used if needed. Else you can use 
RenderSingleTcdlField function for same as well.

Happy coding.