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:
- DataGridColumnBuilder.CellTemplate - specifies a template for column cells in a DataGrid control.
- ListBuilder.ItemTemplate - specifies a template for items in a List control.
- PopupBuilder.ContentTemplate - specifies a template for a Popup control’s content.
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>")
)
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.
- Define a template in a
using(Html.DevExtreme().NamedTemplate(...))
block. - 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.
- Define a template in a
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<b>John</b>
).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
, andwhile
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:
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 aCellTemplate
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 theText
method, the Text(JS) overload is used. - To pass
value
to thehandleGridButtonClick(cellValue)
event handler, the short anonymous function is used. This function captures thevalue
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>)
)
)
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>);
})
)
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
.
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())); } } }
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 withID_PLACEHOLDER
:<a href="/Home/Details/ID_PLACEHOLDER">Link Text</a>
After that,
TemplatedHtmlHelper
calls thereplacementFunc
function that replacesID_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>);
})
)