Two years ago, when we were still actively using KendoUI we had published our approach on how to introduce custom widgets, which we called User Controls.
We even suggested to introduce UserControl widget into KendoUI core through feedback page.
At that time we got a response from KendoUI team:
Our recommended approach for building reusable app building blocks is expressed via the Kendo UI SPA features delivered earlier this year. Here’s a getting started resource if you’ve not already seen it: http://www.kendoui.com/blogs/teamblog/posts/13-05-16/kendo-ui-spa-screencast-and-getting-started.aspx
It was not clear how the response is related to the suggestion, but we decided not to rebuke the team, and to proceed with our user controls.
Nowdays we use KendoUI no more, and have completely switched to angularjs. Ocasionally, however, we need to support old projects, and peek into docs. Today we've discovered that the team has changed their mind, and allowed custom widgets: "Create Your Own Kendo UI Widget by Inheriting from the Base Widget Class". That's good news!
But the most interesting thing is that the design of their custom widget is very close to what we have suggested then.
After several years of experience with KendoUI we turned our attention to AngularJS. As many other libraries it has its strong and weak sides. There are many resources describing what AngularJS is, and what it is not. Our approach to study AngularJS was through an attempt to integrate it into an existing KendoUI web application.
It's rather straightforward to convert model from KendoUI into AngularJS, as logically both frameworks are equal in this regard. But tactically KendoUI implements model-view binding very differently than AngularJS does. KendoUI binds model to view immediately per each model field, where AngularJS delays a binding of each model field and performs whole model binding in one go. Angular's approach is more performant, and even more appealing to a developer, though the problem is that the time it takes to make whole model binding is proportional to a size (number of objects and properties) of model. This means that if you have a relatively big model you will experience tangible halts in browser's UI while a javascript updating view/model is running.
AngularJS advices some workaround, which in essence is to avoid big model. The problem is that a couple of thousands or even several hundrends of objects and properties are already considered big model. So, you should immediately plan your model, and view to avoid any potential impact. This seriously distracts from the task your're solving.
The idea that your UI will halt for the time proportional to the size of your whole model looks flawed in our opinion. KendoUI knows no such a problem. That's the reason why our KendoUI to AngularJS transition experience was not smooth.
Our analysis of AngularJS sources shows that the issue could be resolved provided model to view binding (it's called digest in that library) was asynchronous.
To verify our ideas we have created a branch nesterovsky-bros/angular.js where we implemented required refactorings. It includes:
-
API based on existing deferred/promise to write algorithms in async way, and
- refactored digest logic.
At the end we have proposed to integrate our changes into the main branch: Make $digest async.
We're not sure whether our proposition will be integrated (rather no than yes). Nevertheless what we have come with is an interesting extension of deferred object that we neither have seen in AngularJS nor in JQuery, so later we will quote that API from q.js and scheduler.js.
In the article "Error handling in WCF based web applications"
we've shown a custom error handler for RESTful service
based on WCF. This time we shall do the same for Web API 2.1 service.
Web API 2.1 provides an elegant way to implementat custom error handlers/loggers, see
the following article. Web API permits many error loggers followed by a
single error handler for all uncaught exceptions. A default error handler knows to output an error both in XML and JSON formats depending on requested
MIME type.
In our projects we use unique error reference IDs. This feature allows to an end-user to refer to any error that has happened during the application life time and pass such error ID to the technical support for further investigations. Thus, error details passed to the client-side contain an ErrorID field. An error logger generates ErrorID and passes it over to an error handler for serialization.
Let's look at our error handling implementation for a Web API application.
The first part is an implementation of IExceptionLogger
interface. It assigns ErrorID and logs all errors:
/// Defines a global logger for unhandled exceptions.
public class GlobalExceptionLogger : ExceptionLogger
{
/// Writes log record to the database synchronously.
public override void Log(ExceptionLoggerContext context)
{
try
{
var request = context.Request;
var exception = context.Exception;
var id = LogError(
request.RequestUri.ToString(),
context.RequestContext == null ?
null : context.RequestContext.Principal.Identity.Name,
request.ToString(),
exception.Message,
exception.StackTrace);
// associates retrieved error ID with the current exception
exception.Data["NesterovskyBros:id"] = id;
}
catch
{
// logger shouldn't throw an exception!!!
}
}
// in the real life this method may store all relevant info into a database.
private long LogError(
string address,
string userid,
string request,
string message,
string stackTrace)
{
...
}
}
The second part is the implementation of IExceptionHandler :
/// Defines a global handler for unhandled exceptions.
public class GlobalExceptionHandler : ExceptionHandler
{
/// This core method should implement custom error handling, if any.
/// It determines how an exception will be serialized for client-side processing.
public override void Handle(ExceptionHandlerContext context)
{
var requestContext = context.RequestContext;
var config = requestContext.Configuration;
context.Result = new ErrorResult(
context.Exception,
requestContext == null ? false : requestContext.IncludeErrorDetail,
config.Services.GetContentNegotiator(),
context.Request,
config.Formatters);
}
/// An implementation of IHttpActionResult interface.
private class ErrorResult : ExceptionResult
{
public ErrorResult(
Exception exception,
bool includeErrorDetail,
IContentNegotiator negotiator,
HttpRequestMessage request,
IEnumerable<MediaTypeFormatter> formatters) :
base(exception, includeErrorDetail, negotiator, request, formatters)
{
}
/// Creates an HttpResponseMessage instance asynchronously.
/// This method determines how a HttpResponseMessage content will look like.
public override Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var content = new HttpError(Exception, IncludeErrorDetail);
// define an additional content field with name "ErrorID"
content.Add("ErrorID", Exception.Data["NesterovskyBros:id"] as long?);
var result =
ContentNegotiator.Negotiate(typeof(HttpError), Request, Formatters);
var message = new HttpResponseMessage
{
RequestMessage = Request,
StatusCode = result == null ?
HttpStatusCode.NotAcceptable : HttpStatusCode.InternalServerError
};
if (result != null)
{
try
{
// serializes the HttpError instance either to JSON or to XML
// depend on requested by the client MIME type.
message.Content = new ObjectContent<HttpError>(
content,
result.Formatter,
result.MediaType);
}
catch
{
message.Dispose();
throw;
}
}
return Task.FromResult(message);
}
}
}
Last, but not least part of this solution is registration and configuration of the error logger/handler:
/// WebApi congiguation.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
...
// register the exception logger and handler
config.Services.Add(typeof(IExceptionLogger), new GlobalExceptionLogger());
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
// set error detail policy according with value from Web.config
var customErrors =
(CustomErrorsSection)ConfigurationManager.GetSection("system.web/customErrors");
if (customErrors != null)
{
switch (customErrors.Mode)
{
case CustomErrorsMode.RemoteOnly:
{
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.LocalOnly;
break;
}
case CustomErrorsMode.On:
{
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Never;
break;
}
case CustomErrorsMode.Off:
{
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
break;
}
default:
{
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Default;
break;
}
}
}
}
}
The client-side error handler remain almost untouched. The implementation details you may find in
/Scripts/api/api.js and Scripts/controls/error.js files.
You may download the demo project here.
Feel free to use this solution in your .NET projects.
Earlier we have written a post
KendoUI's slowest function and now,
we want to point to the next slow function, which is kendo.guid() .
It's used to assign uid to each observable object, and also in couple
of other places.
Here is its source:
guid: function() {
var id = "", i, random;
for (i = 0; i < 32; i++) {
random = math.random() * 16 | 0;
if (i == 8 || i == 12 || i == 16 || i == 20) {
id += "-";
}
id += (i == 12 ? 4 : (i == 16 ? (random & 3 | 8) : random)).toString(16);
}
return id;
}
KendoUI people have decided to define uid as a string in format of
Globally unique identifier.
We think there is no reason to have such a complex value; it's enough to
have counter to generate uid values. As KendoUI relies
on the string type of uid , so we have defined a patch like this:
var guid = 0
kendo.guid = function()
{ return ++guid + ""
}
Consider now a test case. It's almost identical to that in previous post:
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<script src="scripts/jquery/jquery.min..js"></script>
<script src="scripts/kendo/kendo.web.min.js"></script>
<link href="styles/kendo.common.min.css" rel="stylesheet" />
<link href="styles/kendo.default.min.css" rel="stylesheet" />
<link href="styles/style.css" rel="stylesheet" />
<script>
var model;
function init()
{
var source = [];
for(var i = 0; i < 1000; ++i)
{
source.push({ text: "value " + i, value: "" + i });
}
model = kendo.observable(
{
value: "1",
source: new kendo.data.DataSource(
{
data: source
})
});
model.source.read();
}
function patch()
{
var base = kendo.data.binders.widget.source.fn._ns;
var result;
var guid = 0;
kendo.guid = function()
{ return ++guid + "";
};
kendo.data.binders.widget.source.fn._ns = function(ns)
{
return ns ? base.call(this, ns) :
(result || (result = base.call(this, ns)));
}
}
function test()
{
init();
kendo.bind("#view", model);
}
patch();
</script>
</head>
<body>
<p>
<button onclick="test()">Click to start test</button>
</p>
<p id="view">
Select:
<input data-role="dropdownlist"
data-bind="value: value, source: source"
data-text-field="text"
data-value-field="value"/>
</p>
</body>
</html>
Now, we can compare performance with and without that patch.
Here is a run statistics without patch:
Level |
Function |
Count |
Inclusive time (ms) |
Inclusive time % |
Avg time (ms) |
1 |
onclick |
1 |
270.73 |
100 |
270.73 |
1.1 |
test |
1 |
269.73 |
99.63 |
269.73 |
1.1.1 |
init |
1 |
117.07 |
43.24 |
117.07 |
1.1.1.1 |
guid |
1,001 |
72.05 |
26.61 |
0.07 |
1.1.2 |
bind |
1 |
152.65 |
56.39 |
152.65 |
and with patch:
Level |
Function |
Count |
Inclusive time (ms) |
Inclusive time % |
Avg time (ms) |
1 |
onclick |
1 |
172.64 |
100 |
172.64 |
1.1 |
test |
1 |
171.65 |
99.42 |
171.65 |
1.1.1 |
init |
1 |
62.04 |
35.94 |
62.04 |
1.1.1.1 |
guid |
1,001 |
1 |
0.58 |
0 |
1.1.2 |
bind |
1 |
109.6 |
63.49 |
109.6 |
Note that statistics were collected for IE 10.
An example can be found at
slow2.html.
What is the slowest function in kendo?
Or better, which function has most negative performance impact in kendo.
Recently, we were dealing with a simple page, which was too
slow, as data binding took more than second.
The page contained a dropdown list, with ~1000 options. To understand the
reason we have run this page under the IE's built-in javascript profiler, and
...
there, we have found that almost half of the time is taken by a function
(call it X), which receives nothing and returns always the same result!
But, let's now see a minimal example that demostrates the problem:
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<script src="scripts/jquery/jquery.js"></script>
<script src="scripts/kendo/kendo.web.min.js"></script>
<link href="styles/kendo.common.min.css" rel="stylesheet" />
<link href="styles/kendo.default.min.css" rel="stylesheet" />
<link href="styles/style.css" rel="stylesheet" />
<script>
var model;
function init()
{
var source = [];
for(var i = 0; i < 1000; ++i)
{
source.push({ text: "value " + i, value: "" + i });
}
model = kendo.observable(
{
value: "1",
source: new kendo.data.DataSource(
{
data: source
})
});
model.source.read();
}
function test()
{
kendo.bind("#view", model);
}
init();
</script>
</head>
<body>
<p>
<button onclick="test()">Click to start test</button>
</p>
<p id="view">
Select:
<input data-role="dropdownlist"
data-bind="value: value, source: source"
data-text-field="text"
data-value-field="value"/>
</p>
</body>
</html>
There are two parts: initialization, and a test itself that starts upon button
click.
In the initialization part we have defined a model, containing a datasource.
The test part performs data binding.
Now, here is a run statistics:
Function |
Count |
Inclusive time (ms) |
Inclusive time % |
Avg time (ms) |
test |
1 |
456.05 |
100 |
456.05 |
X |
1,000 |
200.14 |
43.89 |
0.2 |
So, X is fast by itself, but it run 1000 times, and took about 44% of all time.
And now, to the function. It's kendo.data.binders.widget.source.fn._ns .
Here is its code:
_ns: function(ns) {
ns = ns || kendo.ui;
var all = [ kendo.ui, kendo.dataviz.ui, kendo.mobile.ui ];
all.splice($.inArray(ns, all), 1);
all.unshift(ns);
return kendo.rolesFromNamespaces(all);
}
We can see that on each call it receives a parameter undefined , and always returns an array with the same content.
Not sure why Kendo UI team made it so complex, but one can easily device a
simple patch that optimizes this code path.
function patch()
{
var base = kendo.data.binders.widget.source.fn._ns;
var result;
kendo.data.binders.widget.source.fn._ns = function(ns)
{
return ns ? base.call(this, ns) :
(result || (result = base.call(this, ns)));
}
}
With this patch applied, the profiler shows:
Function |
Count |
Inclusive time (ms) |
Inclusive time % |
Avg time (ms) |
test |
1 |
253.03 |
100 |
253.03 |
_ns |
1,000 |
6 |
2.37 |
0.01 |
Execution time dropped considerably, and _ns() loses its title of most time
consuming function!
An example can be found at
slow.html.
Earlier, in the article How To: Load KendoUI Templates from External Files,
we were talking about the way to combine project's templates into a single file
using
Text Templates. Now, we would like to suggest the next step.
KendoUI defines text templates that it knows to transform into functions, at
runtime obviously. Thus a template like this:
<tr>
<td data-bind="
text: name"></td>
<td>#: kendo.toString(get("price"), "C") #</td>
<td data-bind="text: unitsInStock"></td>
<td><button
class="k-button"
data-bind="click: deleteProduct">
Delete</button></td>
</tr>
is transformed into a function:
function(data)
{
var o,e=kendo.htmlEncode;
with(data)
{
o='<tr><td data-bind="text: name"></td><td>'+
e( kendo.toString(get("price"), "C") )+
'</td><td data-bind="text: unitsInStock"></td>'
+
'<td><button class="k-button" ' +
'data-bind="click: deleteProduct">Delete</button></td></tr>';
}
return o;
}
The transformation is done through a sequence of of regex replaces.
Now, what's the fastest javascript template engine?
Right! That, which does not work at runtime.
What we
thought is that we can generate those functions at compile time rather than
defining templates.
We have updated
templates.tt to generate template functions, and optionally to generate
<script> tags that call those functions. This way, for an
input footer.tmpl.html:
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<base href="/" />
<link href="styles/kendo.common.min.css" rel="stylesheet" />
<link href="styles/kendo.default.min.css" rel="stylesheet" />
</head>
<body>
<table data-template-id="view">
<tr>
<td>Products count: #: total() #</td>
<td>Total price: #: totalPrice() #</td>
<td colspan="2">Units in stock: #: totalUnitsInStock() #</td>
</tr>
</table>
</body>
</html>
templates.js
will look like this:
nesterovskyBros.templates=
{
...
"footer-view":function(data)
{
var o,e=kendo.htmlEncode;
with(data)
{
...
}
return o;
},
...
};
document.write('<script id="footer-view-template" type="text/x-kendo-template">#=nesterovskyBros.templates["footer-view"](data)#</script>');
To get template function at runtime you simply refer to
nesterovskyBros.templates["footer-view"] .
template.tt now allows you to specify:
scope - a javascript scope for tempate functions, e.g. "nesterovskyBros.templates ";
data-script attribute over each template (default is true ) to prevent
generation of <script> tag;
data-with-block attribute (default is true ) to prevent with(data) {...}
statement in javascript.
See a sample
application that shows how nicely KendoUI UserControls work with those compiled
templates.
While developing with KendoUI we have found kendo.ui.progress(container,
toggle) function to be
very useful. It's used to show or hide a progress indicator
in the container element.
At the same time we have found that we usually used it in a context of async
operation. This way, we want to show progress, perform some asynchronous
operations, hide progress. So, we clearly want to benifit from
RAII pattern: we would like to open a progress scope, and to perform some
activity withing this scope.
Arguing like this, we have defined a utility function, which is the fusion of
kendo.ui.progress() and $.when() . Its signature is
like this:
nesterovskyBros.progress = function(instance /*, task ... */)
where instance is either Model , Widget , JQuery or DOM Element ,
and task is one or more deferred objects. This function shows a progress
and returns a
Promise that will hide a progress when all tasks will be complete.
Implementation is trivial, so we quote it here:
// Fusion of kendo.ui.progress() and $.when().
scope.progress = function(instance /*, task ... */)
{
if (instance instanceof Model)
{
instance = instance.owner && instance.owner();
}
if (instance instanceof Widget)
{
instance = instance.element;
}
if (instance && instance.nodeType)
{
instance = $(instance);
}
var id = ns + "-progress"; // "nesterovskyBros-progress";
var progress = (instance && instance.data(id)) || 0;
if (arguments.length < 2)
{
return progress;
}
var result = $.when.apply(null, [].slice.call(arguments, 1));
if (instance)
{
instance.data(id, ++progress);
kendo.ui.progress(instance, progress > 0);
result.always(
function()
{
progress = instance.data(id) || 0;
instance.data(id, --progress);
kendo.ui.progress(instance, progress > 0);
});
}
return result;
};
The use is like this:
nesterovskyBros.progress(element, $.ajax("/service1"), $.ajax("/service2")).then(myFunc);
The code can be found at
controls.js.
While trying to generalize our practices from KendoUI related projects we've
participated so far, we updated
control.js - a small javascript additions to KendoUI.
At present we have defined:
1.
An extended model. See KendoUI extended
model.
2.
A lightweight user control - a widget to bind a template and a model, and to
facilitate declarative instantiation. See KendoUI User control.
3.
A reworked version of nesterovskyBros.defineControl() function.
var widgetType = scope.defineControl(
{
name:
widget-name-string,
model: widget-model-type,
template: optional-content-template,
windowOptions: optional-window-options
},
base);
When optional-content-template is not specified then template is
calculated as following:
var template = options.temlate || proto.template || model.temlate;
if (template === undefined)
{
template = scope.template(options.name.toLowerCase() + "-template");
}
When windowOptions is specified then
widgetType.dialog(options) function is defined. It's used to open dialog based on
the specified user control. windowOptions is passed to kendo.ui.Window
constructor. windowOptions.closeOnEscape indicates whether to close opened dialog on escape.
widgetType.dialog() returns a kendo.ui.Window instance with content based on the
user control. Window instance contains functions:
result() - a $.Deffered for
the dialog result, and
model() - referring to the user control model.
The model
instance has functions:
-
dialog() referring to the dialog, and
result() referring
to the dialog result.
widget.dialog() allows all css units in windowOptions.width and windowOptions.height
parameters.
base - is optional user control base. It defaults to nesterovskyBros.ui.UserControl .
4. Adjusted splitter. See Adjust KendoUI
Splitter.
5. Auto resize support.
Layout is often depends on available area. One example is Splitter widget that
recalculates its panes when window or container Splitter is resized.
There are other cases when you would like to adjust layout when a container's
area is changed like: adjust grid, tab, editor or user's control contents.
KendoUI does not provide a solution for this problem, so we have defined our
own.
- A widget can be marked with
class="auto-resize" marker;
- A widget may define a
widgetType.autoResize(element) function that adapts widget to a new size.
- A code can call
nesterovskyBros.resize(element) function at trigger resizing of the subtree.
To support existing controls we have defined autoResize() function for Grid ,
Splitter , TabStrip , and Editor widgets.
To see how auto resizing works, it's best to look into
index.html,
products.tmpl.html, and into the implementation
controls.js.
Please note that we consider
controls.js as an addition to KendoUI library. If in the future the library
will integrate or implement similar features we will be happy to start using
their API.
See also: Compile KendoUI templates.
We heavily use kendo.ui.Splitter widget. Unfortunately it has several drawbacks:
- you cannot easily configure panes declaratively;
- you cannot define a pane that takes space according to its content.
Although we don't like to patch widgets, in this case we found no better
way but to patch two functions: kendo.ui.Splitter.fn._initPanes ,
and kendo.ui.Splitter.fn._resize .
After the fix, splitter markup may look like the following:
<div style="height: 100%"
data-role="splitter"
data-orientation="vertical">
<div data-pane='{ size: "auto", resizable: false, scrollable: false }'>
Header with size depending on content.
</div>
<div data-pane='{ resizable: false, scrollable: true }'>
Body with size equal to a remaining area.
</div>
<div data-pane='{ size: "auto", resizable: false, scrollable: false }'>
Footer with size depending on content.
</div>
</div>
Each pane may define a data-pane attribute with pane parameters. A pane may
specify size = "auto" to take space according to its content.
The code can be found at
splitter.js A test can be seen at
splitter.html.
Although WCF REST service + JSON is outdated comparing to Web API, there are yet a lot of such solutions (and probably will appear new ones) that use such "old" technology.
One of the crucial points of any web application is an error handler that allows gracefully resolve server-side exceptions and routes them as JSON objects to the client for further processing. There are dozen approachesin Internet that solve this issue (e.g. http://blog.manglar.com/how-to-provide-custom-json-exceptions-from-as-wcf-service/), but there is no one that demonstrates error handling ot the client-side. We realize that it's impossible to write something general that suits for every web application, but we'd like to show a client-side error handler that utilizes JSON and KendoUI.
On our opinion, the successfull error handler must display an understandable error message on one hand, and on the other hand it has to provide technical info for developers in order to investigate the exception reason (and to fix it, if need):
You may download demo project here. It contains three crucial parts:
- A server-side error handler that catches all exceptions and serializes them as JSON objects (see /Code/JsonErrorHandler.cs and /Code/JsonWebHttpBehaviour.cs).
- An error dialog that's based on user-control defined in previous articles (see /scripts/controls/error.js, /scripts/controls/error.resources.js and /scripts/templates/error.tmpl.html).
- A client-side error handler that displays errors in user-friendly's manner (see /scripts/api/api.js, method defaultErrorHandler()).
Of course this is only a draft solution, but it defines a direction for further customizations in your web applications.
We have upgraded KendoUI and have found that kendo window has stopped to size properly.
In the old implementation window set dimensions like this:
_dimensions: function()
{
...
if (options.width) {
wrapper.width(options.width);
}
if (options.height) {
wrapper.height(options.height);
}
...
}
And here is a new implementation:
_dimensions: function() {
...
if (options.width) {
wrapper.width(constrain(parseInt(options.width, 10), options.minWidth, options.maxWidth));
}
if (options.height) {
wrapper.height(constrain(parseInt(options.height, 10), options.minHeight, options.maxHeight));
}
...
}
Thus nothing but pixels are supported. Earlier we often used 'em' units to define dialog sizes. There was no reason to restrict it like this. That's very unfortunate.
|