Validating the format of SharePoint display templates

Display templates are one of the many cool new features in SharePoint 2013, and we have been using them a lot in our search driven solutions (both on premise and in SharePoint Online). In our work, we encountered an interesting bug that could affect you if you are not careful with how you format the managed properties within your HTML template. Hopefully this post will save you a few hours of frustration, and perhaps a few grey hairs!

Let’s say you have a display template with a decent number of managed properties, and you want to have the text formatted in a way where you don’t have to scroll to see all of the properties. Maybe, you want something like this:

<html xmlns:mso="urn:schemas-microsoft-com:office:office" xmlns:msdt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882">
<head>
<title>Event Listing</title>
<!--[if gte mso 9]><xml>
<mso:CustomDocumentProperties>
       <mso:TemplateHidden msdt:dt="string">0</mso:TemplateHidden>
       <mso:ManagedPropertyMapping msdt:dt="string">
              'Title'{Title}:'Title',
              'Path':'Path',
              'EventStartDateOWSDATE':'EventStartDateOWSDATE',
              'EventEndDateOWSDATE':'EventEndDateOWSDATE',
              'EventTimeOWSTEXT':'EventTimeOWSTEXT',
              'RefinableString02':'RefinableString02',
              'RefinableString03':'RefinableString03',
              'EventStreetAddressOWSTEXT':'EventStreetAddressOWSTEXT',
              'AbstractNoteOWSMTXT':'AbstractNoteOWSMTXT'
       </mso:ManagedPropertyMapping>
       <mso:MasterPageDescription msdt:dt="string">This display template will render events.</mso:MasterPageDescription>
    <mso:ContentTypeId msdt:dt="string">0x0101002039C03B61C64EC4A04F5361F385106603</mso:ContentTypeId>
    <mso:TargetControlType msdt:dt="string">;#SearchResults;#</mso:TargetControlType>
    <mso:HtmlDesignAssociated msdt:dt="string">1</mso:HtmlDesignAssociated>
</mso:CustomDocumentProperties></xml><![endif]-->
</head>
<body>
       ...

While you would think this definition of managed properties is completely valid, it will cause you some serious problems when you try to use the last managed property defined in this list, in this example: AbstractNoteOWSTXT. When you upload this HTML Display Template, SharePoint will generate the corresponding JavaScript file that you’ll use in your Search Web Parts and everything appears to be okay. However, if you look closely at the ManagedPropertyMapping in the generated JavaScript file, you will see that a new line character has been injected into the AbstractNoteOWSTXT field. With this setup, you will get undefined when you try to access that property.

ctx['DisplayTemplateData']['ManagedPropertyMapping']={'Title':['Title'], 'Path':['Path'], 'EventStartDateOWSDATE':['EventStartDateOWSDATE'], 'EventEndDateOWSDATE':['EventEndDateOWSDATE'], 'EventTimeOWSTEXT':['EventTimeOWSTEXT'], 'RefinableString02':['RefinableString02'], 'RefinableString03':['RefinableString03'], 'EventStreetAddressOWSTEXT':['EventStreetAddressOWSTEXT'], 'AbstractNoteOWSMTXT':['AbstractNoteOWSMTXT\u0027\r\n     ']};

In order to protect ourselves from this, we added a simple check into scripts that deploy our display templates that ensures the closing tag </mso:ManagedPropertyMapping> is not on a new line; it must immediately follow the last managed property.

Included below is a snippet of that logic. You can use this in your automated build or wherever it fits into your development process.

$spBaseDir = "$spArtifactsDir\DisplayTemplates"

if (!$SkipFolderCheck)
{
    Confirm-FoldersExists $context $list $folder $spBaseDir
}

$filter = @()

# Construct array of filters to pass in as a filter
$Include | foreach {
    $filter += $spBaseDir + '\' + $_
}

dir $filter -Recurse | where { !$_.PsIsContainer } | foreach {

    # First we validate the display template formatting
    $text = [System.IO.File]::ReadAllText($_.FullName)
    
    # We are looking for the new line character with spaces and/or tabs, followed by the managed property mapping closing element
    if ($text -match "`r`n" -or $text -match "`r`n[ `t]+")
    {
        throw "The display template $($_.FullName) has a new line character in front of which will cause us problems."
    }
    
    $folderUrl = $_.DirectoryName -replace [regex]::Escape($spBaseDir), '' -replace [regex]::Escape('\'), '/'
    $folderUrl = "/$(Join-Parts -Separator '/' -Parts $($folder.ServerRelativeUrl), $folderUrl)"
    
    try
    {
        $fs = New-Object System.IO.FileStream($_.FullName, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)
        
        # We have a file that meets our criteria, so we deploy it
        Update-SPOFile -context $context -web $web -list $list -fileStream $fs -fileName $_.Name -folderUrl $folderUrl
    }
    finally
    {
        if ($fs)
        {
            $fs.Dispose()
        }
    }
}

Stories say it best.

Are you ready to make your workplace awesome? We're keen to hear what you have in mind.

Interested in learning more about the work we do?

Explore our culture and transformation services.