Skip to main content

Templates

  • 9 minutes to read

This article explains how to implement and apply templates. Templates allow you to customize how the control parts (titles, cells, items, and so on) are rendered.

Use *Template() methods to define templates, for example:

Template Syntax

A template consists of Razor markup and ERB-style constructs (<% %>) that can use parameters. To define a template, use the @<text> block in a control’s *Template(RazorBlock templateContent) method.

@(Html.DevExtreme().List()
    .DataSource(DataSource)
    .ItemTemplate(@<text>
        <div><%- Name %></div>
    </text>)
)
object[] DataSource = new[] {
    new { Name = "John" },
    new { Name = "Jane" }
};

You can also use @Html in templates, for example, to nest controls or access standard HTML helpers.

If a template is short and does not use Razor constructs (started with @), you can use a shorthand overload of a *Template method with a String argument:

@(Html.DevExtreme().List()
    .DataSource(DataSource)
    .ItemTemplate("<div><%- Name %></div>")
)

Note

DevExtreme *Template(RazorBlock templateContent) methods, like ItemTemplate(RazorBlock), are not compatible with native ASP.NET Core/MVC’s @Render* expressions (for example, @RenderBody). To incorporate these expressions, use named templates.

External Templates

You can define templates outside of a control declaration. This can be useful in the following cases:

  • templates are large;
  • you want to reuse a template;
  • you need to nest templates (the code below demonstrates how to nest the List control within the Popup control).

There are two ways to declare external templates:

  • Extract the template markup to a partial Razor file.

    @(Html.DevExtreme().Popup()
        .ID("myPopup")
        .ContentTemplate(@<text>
            @(await Html.PartialAsync("_MyPopupContentTemplate"))
        </text>)
    )
    
    @(Html.DevExtreme().List()
        .DataSource(ListDataSource)
        .ItemTemplate(@<text>
            <div><%- Name %></div>
        </text>)
    )
    
  • Use named templates.

    1. Define a template in a using(Html.DevExtreme().NamedTemplate(...)) block.
    2. Specify the template name in a *Template(TemplateName name) method.
    @(Html.DevExtreme().Popup()
        .ID("myPopup")
        .ContentTemplate(new TemplateName("myPopupContentTemplate"))
    )
    
    @using (Html.DevExtreme().NamedTemplate("myPopupContentTemplate")) {
        @(Html.DevExtreme().List()
            .DataSource(ListDataSource)
            .ItemTemplate(@<text>
                <div><%- Name %></div>
            </text>)
        )
    }
    

    A named template can be declared in the same Razor file where you declare a control or in a layout.

    Note

    • Template names should be unique throughout an application.
    • Named templates should be defined at the top level. They cannot be declared in another template.

ERB-style Constructs

Template syntax allows you to embed JavaScript code within a pair of <% and%> delimiters. There are three types of tags:

  • HTML-encoded expression tags

    <%- expression %>

    These tags are substituted with the HTML-encoded result of the expression code (<b>John</b> becomes &lt;b&gt;John&lt;/b&gt;).

  • Non-HTML-encoded expression tags

    <%= expression %>

    These tags are substituted with the result of the expression code without encoding.

    Important

    Use non-HTML-encoded expressions only when you need HTML. Note that this can make your application vulnerable to cross-site scripting (XSS).

  • Execution tags

    <% code %>

    Use these tags to execute arbitrary JavaScript code. The code does not need to be complete or self-contained within a single tag. For example, if, for, and while statements opened in one tag can be closed in a later tag.

    The example below shows how to render markup that depends on the Calendar’s current view:

    @(Html.DevExtreme().Calendar()
        .CellTemplate(@<text>
            <% if(view !== "month") { %>
                <b><%- text %></b>
            <% } else { %>
                <%- text %>
            <% } %>
        </text>)
    )
    

Important

  • ERB-style expressions are executed on a client. You cannot use server code in them.

Access Template Parameters

Template parameters are the *Data or *Info objects that are specific for different templates and are documented in the client API reference. For example:

  • The cellInfo object of a DataGrid column’s cellTemplate provides a predefined set of properties: value, text, columnIndex, and so on.
  • The itemData object of a List’s itemTemplate allows you to access the data linked to the current list item. The object returns an entry from items or a data record from dataSource.

