Coveo for Sitecore 5 is now available!

Understanding How MVC Components Work Behind the Scenes Using the Coveo for Sitecore Legacy Search UI Framework

Coveo for Sitecore 4.1 (November 2018)

This page is an overview of the Coveo for Sitecore MVC components.

Requirements

Using Views Instead of Layouts and Sublayouts

The key difference when you develop a Sitecore website using ASP.NET MVC instead of standard ASP.NET is that you use View Renderings - or simply put, views - instead of traditional layouts and sublayouts.

This implies that you will no longer be working with ASPX and ASCX files in the background, but with CSHTML files instead. For example, in the context of Coveo for Sitecore, the Coveo Search View renders links to a file named SearchView.cshtml. The same principle applies to the other components of a search interface (i.e., facet and sort components).

There is also a model associated with each view, as you can see in the Model field. A model manages the state of a component (see Model-View-Controller), but you should not worry about this for now.

Using the Coveo for Sitecore MVC Template and Components

As you can see in the previous diagram, there is an MVC version of the Coveo Search Page template (located under /sitecore/Templates/CoveoModule/Search/Coveo Search Page (MVC)). This is what you need to create an MVC-based search interface.

Just like the standard Coveo Search Page template, the MVC-dedicated one defines the fields available on an item of type Coveo Search Page (MVC), as well as the components that are automatically inserted by default in such a search page. Indeed, if you look at the __Standard Values item associated with this template, you will see that only a Coveo Search View component is inserted by default in a Coveo Search Page (MVC).

There is also an MVC version of each one of the standard ASP.NET components.

Understanding the Code Behind Views

CSHTML files associated with views are stored under <sitecore installation folder>\Website\Views\Coveo. Their overall markup is similar to traditional ASPX and ASCX files. However, the difference lies in the syntax that you must use for inline code: the Razor Syntax.

For example, here is what the code of SearchView.cshtml (Coveo Search View component) looks like:

  • At the top, there is a block of JavaScript code to retrieve the default options of the JavaScript Search Framework V0.9 (Legacy).
  • At the bottom, there is the exact same block of JavaScript code as in CoveoSearch.ascx to initialize the JavaScript Search Framework.
  • All over the file, you can find the same HTML elements as in CoveoSearch.ascx.
  • You can also find statements such as:

    @if (Model.DisplayLogo) {
      <div class="coveo-logo-column">
        <div class="coveo-logo"></div>
      </div>
    }
    
    • this uses the Razor Syntax. It checks whether the Display the logo option is checked in the options of the Coveo Search View component. If it does, then the inner div element is rendered.
@using Coveo.UI
@using Coveo.UI.Extensions
@using Sitecore.Mvc
@model Coveo.UI.Mvc.Models.SearchModel
@* When customizing this component, ensure to use "Coveo.$" instead of the regular jQuery "$" to
   avoid any conflicts with Sitecore's Page Editor/Experience Editor.  *@
