THIS IS ARCHIVED DOCUMENTATION

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’ll 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 (that is, facet and sort components).

There’s 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 shouldn’t worry about this for now.

Using the Coveo for Sitecore MVC Template and Components

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

Just like the standard Coveo-Powered Search Page template, the MVC-dedicated one defines the fields available on an item of type Coveo-Powered 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’ll see that only a Coveo Search View component is inserted by default in a Coveo-Powered Search Page (MVC).

There’s 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_INSTANCE_ROOT>\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’s a block of JavaScript code to retrieve the default options of the JavaScript Search Framework V0.9 (Legacy).

  • At the bottom, there’s 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's 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>
    }
}