The entire objects can be referenced in a template as obj. Properties of the *Data or *Info object are available directly in a template scope. These properties are called free variables.

The example below shows how to access the Name property.

@(Html.DevExtreme().List()
    .DataSource(DataSource)
    .ItemTemplate(@<text>
        <%- Name %>     <!-- Name as a free variable -->
        <%- obj.Name %> <!-- Name as a property of obj -->
    </text>)
)
object[] DataSource = new[] {
    new { Name = "John" },
    new { Name = "Jane" }
};

You can use obj and free variables in JavaScript code within a template. Refer to Bind a Nested Control to Template Parameters for more information.

JavaScript Functions as Templates

You can define templates as JavaScript functions like you do in DevExtreme (refer to the Function section of the template article) and use them to:

  • implement complex logic (like conditional render);
  • modify a template container element;
  • access extra parameters passed by DevExtreme.

Use *Template(new JS(...)) to define a template as a function. Refer to new JS() Expression for more information.

In the example below, the List’s ItemTemplate is declared as the myList_itemTemplate function that renders items content and applies a custom style to the items using jQuery. The JavaScript function approach is used to access the itemIndex and itemElement parameters in the template. These parameters are not available if you use ERB-style constructs (refer to Access Template Parameters).

@(Html.DevExtreme().List()
    .DataSource(DataSource)
    .ItemTemplate(new JS("myList_itemTemplate"))
)

<script>
    function myList_itemTemplate(itemData, itemIndex, itemElement) {
        itemElement
            .addClass("my-custom-style")
            .append(
                $("<span>").text("Item index: " + itemIndex + ", Name: " + itemData.Name)
            );
    }
</script>

Nested Controls

You can use DevExtreme-based controls within templates. For example, the following code nests the DataGrid control in the Popup control:

@(Html.DevExtreme().Popup()
    // ...
    // Specifies the contents of the Popup control
    .ContentTemplate(@<text>
        @(Html.DevExtreme().DataGrid<Sale>()
            .Columns(columns => {
                columns.AddFor(m => m.Region);
                columns.AddFor(m => m.City);
                columns.AddFor(m => m.Amount);
                columns.AddFor(m => m.Date);
            })
        )
    </text>)
)

The popup should look like this:

Popup in DataGrid

You can find this code in the Demo: Drill Down.

Note

  • Since Razor allows only one level of @<text> tags, you cannot define a template within another template. Use external templates for high-level nesting.
  • If you use DevExtreme-based controls within an ItemTemplate or a CellTemplate template, ensure you specify a unique ID in the nested control’s ID option. Alternatively, do not specify this option. In this case, nested control instances get auto-generated IDs.

Bind a Nested Control to Template Parameters

A nested control’s configuration can depend on template parameters.

The example below defines a template for DataGrid cells. A cell contains a button whose configuration depends on the cellTemplate‘s value free variable.

  • To pass value to the Text method, the Text(JS) overload is used.
  • To pass value to the handleGridButtonClick(cellValue) event handler, the short anonymous function is used. This function captures the value free variable and passes it to the event handler as an argument.

    Note

    If a handler is short, you do not need to extract it to an external function - you can use a short inline function instead, for example:

    .OnClick("function() { alert(value); }")

    Refer to Handle Events and Define Callbacks for more information.

@(Html.DevExtreme().DataGrid()
    // ...
    .Columns(columns => {
        columns.Add()
            .DataField("Name")
            .CellTemplate(@<text>
                @(Html.DevExtreme().Button()
                    .Text(new JS("value"))
                    .OnClick("function() { handleGridButtonClick(value); }")
                )
            </text>);
    })
)
<script>
    function handleGridButtonClick(cellValue) {
        alert("Cell value:" + cellValue);
    }
</script>

Example: Master-Detail Grid

A common use-case of binding a nested control to template parameters is the Master-Detail Grid. In the following code, the detail section nests another DataGrid. The data.OrderID free variable is used in the LoadParams of its DataSource.