@Html.Coveo().RenderErrorSummary(Model.ValidateModel())
@if (Model.IsConfigured) {
    <script type="text/javascript" src="/Coveo/js/cultures/@(Model.CultureName).js"></script>
    <script type="text/javascript">
        Coveo.$(function() {
            CoveoForSitecore.componentsOptions = @(Html.Raw(Model.GetJavaScriptInitializationOptions()));
        });
    </script>
    <!-- This hidden input is required to bypass a problem with the Enter key causing a form submission
        if the form has exactly one text field, or only when there is a submit button present. -->
    <input type="text" class="fix-submit" />
    <div class="search-container">
        <div id="search" class="CoveoSearchInterface" data-enable-history="@Model.EnableHistory"
                                                      data-results-per-page="@Model.ResultsPerPage"
                                                      data-excerpt-length="@Model.ExcerptLength"
                                                      data-hide-until-first-query="@Model.HideUntilFirstQuery"
                                                      data-auto-trigger-query="@Model.AutoTriggerQuery">
            @if (Model.AnalyticsEnabled) {
                <div class="CoveoAnalytics"
                     data-anonymous="@Model.IsUserAnonymous"
                     data-endpoint="@Model.GetAnalyticsEndpoint()"
                     data-search-hub="@Model.GetAnalyticsCurrentPageName()"
                     data-send-to-cloud="@Model.CoveoAnalyticsEnabled">
                </div>
            }
            <div class="coveo-results-section">
                <div class= @if (Model.DisplayFacets) {
                                @:"coveo-results-column float-right"
                            } else {
                                @:"coveo-results-column"
                            }>
                    @if (Model.DisplaySearchBox) {
                        <div class="coveo-searchBox-column">
                            <div class="CoveoSearchBox" data-activate-omnibox="@Model.ActivateOmniBox"
                                @if (Model.ActivateOmniBox) {
                                    @:data-omnibox-delay="@Model.SearchBoxSuggestionsDelay"
                                }
                                @if (Model.IsSearchAsYouTypeActivated) {
                                    @:data-enable-search-as-you-type="true"
                                    @:data-search-as-you-type-delay="@Model.SearchBoxSuggestionsDelay"
                                }>
                            </div>
                        </div>
                    }
                    </div>
                @if (Model.DisplayFacets) {
                    <div class="coveo-facet-column">
                        @if (Model.DisplayLogo) {
                            <div class="coveo-logo-column">
                                <div class="coveo-logo"></div>
                            </div>
                        }
                        @Html.Sitecore().Placeholder("coveo-facets-mvc")
                        &nbsp;
                    </div>
                }
                <div class= @if (Model.DisplayFacets) {
                                @:"coveo-results-column float-right"
                            } else {
                                @:"coveo-results-column"
                            }>
                    @if (Model.DisplayBreadcrumb) {
                        <div class="CoveoBreadcrumb"></div>
                    }
                    <div class="coveo-results-header">
                        <div class="coveo-summary-section">
                            @if (Model.DisplayQuerySummary) {
                                <span class="CoveoQuerySummary"></span>
                            }
                            @if (Model.DisplayQueryDuration) {
                                <span class="CoveoQueryDuration"></span>
                            }
                        </div>
                        @if (Model.DisplaySorting) {
                            <div class="coveo-sort-section">
                                @Html.Sitecore().Placeholder("coveo-sorts-mvc")
                            </div>
                        }
                    </div>
                    <div class="CoveoHiddenQuery"></div>
                    @if (Model.DisplayDidYouMean) {
                        <div class="CoveoDidYouMean"></div>
                    }
                    @if (Model.DisplayErrorReport) {
                        <div class="CoveoErrorReport"></div>
                    }
                    @if (Model.DisplayResultList) {
                        if (Model.DisplayTopPager) {
                            <div class="CoveoPager" data-number-of-pages="@Model.PagerNumberOfPages"
                                                    data-show-previous-next="@Model.PagerShowPreviousNext"></div>
                        }
                        <div class="CoveoResultList" data-wait-animation="fade"
                                                     data-enable-infinite-scroll="@Model.EnableInfiniteScroll"
                                                     data-infinite-scroll-page-size="@Model.InfiniteScrollPageSize">
                            <script class="result-template" type="text/x-underscore-template">
                                <div class='coveo-icon objecttype {{-raw.objecttype}}'>{{-raw.objecttype}}</div>

                                {{/* Change the tooltip on the date. */}}
                                {{if(raw.@(Model.ToCoveoFieldName("created", false)) === raw.@(Model.ToCoveoFieldName("updated", false))) { }}
                                    <div class='coveo-date' title='@(Model.Labels[LocalizedStringKeys.CREATION_TIME])'>{{-dateTime(raw.sysdate)}}</div>
                                {{ } else { }}
                                    <div class='coveo-date' title='@(Model.Labels[LocalizedStringKeys.LAST_TIME_MODIFIED])'>{{-dateTime(raw.sysdate)}}</div>
                                {{ } }}
                                <div class='coveo-title'>
                                    {{ if (raw.@(Model.ToCoveoFieldName("HasLayout", false)) === "1" || raw.syssource !== "@Model.IndexSourceName") { }}
                                        <a class='CoveoResultLink'>{{=title?highlight(title, titleHighlights):clickUri}}</a>
                                    {{ } else { }}
                                        <span class='CoveoResultTitle'>{{=title?highlight(title, titleHighlights):''}}</span>
                                    {{ } }}
                                    {{ if (hasHtmlVersion) { }}
                                        <div class='CoveoQuickView' data-title="<span class='coveo-icon objecttype {{-raw.objecttype}}'></span>{{= Title }}" data-fixed='true' data-width='70%' data-heigth='70%'>
                                            <div class='coveo-quick-view-header'>
                                                <table class='CoveoFieldTable'>
                                                    <tr data-field='@@sysdate' data-caption='Date' data-helper='dateTime' />
                                                    <tr data-field='@@sysauthor' data-caption='Author' />
                                                    <tr data-field='@@clickuri' data-html-value='true' data-caption='URL' data-helper='anchor' data-helper-options='{text: \"{{= raw.syssource }}\" , target:\"_blank\"}'>
                                                </table>
                                            </div>
                                            <div class='CoveoQuickViewDocument'></div>
                                        </div>
                                    {{ } }}
                                </div>

                                <div class='coveo-excerpt'>
                                    {{=highlight(excerpt, excerptHighlights)}}
                                </div>

                                <table class='CoveoFieldTable'>
                                    <tr data-field='@(Model.ToCoveoFieldName("_templatename"))' data-caption='@(Model.Labels[LocalizedStringKeys.TEMPLATE])' />
                                    {{ if (raw.@(Model.ToCoveoFieldName("created", false)) === raw.@(Model.ToCoveoFieldName("updated", false))) { }}
                                        <tr data-field='@(Model.ToCoveoFieldName("parsedcreatedby"))' data-caption='@(Model.Labels[LocalizedStringKeys.CREATED_BY])' />
                                    {{ } else { }}
                                        <tr data-field='@(Model.ToCoveoFieldName("parsedcreatedby"))' data-caption='@(Model.Labels[LocalizedStringKeys.CREATED_BY])' />
                                        <tr data-field='@(Model.ToCoveoFieldName("created"))' data-caption='@(Model.Labels[LocalizedStringKeys.CREATED])' data-helper='dateTime' />
                                        <tr data-field='@(Model.ToCoveoFieldName("parsedupdatedby"))' data-caption='@(Model.Labels[LocalizedStringKeys.UPDATED_BY])' />
                                    {{ } }}
                                    <tr data-field='@(Model.ToCoveoFieldName("parsedlanguage"))' data-caption='@(Model.Labels[LocalizedStringKeys.LANGUAGE])' />
                                    <tr>
                                        <th class='CoveoCaption'>@(Model.Labels[LocalizedStringKeys.UNIFORM_RESOURCE_IDENTIFIER])</th>
                                        <td class='CoveoClickableUri'>
                                            <a href="{{= clickUri}}">{{= clickUri}}</a>
                                        </td>
                                    </tr>
                                </table>
                            </script>
                        </div>
                        if (Model.DisplayBottomPager) {
                            <div class="CoveoPager" data-number-of-pages="@Model.PagerNumberOfPages"
                                                    data-show-previous-next="@Model.PagerShowPreviousNext"></div>
                        }
                    }
                </div>
            </div>
        </div>
    </div>
    <script type="text/javascript">
        Coveo.$(function() {
            Coveo.$('#search').coveoForSitecore('init', CoveoForSitecore.componentsOptions);
        });
    </script>
}
@if (Model.HasErrors) {
    <div class="CoveoServerError">
        <h3>@Model.Labels[LocalizedStringKeys.FRIENDLY_SEARCH_UNAVAILABLE_TITLE]</h3>
        <h4>@Model.Labels[LocalizedStringKeys.FRIENDLY_SEARCH_UNAVAILABLE_DETAIL]</h4>
    </div>
    if (Html.Coveo().IsEditingInPageEditor()) {
        <script type="text/javascript">
            Coveo.$(function() {
                Coveo.PageEditorDeferRefresh.triggerUpdate();
            });
        </script>
    }
}