RESTful is well known and established archetecture. It does not need our approvals or recomendations.
It's something different that we want to express. We'd like to thank all those people who formulated those ideas.
While one can argue that REST has "always" existed along with the web, from our experience we can see that most of web applications we have created up to the late 200x were stateful.
We can see that many web applications should support the application (session) state. And here REST has given a rule:
client stores a session state;
server does not store a session, and is represented as set of services;
if there is a state that cannot be stored on a client (e.g. due to security reasons), then server should use caches, and be able to reconstruct that state upon cache miss.
Now is a good time for the proliferation of the REST, as even weakest clients (browsers) can implement its requirements.
REST allowed us to drastically simplify development and support, to unload server, and to build better web applications.
We compare web applications we have written a decade ago using ASP.NET, and in last 3-4 years using RESTful ideas both in java and in .NET. Clearly, the later have better performance, but from the support standpoint the most appealing is that you can instantly upgrade the application without impacting users, as there are no sessions on the server. For the same reason you should not puzzle over whether you should use in-process sessions and session stickness with load balancing server, or out-of-process sessions.
Things became simpler:
server now is pure logic through services (WCF, Web API, JAX-RS);
client is gui - jquery, kendoui or other;
aspx/jsf pages gone completely;
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.
Awhile ago we have created a set of xml schemas and xslt to represent different languages as xml, and to generate source from those xmls. This way we know to represent and generate: java, c#, cobol, and several sql dialects (read about languages xom on this site).
Here, we'd like to expose a nuisance we had with sql dialects schema.
Our goal was to define a basic sql schema, and dialect extensions. This way we assumed to express general and dialect specific constructs. So, lets consider an example.
General:
-- Select one row
select * from A
DB2:
select * from A fetch first row only
T-SQL:
select top 1 * from A
Oracle:
select * from A where rownum = 1
All these queries have common core syntax, while at the same time have dialect specific means to express intention to return first row only.
Down to the xml schema basic select statement looks like this:
<xs:complexType name="select-statement">
<xs:complexContent>
<xs:extension base="full-select-statement">
<xs:sequence>
<xs:element name="columns" type="columns-clause">
<xs:element name="from" type="from-clause" minOccurs="0">
<xs:element name="where" type="unary-expression" minOccurs="0"/>
<xs:element name="group-by" type="expression-list" minOccurs="0"/>
<xs:element name="having" type="unary-expression" minOccurs="0"/>
<xs:element name="order-by" type="order-by-clause" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="specification" type="query-specification"
use="optional" default="all"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
Here all is relatively clear. The generic select looks like:
<sql:select>
<sql:columns>
<sql:column
wildcard="true"/>
</sql:columns>
<sql:from>
<sql:table name="A"/>
</sql:from>
</sql:select>
But how would you define dialect specifics?
E.g. for T-SQL we would like to see a markup:
<sql:select>
<tsql:top>
<sql:number value="1"/>
</tsql:top>
<sql:columns>
<sql:column
wildcard="true"/>
</sql:columns>
<sql:from>
<sql:table name="A"/>
</sql:from>
</sql:select>
While for DB2 there should be:
<sql:select>
<sql:columns>
<sql:column
wildcard="true"/>
</sql:columns>
<sql:from>
<sql:table name="A"/>
</sql:from>
<db2:fetch-first rows="1"/>
</sql:select>
So, again the quesions are:
Though we have tried several solutions to that problem, none is satisfactory enough.
To allow extensions we have defined that all elements in sql schema are based on
sql-element
, which allows extensions:
<xs:complexType name="sql-element" abstract="true">
<xs:sequence>
<xs:element ref="extension" minOccurs="0"
maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:element name="extension" type="extension"/>
<xs:complexType name="extension" abstract="true">
<xs:complexContent>
<xs:extension base="sql-element"/>
</xs:complexContent>
</xs:complexType>
...
<xs:element name="top" type="top-extension"
substitutionGroup="sql:extension"/>
<xs:complexType name="top-extension">
<xs:complexContent>
<xs:extension base="sql:extension">
<xs:sequence>
<xs:element ref="sql:expression"/>
</xs:sequence>
<xs:attribute name="percent" type="xs:boolean"
use="optional" default="false"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
Unfortunately, this creates too weak typed schema for extensions, thus intellisence suggests too many options.
On a way to a home we usually listen audio books in our car. In most cases these are science fiction stories. One of our favorite authors is Ray Bradbury. Probably you know his short story collection "The Martian Chronicles". Also, we like to glance over technology and science news to stay tuned in the latest innovations.
Recently, we've read an article that states that NASA is going to send a group of scientists-colonists to Mars in an observable future. It sounds in Ray Bradbury style.
Although, we're not specialists in this field, we discussed what difficulties may face such an expedition.
As far as we understand, there are following issues that must be solved before expedition may start:
speed of a spaceship is yet too slow
a shield from radiation doesn't exist
big cargo section to contain sufficient reserve of water, food and oxygen for the expedition to survive
a way to produce enough energy to survive on Mars
Actually, the second issue depends on the first one. At present there is no reliable long term protection from cosmic radiation, which can be installed on a spacecraft. At least we didn't hear about such thing. Thus, long staying in the open space will seriously harm colonists and may bring to naught the whole mission.
Сonsidering these facts, we concluded that a space travel further than the moon is not possible at present.
What could be done to solve these issues?
As a solution of space colonization could be the following. The earthlings won't send people but robots with corresponding equipment and containers for man, animals and plants DNAs or embryos. At the destination place robots will build a colony and will begin to grow people, animals, plants; to train them and to serve them later (at least to man " src="http://www.nesterovsky-bros.com/weblog/smilies/wink.gif"/> ).
How all this may solve the mentioned issues?
It's much easier to create reliable shield from radiation for small DNA or embrios containers.
time and speed of a spacecraft, in such case, will impact less on the mission.
weight of spacecraft in this case could be less, or it may get more payload.
Thus, to bring the era of cosmic expansion, human kind must invest in the development of robotics, artificial intelligence, genetic engineering, development of rapid learning, among other scientific fields in space exploration.
Right now, you may see a beginning? of this trend in sciense here DFKI's robot ape to colonize the Moon?
In the far future, after the beginning will be forgotten, all this may lead to question: who was the first a man or a robot? " src="http://www.nesterovsky-bros.com/weblog/smilies/wink.gif"/>
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.
class="auto-resize"
marker;widgetType.autoResize(element)
function that adapts widget to a new size.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:
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:
Of course this is only a draft solution, but it defines a direction for further customizations in your web applications.
Useful links to those who are still dealing with Cool:GEN.
The site is not cool by itself, but the value is in the tools that authors provide to simplify Cool:GEN development. Especially we would like to mention:
These tools help to manage clean and error free models, which simplifies next migration to Java and C# that we perform.
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.
To simplify KendoUI development we have defined nesterovskyBros.data.Model
, which extends kend.data.Model
class.
nesterovskyBros.data.Model
kendo.data.Model
there is fields
Object - a set of key/value pairs to configure the model fields, but fields have some more options:
fields.fieldName.serializable Boolean
- indicates whether the field appears in an object returned in model.toJSON()
. Default is true
.fields.fieldName.updateDirty Boolean
- indicates whether the change of the property should trigger dirty
field change. Default is true
.model.set()
method then dirty
change event is triggered (provided that fields.fieldName.updateDirty !== false). This helps to build a dependcy graph on that property.Here is an example of a model:
nesterovskyBros.data.ProductModel = nesterovskyBros.data.Model.define(
{
fields:
{
name: { type: "string", defaultValue: "Product Name" },
price: { type: "number", defaultValue: 10 },
unitsInStockValue: { type: "number", defaultValue: 10, serializable: false },
unitsInStock: { type: "string" }
},
unitsInStock: function(value)
{
if (value === undefined)
{
var count = this.get("unitsInStockValue");
return ["one", "two", "three", "four"][count] || (count + "");
}
else
{
this.set("unitsInStockValue", ({one: 1, two: 2, three: 3, four: 4 })[value] || value);
}
}
});
Notice that:
unitsInStock
property is implemented as a function - this helps to map model values to presentation values. model.toJSON()
, or JSON.stringify()
you will see in result name
, price
, unitsInStock
values only - this helps to get model's state and to store it somewhere (e.g. in sessionStorage
). var model = new nesterovskyBros.data.ProductModel({ price: "7", unitsInStock: "one" });
(typeof(model.price) == "number") && (mode.price == 7) && (model.name == "Product Name") && (model.unitsInStockValue == 1)
As with UserControl the implemntation is defined in the controls.js. The sample page is the same index.html
Two weeks ago we've gotten new Lenovo 13" laptops (Yoga-13 with touch screens and Windows 8 Pro on board).
The first expression was WOW! Touch screens! Windows 8! Now we'll try our hand on that new (for us) API. So new, so cool...
A day later. What a shit this new UI. Where are my desktop, "Start" button, all the programs... After googling we've understood - we're not alone.
Few more days later. We've recognized that our SSD hard disk won't live long life with our projects. We generates output several GB a day. Thus we've decided to buy external SD cards - additional 64Gb, class 10. That's enough for us. No sooner said than done. After several attempts to copy our projects from hard drive to SD card (~9Gb of sources) we strongly believe that such a vigorous mix (Lenovo + Win 8 + external SD card) won't survive. Windows 8 hangs up when display off (in middle of data copy, after an hour of work). What a .... of .... this Windows 8, Lenovo and SD cards all together.
Developing with KendoUI we try to formalize tasks. With this in mind we would like to have user controls.
We define user control as following:
It is a javascript class that extends Widget.
It offers a way to reuse UI.
It allows to define a model and a template with UI and data binding.
Unfortunately, KendoUI does not have such API, though one can easily define it; so we have defined our version.
Here we review our solution. We have taken a grid KendoUI example and converted it into a user control.
See index.html
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<!-- (1) Include templates for controls. -->
<script src="scripts/templates.js"></script>
<script src="scripts/jquery/jquery.js"></script>
<script src="scripts/kendo/kendo.web.min.js"></script>
<!-- (2) UserControl definition. -->
<script src="scripts/controls.js"></script>
<!-- (3) Confirm dialog user control. -->
<script src="scripts/controls/confirm.js"></script>
<!-- (4) Products user control. -->
<script src="scripts/controls/products.js"></script>
<link href="styles/kendo.common.min.css" rel="stylesheet" />
<link href="styles/kendo.default.min.css" rel="stylesheet" />
<script>
$(function ()
{
// (5) Bind the page.
kendo.bind(
document.body,
// (6) Model as a datasource.
{ source: [new nesterovskyBros.data.ProductsModel] });
});
</script>
</head>
<body>
<!-- (7) User control and its binding. -->
<div data-role="products" data-bind="source: source"></div>
</body>
</html>
That's what we see here:
Declaration consists of a view and a model.
View is html with data binding. See products.tmpl.html
We build our project using Visual Studio, so templates packaging is done with templates.tt. This transformation converts products template into a tag:
<script id="products-template" type="text/x-kendo-template">
thus template can be referred by a utility function: nesterovskyBros.template("products-template")
.
Model inherits kedo.data.Model. Here how it looks:
// (1) Define a ProducsModel class.
nesterovskyBros.data.ProductsModel = kendo.data.Model.define(
{
// (2) Model properties.
fields:
{
productName: { type: "string", defaultValue: "Product Name" },
productPrice: { type: "number", defaultValue: 10 },
productUnitsInStock: { type: "number", defaultValue: 10 },
products: { type: "default", defaultValue: [] }
},
// (3) Model methods.
addProduct: function ()
{
...
},
deleteProduct: function (e)
{
...
},
...
});
// (4) Register user control.
nesterovskyBros.ui.Products = nesterovskyBros.defineControl(
{
name: "Products",
model: nesterovskyBros.data.ProductsModel
});
That's what we have here:
nesterovskyBros.defineControl(proto)
call, where:proto.name
- defines user control name;proto.model
- defines model type;proto.template
- defines optional template. If not specified, a template is retrieved from $("#" + proto.name.toLowerCase() + "-template").html()
.Now, what's remained is API for the UserControl. See controls.js.
change
- triggered when data source is changed;dataBound
- triggered when widget is data bound;dataBinding
- triggered befor widget data binding;save
- used to notify user to save model state.autoBind (default false)
- autoBind data source;template (default $.noop)
- user control template.dataSource
field and setDataSource()
method.rebind()
method to manually rebuild widget's view from the template and model.model.refresh
method is called, if any.nesterovskyBros.defineControl(proto)
. See above.options.name
- a user control name (used in the data-role);options.model
- a model type;options.windowOptions
- a window options.kendo.ui.Window
) based on the user control.model()
function that returns an instance of model.dialog()
function that returns an instance of the dialog.result()
function that returns an instance of deferred object used to track dialog completion.
deleteProduct: function(e)
{
var that = this;
return nesterovskyBros.dialog.confirm(
{
title: "Please confirm",
message: "Do you want to delete the record?",
confirm: "Yes",
cancel: "No"
}).
open().
center().
result().
then(
function(confirmed)
{
if (!confirmed)
{
return;
}
...
});
}
User controls along with technique to manage and cache templates allow us to build robust web applications. As the added value it's became a trivial task to build SPA.
See also: Compile KendoUI templates.