@(Html.DevExtreme().DataGrid()
    .DataSource(d => d.WebApi().Controller("DataGridMasterDetailView").Key("ID"))
    .Columns(columns => {
        columns.Add().DataField("FirstName");
        columns.Add().DataField("LastName");
        // ...
    })
    // Configures the Master-Detail UI
    .MasterDetail(md => md
        .Enabled(true)
        // Specifies the contents of the detail section
        .Template(@<text>
            @(Html.DevExtreme().DataGrid()
                .DataSource(d => d.WebApi()
                    .Controller("DataGridMasterDetailView")
                    .LoadAction("TasksDetails")
                    // Use "data.ID" in LoadParams
                    .LoadParams(new { id = new JS("data.ID") })
                )
            )
        </text>)
    )
)

Demo: Master-Detail View

Multi-Nested Controls

There are cases when template parameters should be passed through an intermediate control. For example, if you place a detail DataGrid in a TabPanel, this DataGrid has access to Tab template’s parameters, but does not see parameters of a master DataGrid’s detail template. To solve this situation, attach the tabExtras custom option to the Tab, then you can access it in the tab1Template template:

@(Html.DevExtreme().DataGrid()
    .KeyExpr("ID")
    .DataSource(MasterGridDataSource, key: "ID")
    .MasterDetail(m => m
        .Enabled(true)
        .Template(@<text>

            @(Html.DevExtreme().TabPanel()
                .Items(items => {
                    items.Add()
                        .Title("Tab 1")
                        .Option("tabExtras", new {
                            masterKey = new JS("key")
                        })
                        .Template(new TemplateName("tab1Template"));
                })
            )

        </text>)
    )
)

@using (Html.DevExtreme().NamedTemplate("tab1Template")) {
    <!-- Use tabExtras.masterKey to configure a detail grid  -->
}
object[] MasterGridDataSource = new[] {
    new { ID = 1, Name = "John" },
    new { ID = 2, Name = "Jane" }
};

Demo: Advanced Master-Detail View

HTML Helpers in Templates

You can use HTML helpers in templates as usual if they render static content. For instance, if you configure grid columns and a column’s cells should display the same link. The code below shows how to use the ActionLink HTML helper to render this link.

@(Html.DevExtreme().DataGrid()
    .Columns(columns => {
        //...
        columns.Add().CellTemplate(@<text>
            @Html.ActionLink("Link Text", "Details")
        </text>);
    })
)

Configure grid columns

If you need to use template parameters, use the approach with a custom HtmlHelper extension. The steps below demonstrate an example of how to create a template if the links in the grid column depend on the OrderID.

  1. In the project’s root, create the TemplatedHtmlHelperExtensions.cs file that declares an HtmlHelper extension:

    using Microsoft.AspNetCore.Html;
    using Microsoft.AspNetCore.Mvc.Rendering;
    using System;
    using System.IO;
    using System.Text.Encodings.Web;
    
    public static class TemplatedHtmlHelperExtensions {
        public static IHtmlContent TemplatedHtmlHelper(this IHtmlHelper html, IHtmlContent template, Func<string, string> replacementFunc) {
            using(var writer = new StringWriter()) {
                template.WriteTo(writer, HtmlEncoder.Default);
                return html.Raw(replacementFunc(writer.ToString()));
            }
        }
    }
    
  2. Use the declared extension in a Razor file (.cshtml):

    @(Html.DevExtreme().DataGrid()
        .Columns(columns => {
            //...
            columns.Add().CellTemplate(@<text>
                @Html.TemplatedHtmlHelper(
                    Html.ActionLink("Link Text", "Details", "ControllerName", new { id = "ID_PLACEHOLDER" }, null),
                    text => text.Replace("ID_PLACEHOLDER", "<%- data.OrderID %>")
                )
            </text>);
        })
    )
    

    In this code, ActionLink generates the link with ID_PLACEHOLDER:

    <a href="/Home/Details/ID_PLACEHOLDER">Link Text</a>
    

    After that, TemplatedHtmlHelper calls the replacementFunc function that replaces ID_PLACEHOLDER with the <%- data.OrderID %> ERB-style expression tag. The resulting expression:

    <a href="/Home/Details/"<%- data.OrderID %>">Link Text</a>
    

You can use HTML elements instead of helpers.

@(Html.DevExtreme().DataGrid()
    .Columns(columns => {
        columns.Add().CellTemplate(@<text>
            <a href="@Url.Action("ActionName", "ControllerName")/<%- data.OrderID %>">Link Text</a>
        </text>);
    })
)