Filter Orders on the Server
Since SalesHub contains a lot of orders for various customers, it would not be very efficient for all of the orders to be returned when the orders grid renders. To reduce wait times and cut back on the amount of bandwidth that is used when retrieving orders from the server, the Kendo DataSource that the orders grid uses has been configured to do server-side operations.
Configuration
Enable Server Operations
To support filtering on the server side in the Orders Grid, enable ServerOperation
for the Grid on the DataSource declaration.
The following example is an excerpt from the declaration of the Orders Grid—which can be found in Views/Home/Index.cshtml—which shows the configuration of the DataSource.
.DataSource(dataSource => dataSource
.Ajax()
.Read(builder => builder.Url("/api/CustomerOrders/GetOrdersForCustomer/").Type(HttpVerbs.Get))
.Model(model => model.Id("OrderId"))
.ServerOperation(true)
.PageSize(20)
The most important part of the DataSource configuration is the ServerOperation(true)
function call. The passing of true
to this function makes the resulting client-side Kendo UI DataSource include any filters that are applied to it in the request, which it sends to the server. In passing along any filters that need to be applied, the DataSource will also pass up any paging information (if it has been configured for it) to the server in its request.
For the full declaration for the Orders Grid, refer to Views/Home/Index.cshtml.
Support Server-Side Operations
To make the supporting server-side filtering easier, the Kendo UI MVC extensions expose a few classes and functions which handle this approach. The extensions provide DataSourceRequest
, DataSourceResponse
, and ToDataSourceResult
to help with the server-side filtering.
The following example demonstrates part of the code in Api/CustomerOrdersController.cs
that supports this behavior.
using System.Linq;
using System.Web.Mvc;
using Kendo.Mvc.Extensions;
using Kendo.Mvc.UI;
using SalesHub.Client.ViewModels.Api;
using SalesHub.Core.Models;
using SalesHub.Core.Repositories;
namespace SalesHub.Client.Api
{
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
public class CustomerOrdersController : Controller
{
private readonly IOrderRepository _orderRepository;
public CustomerOrdersController(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public JsonResult GetOrdersForCustomer([DataSourceRequest] DataSourceRequest request)
{
IQueryable<Order> orders = _orderRepository.GetAllOrders();
DataSourceResult response = orders.ToDataSourceResult(request, o => new CustomerOrderViewModel
{
IsActive = o.IsActive,
OrderDate = o.OrderDate,
OrderId = o.OrderId,
OrderNumber = o.OrderNumber,
Value = o.ContractAmount,
Weight = o.ContractWeight
});
return Json(response, JsonRequestBehavior.AllowGet);
}
}
}
<--*-->
The implementation for the CustomerOrdersController
is short, because the Kendo UI MVC extensions greatly reduce the amount of code you have to write to support filtering.
Breakdown of Code
The following example demonstrates the first chunk of code.
using Kendo.Mvc.Extensions;
using Kendo.Mvc.UI;
The using
statements in the implementation of the Controller are important because they include the namespaces, which contain the DataSource
request helpers that are used later.
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
<--*--> Though not related to Kendo UI, this attribute is useful when trying to prevent some browsers (for example, Internet Explorer) from caching AJAX calls to the service. They need to be included on the Controller because the underlying data might change any time and the data has to be updated for the user to see.
public JsonResult GetOrdersForCustomer([DataSourceRequest] DataSourceRequest request)
In the previous example, an MVC Action for the CustomerOrdersController
is declared. The return value of the function is a JsonResult
because it is a data service. As filtering data will be received from the client-side DataSource, you have to take a DataSourceRequest
object as a parameter. The [DataSourceRequest]
attribute, which is applied to the parameter, is used by the MVC framework when it binds data from the request to parameters that the Action takes.
IQueryable<Order> orders = _orderRepository.GetAllOrders();
DataSourceResult response = orders.ToDataSourceResult(request, o => new CustomerOrderViewModel
{
IsActive = o.IsActive,
OrderDate = o.OrderDate,
OrderId = o.OrderId,
OrderNumber = o.OrderNumber,
Value = o.ContractAmount,
Weight = o.ContractWeight
});
return Json(response, JsonRequestBehavior.AllowGet);
<--_-->
In the body of the function you get all orders from the database in the form of a Queryable. Then you need to call an extension method, ToDataSourceResult
, on the Queryable of orders. This extension method is provided by the Kendo UI MVC extensions and handles the filtering of the Queryable of orders based on the DataSourceRequest
that you get from DataSource.
The ToDataSourceResult
also provides an overload which allows you to specify a selector function. This selector function is the same as what is normally passed to the standard Select
LINQ function. In this case, you have to convert each order to a CustomerOrderViewModel
before it is returned to the client.
The DataSourceResult
you get from calling ToDataSourceResult
contains the filtered list of Orders, which can now be returned to the client by calling the Json
function and passing in the response.
Filter Orders by Customer
To display orders for the currently selected Customer in the TreeView, you need to write some custom JavaScript. The JavaScript you now have only listens to the select
event of the Customer TreeView and updates the filters on the DataSource, which is used by the Orders Grid.
The following code snippets are located in Scripts/home.js.
var updateGridCustomerFilter = function (customerId) {
var ordersGrid = $("#ordersGrid").data("kendoGrid");
ordersGrid.dataSource.filter({ field: "CustomerId", operator: "eq", value: customerId });
};
window.SalesHub.CustomerTreeView_Select = function (e) {
var node = $(e.node);
var dataItem = e.sender.dataItem(e.node);
if (!dataItem.hasChildren) {
updateGridCustomerFilter(node.data("customer-id"));
}
};
During its setup, a select
event handler is configured for the customer TreeeView and the window.SalesHub.CustomerTreeView_Select
event handler is stated.
window.SalesHub.CustomerTreeView_Select = function (e) {
var node = $(e.node);
var dataItem = e.sender.dataItem(e.node);
if (!dataItem.hasChildren) {
updateGridCustomerFilter(node.data("customer-id"));
}
};
The event handler contains an e
parameter. This is the event object that you get from the Kendo UI TreeView when a new node is selected in it. It contains information about which node is selected.
var node = $(e.node);
var dataItem = e.sender.dataItem(e.node);
The first part of the event handler interacts with the node
property on the event object. The node
property is the DOM element that is selected. That is why you need to convert the DOM object into a jQuery object and then get the corresponding dataItem
for that node.
The dataItem
you get from the Kendo UI TreeView is an object that the Kendo UI TreeView uses to describe each node in the tree.
if (!dataItem.hasChildren) {
updateGridCustomerFilter(node.data("customer-id"));
}
After the dataItem
and the jQuery object for the node are present, you need to check if the node that was selected was actually a customer. To achieve this, see if the dataItem
has any children. If the dataItem
has no children, a customer was selected.
Now you need to call the updateGridCustomerFilter
function, which takes a customerId
as a parameter. Because the data-customer-id
attribute was added to all of the customer nodes when the TreeView was generated on the server side, you need to retrieve that value from the jQuery object by calling the data
function on it.
The following example demonstrates the function which commands the update of the filters on the Grid.
var updateGridCustomerFilter = function (customerId) {
var ordersGrid = $("#ordersGrid").data("kendoGrid");
ordersGrid.dataSource.filter({ field: "CustomerId", operator: "eq", value: customerId });
};
First, you have to find the Orders Grid element on the page by using a jQuery selector locating it by id. Once you get the Grid on the page, get the kendoGrid
object that is associated with it.
After you got the kendoGrid
object, access the DataSource for it through the dataSource
property. Call the filter
function on it and pass in a new filter object to it by using the DataSource.
{ field: "CustomerId", operator: "eq", value: customerId }
The filter you add to the DataSource indicates that the DataSource has to contain only items, which have a CustomerId
equal to the customerId
that you got as a parameter to the function. By adding this filter, the DataSource sends a request to the server asking only for Orders that belong to the specified customer.