Hi guys,
I've been trying to write a generic templating framework that wraps up kendo to deliver a standardised view of a component.
So the idea is that in MVC I would just ask for a url like "~/{type}/Grid" or "~/{type}/View" or "~/{type}/Edit".
For this to be viable I have to be able to prove that this is both maintainable but also "tweakable" in those situations where a type isn't quite the norm for some reason in its rendering requirements.
I get the feeling Kendo is well suited to this but it does require some magic tricks since MVC does not support generic views.
MVC supports generics of course but no way to say something like this in a view to have a multi type view ...
... So i started looking at ways to effectively "work around that MVC limit".
I came to the conclusion that If I defined a generic controller and did url binding by convention I could get the type of the controller so I would know what that T actually was and create a ComponentViewModel<T>.
Then if ComponentViewModel<T> had a base class that was the non generic version where T was a type property I could then do something like this to generate an editor for an object of type T ...
The row template in this case does the same as the object MVC template (iterates over each property and calls html.displayFor(property)) with the key difference being that the output is wrapped in a td tag to fit in a table row.
Here's where my problems begin.
In the editor template above the MVC view emits a kendo template that looks something like this ...
.. when the call to kendoGrid is run I get a row for each item in my datasource but I get the template and no bound values in it.
As in, the template is literally the output for each item in the datasource.
So my question ...
Why does initialising a kendo grid only template and not bind (do I have to template something specific) as opposed to the editor scenario which is just binding and not templating?
The editor is working as I would expect, I call bind and it binds, and the markup that is emitted is the expected output on the server.
With the grid implementation however the markup that is emitted is a row definition and to each row I need to bind an item in the datasource.
The demos / documentation aren't clear about how the javascript kendo functions work internally but I would expect for consistency a templating operation to also perform a bind.
This appears however to not be the case ... or did I miss something?
In other words ...
In an element I want to bind to I might write:
<td><span data-format="dd MMM yyyy" data-bind="text: Created"></span></td>
... but in an element template I would have to write ...
<td><span>#= kendo.toString(Created, 'dd MM yyyy') #</span></td>
My biggest issue is the inconsistency here.
On the server I have an MVC view for outputting an editable version and another for a non editable version, by this convention I now need 2 more versions of each view to account for when the field is being templated vs when it's being bound.
This also raises another interesting question, if a templates data source is updated is this reflected in the templated output since there is no "binding syntax" to inform kendo that the field is bound or do all templated updates trigger a fresh render of the template?
so if I do this ...
<td><span data-format="dd MMM yyyy" data-bind="text: Created, events: { click: myClick }">#= kendo.toString(Created, 'dd MM yyyy') #</span></td>
I can't seem to hook up the click handler.
I've been trying to write a generic templating framework that wraps up kendo to deliver a standardised view of a component.
So the idea is that in MVC I would just ask for a url like "~/{type}/Grid" or "~/{type}/View" or "~/{type}/Edit".
For this to be viable I have to be able to prove that this is both maintainable but also "tweakable" in those situations where a type isn't quite the norm for some reason in its rendering requirements.
I get the feeling Kendo is well suited to this but it does require some magic tricks since MVC does not support generic views.
MVC supports generics of course but no way to say something like this in a view to have a multi type view ...
1.
@model ComponentViewModel<T>
2.
@Html.Kendo().Grid<T>() ...
... So i started looking at ways to effectively "work around that MVC limit".
I came to the conclusion that If I defined a generic controller and did url binding by convention I could get the type of the controller so I would know what that T actually was and create a ComponentViewModel<T>.
Then if ComponentViewModel<T> had a base class that was the non generic version where T was a type property I could then do something like this to generate an editor for an object of type T ...
01.
@using System.Text.RegularExpressions
02.
@using App.Models
03.
@model ComponentViewModel
04.
@{
05.
var rootId = Model.TypeName + Model.ObjectId + "_Editor";
06.
var modelName = Model.TypeName + Model.ObjectId + "_EditorModel";
07.
var m = Activator.CreateInstance(Model.Type);
08.
var dependency = "emedia.dependency.get('" + Model.TypeName + "');";
09.
}
10.
11.
<
div
id
=
"@(rootId)"
class
=
"component k-content"
data-type
=
"@Model.TypeName"
data-item-id
=
"@Model.ObjectId"
>
12.
<
h2
><
span
class
=
"k-sprite edit"
></
span
> @Regex.Replace(Model.Type.Name, @"(?<!_)([A-Z])", " $1"): @Model.ObjectId <
img
src
=
"@Url.Content("
~/Content/close.png")" /></
h2
>
13.
<
script
>
14.
$(function () {
15.
@if(Model.CustomDependencyExists)
16.
{
17.
@Html.Raw(dependency)
18.
}
19.
20.
var @modelName = emedia.createModel('@Model.TypeName', '@Model.ObjectId');
21.
var component = $("#@(rootId)");
22.
23.
kendo.bind(component, @modelName);
24.
$(".details", component).validate();
25.
});
26.
</
script
>
27.
<
form
class
=
"details"
>
28.
<
div
data-description
=
"non dynamic templated stuff"
>
29.
<
input
type
=
"hidden"
name
=
"ID"
data-rule-required
=
"true"
data-bind
=
"value: ID"
/>
30.
</
div
>
31.
<
ul
class
=
"fieldList"
>
32.
@{ Html.RenderPartial("EditorTemplates/Object", m); }
33.
</
ul
>
34.
<
hr
/>
35.
<
button
class
=
"k-button"
data-bind
=
"events: { click: save }"
>Save</
button
>
36.
</
form
>
37.
</
div
>
... Notice the call to Html.RenderPartial where an instance of a T is then MVC templated, pretty neat huh, that works a treat!
All good so far.
So then I thought, ok lets take this a step a further and see if we can template grids, taking things slow I figured it best to start with non editable grids.
Following the same convention I did this ...
01.
@using System.Text.RegularExpressions
02.
@using App.Models
03.
@model GridComponentViewModel
04.
@{
05.
var gridName = Model.TypeName + "Grid";
06.
var modelName = Model.TypeName + "Model";
07.
var dependency = "emedia.dependency.get('" + Model.TypeName + "');";
08.
var m = Activator.CreateInstance(Model.Type);
09.
}
10.
11.
<
div
class
=
"component k-content"
data-type
=
"@Model.TypeName"
>
12.
<
h2
><
span
class
=
"k-sprite folder"
></
span
> @Regex.Replace(Model.TypeName, @"(?<!_)([A-Z])", " $1")s <
img
src
=
"@Url.Content("
~/Content/close.png")" /></
h2
>
13.
<
script
>
14.
$(function () {
15.
//TODO: add signalR behaviour to allow grid update notifications from the server
16.
@if(Model.CustomDependencyExists)
17.
{
18.
@Html.Raw(dependency)
19.
}
20.
21.
@(gridName)Change = function (arg) {
22.
if(typeof emedia.@(Model.TypeName).gridItemClick != 'undefined') {
23.
var grid = $("#@(gridName)").data("kendoGrid");
24.
var item = grid.dataItem(grid.select());
25.
emedia.@(Model.TypeName).gridItemClick(item);
26.
}
27.
};
28.
29.
var @modelName = emedia.createModel('@Model.TypeName');
30.
31.
$("#@(gridName)").kendoGrid({
32.
dataSource: @modelName,
33.
rowTemplate: kendo.Template.compile($("#@(Model.TypeName)Row").html()),
34.
selectable: "row",
35.
columns: emedia.type.columnsFor("@Model.TypeName"),
36.
change: @(gridName)Change,
37.
error: page.error
38.
});
39.
});
40.
</
script
>
41.
<
script
id
=
"@(Model.TypeName)Row"
type
=
"text/x-kendo-template"
>
42.
@{ Html.RenderPartial("DisplayTemplates/Row", m); }
43.
</
script
>
44.
<
div
id
=
"@(gridName)"
class
=
"details"
></
div
>
45.
</
div
>
The row template in this case does the same as the object MVC template (iterates over each property and calls html.displayFor(property)) with the key difference being that the output is wrapped in a td tag to fit in a table row.
Here's where my problems begin.
In the editor template above the MVC view emits a kendo template that looks something like this ...
01.
<
script
id
=
"BusinessTaskRow"
type
=
"text/x-kendo-template"
>
02.
<
tr
>
03.
<
td
><
span
data-bind
=
"text: ID"
></
span
></
td
>
04.
<
td
><
a
href
=
""
data-ref-type
=
"Workflow"
data-bind
=
"attr: { data-ref: WorkflowID }, events: { click: refClick }"
>View Workflow</
a
></
td
>
05.
<
td
><
span
data-bind
=
"text: Title"
></
span
></
td
>
06.
<
td
><
pre
data-bind
=
"text: Description"
></
pre
></
td
>
07.
<
td
><
span
data-format
=
"dd MMM yyyy"
data-bind
=
"text: Created"
></
span
></
td
>
08.
<
td
><
span
data-format
=
"dd MMM yyyy"
data-bind
=
"text: Due"
></
span
></
td
>
09.
<
td
><
span
data-bind
=
"text: BusinessTaskStatusID"
></
span
></
td
></
td
>
10.
</
tr
>
11.
</
script
>
.. when the call to kendoGrid is run I get a row for each item in my datasource but I get the template and no bound values in it.
As in, the template is literally the output for each item in the datasource.
So my question ...
Why does initialising a kendo grid only template and not bind (do I have to template something specific) as opposed to the editor scenario which is just binding and not templating?
The editor is working as I would expect, I call bind and it binds, and the markup that is emitted is the expected output on the server.
With the grid implementation however the markup that is emitted is a row definition and to each row I need to bind an item in the datasource.
The demos / documentation aren't clear about how the javascript kendo functions work internally but I would expect for consistency a templating operation to also perform a bind.
This appears however to not be the case ... or did I miss something?
In other words ...
In an element I want to bind to I might write:
<td><span data-format="dd MMM yyyy" data-bind="text: Created"></span></td>
... but in an element template I would have to write ...
<td><span>#= kendo.toString(Created, 'dd MM yyyy') #</span></td>
My biggest issue is the inconsistency here.
On the server I have an MVC view for outputting an editable version and another for a non editable version, by this convention I now need 2 more versions of each view to account for when the field is being templated vs when it's being bound.
This also raises another interesting question, if a templates data source is updated is this reflected in the templated output since there is no "binding syntax" to inform kendo that the field is bound or do all templated updates trigger a fresh render of the template?
so if I do this ...
<td><span data-format="dd MMM yyyy" data-bind="text: Created, events: { click: myClick }">#= kendo.toString(Created, 'dd MM yyyy') #</span></td>
I can't seem to hook up the click handler.