This is a migrated thread and some comments may be shown as answers.

Templating and binding

6 Answers 348 Views
General Discussions
This is a migrated thread and some comments may be shown as answers.
Paul
Top achievements
Rank 1
Paul asked on 27 Nov 2014, 10:24 AM
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 ...

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.

6 Answers, 1 is accepted

Sort by
0
Paul
Top achievements
Rank 1
answered on 27 Nov 2014, 12:21 PM
Ok so thinking about this a bit deeper I think the problem stems from the way MVC works compared to the way kendo / any js templating engine works.

In MVC I would say something like ...

@Html.EditorFor(m => m)
    or
@Html.DisplayFor(m => m)

This would pass quite a lot of information and result in a very specific output. 
   The model expression (mapping to a property on an object)
   The model (current instance of that type being bound to)
   The metadata that describes both that type and that particular property on that type

The problem I find with pretty much all javascript templating engines (mainly because of a limitation in the javascript language) is that they lack that metadata.

So if I do this ...

<script id="myTemplate" type="html/x-kendo-templ">
    stuff
</script>

 That will always spit out the same result given a value as a model with basically only the model vlaue being different.

MVC however has the ability to be a lot smarter because of that metadata.
It's the difference for example between say ...

<input type="text" />
    and 
<textarea />

and then a ton of variations like ...
<input type="text" required />
<input type="text" minlength="10" maxlength="100" />

MVC can handle this with a single view whereas the same result in a js engine is currently not possible.

So I came to the conclusion that the way to go was to have MVC worry about rendering markup and have kendo worry about binding markup to data, thus asking each framework to do what it does best.

Looking back, what this means is that in MVC I can write an object template but in javascript I can't (believe me I tried).
The problem stemmed from the passing of metadata down a tree of templates.

For example ...
I tried dynamically attaching metadata to each property on each object taking from the end of my odata endpoints so that everything templated had with it some concept of type in js.
This allowed me to do something like this ...

<script id="view" type="text/x-kendo-template">
    <h2><span class="k-sprite query"></span> #: data.metadata.DisplayName #: <span data-bind="text:ID">#: ID.value #</span> <img src="/Content/close.png" /></h2>
    <ul class="fieldList">
    # for(i in data) { #
     # if(typeof data[i] !== undefined && data[i] != null) { #
      # if(typeof data[i].metadata !== undefined && data[i].metadata != null) { #
       # if(emedia.getTemplate(data[i].metadata.Template) != null) { #
        <li>
            <label class="fieldLabel">#: data[i].metadata.DisplayName  #</label>
            #= renderTemplate(data[i].metadata.Template, data[data[i].metadata.PropertyName]) #
        </li>
    # }}}} #
    </ul>
</script>

This essentially worked because I had the C# code on the server figure out the metadata using a custom metaprovider then return that as a separate type definition call.

The problem I had was that by adding metadata in to the objects in this way everything in an object graph effectively became an object with a value and metadata property in it during a parse operation after a receive and then pushing data back forced the requirement to do the same in reverse.

This felt horrible as a js scripting experience and like I was trying to get the language to do something it wasn't designed to do so I went back to having the server provide the template using MVC and the webAPI provide the data so the calls are very simple but each "framework" was only asked to do what it was built for.

Now I have this problem in that kendo says "do it one way to bind and another way to template". 
This feels wrong to me.

Or am I missing some fundamental reason why binding and templating are deliberately requiring different markup here?

my thinking being that if i do say ...

<span data-bind="text: name" />

... either inside or out of a template I should in theory get the same behaviour as any templating operation should also bind should it not?
Right now I don't appear to be getting that behaviour.



0
T. Tsonev
Telerik team
answered on 01 Dec 2014, 09:21 AM
Hello,

Thank you for taking the time to describe the issue.

The Grid, and other widgets with item templates, will not automatically initialize bindings in their templates.
Instead it will listen for change events on the data source and will redraw the entire page.

Using MVVM bindings in templates is possible, but its not the default behavior and requires an explicit call to kendo.bind

Perhaps you should consider a framework like Angular JS?
The Grid integrates well with it and its semantics are pretty much what you expect.
Bindings work wherever you place them, row template or not.

I hope this helps.

Regards,
T. Tsonev
Telerik
 
Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
 
0
Paul
Top achievements
Rank 1
answered on 01 Dec 2014, 10:40 AM
Hi T. Tsonev,

Seriously? ... that's borderline insulting to everyone purchasing a Telerik licence.

Your suggestion is that I "fix kendo" (an expensive paid for framework) by wrapping it up in Angular (an open source framework)?
Something smells in here.

I agree that it may be possible to do binding and templating correctly with angular but kendo is showing inconsistent behaviour which is the root of my problem.
If the kendo framework was more consistent and inline with the expected pattern of all other frameworks then this type of problem would not present itself, and that still doesn't solve the metadata problem (but I think I've sorted that now anyway).







0
T. Tsonev
Telerik team
answered on 01 Dec 2014, 02:39 PM
Hello,

I didn't mean to offend. Using AngularJS (or Knockout, or others) doesn't mean that you throw away Kendo UI.
Quite the contrary, we'd like the widgets to be able to work in more than one MV* framework.

That's why we try to keep our MVVM module loosely coupled. We won't, for instance, kend.bind the grid rows as this assumes our MVVM framework.
Once you use our AngularJS wrappers or call kendo.bind we know what the context is and can act accordingly.

We actually use model metadata in the MVC helpers, e.g. for validation.
The client-side widgets don't have a concept for metadata and handling it is left to the developer.

Please, let us know if there's anything else that we can address.

Regards,
T. Tsonev
Telerik
 
Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
 
0
Paul
Top achievements
Rank 1
answered on 01 Dec 2014, 04:01 PM
I was just trying to point out that if Kendo has an MVVM and templating framework why can that not behave how the others do since it apparently is capable of doing so when plugged in to the other frameworks?
It looks like in order to support more than one mv* framework you have chosen to work in a way that has no common ground with any of them?
I'm not sure I understand this logic.
 
Can we at least agree that there are a ton of inconsistencies here between the way that kendo works and the way that all other mvvm templating and binding processes work in terms of the code I as a consumer has to work?

Also I think its pretty safe to say that binding to a template or an element in the dom are pretty much one and the same, and in the same manner so is binding 1 item or a list of items.

At least in terms of consumer syntax ... but this appears to be an exception for kendo.

For example ...
If I was to do:
var basicObject = { ... };
var basicArray = [ ... ];
var observableObject = new kendo.observable( ... );
var observableArray = new kendo.observableArray( ... );

I should be able to do:
kendo.bind($("selector"), <any of the above>);

In any other framework I can, but for kendo I have to do things differently quite a lot to compensate for "quirks".

I'd like to be able to do something like this ...

var gridSource = new kendo.data.datasource({ options }); 
kendo.bind($("#grid"), gridSource);

<div id="grid" data-role="grid" />

AND this ...

var source = new kendo.data.datasource({ options });
kendo.bind($("#myThing"), source);

<div id="myThing" />

... but apparently datasource is only for collections?

Can I not then have (as an example) ... a div on my page bound to a complex object that has children so I want to use an odata based datasource to handle the data for me allowing me to just focus on the ui?

And going the other way, I lose all the niceness of datasource for my collection to try getting a consistent binding experience.

...

This probably comes across a bit ranty (sorry about that) so I'm going to stop.
I am getting to grips with it all now so it's not a huge issue but it does result in me having to write hundreds of lines of code to wrap up what should be as standard out of the box so I feel its worth a mention.

Moving on to my immediate problems ...
Taking grids as an example, the binding syntax seems to allow calling functions in order to bind but only for some properties why is that?

For example:
<div data-role="grid" data-row-template="myTemplate" data-columns="myCols" />

myTemplate is meant to be a template id, so why can't myCols just be a js variable / function on the datasource?
Both operations require a js function call do they not?

Also when templating is there a way I can define a template next to the element I plan to template with it when that element is inside some other element binding context ...

In knockout I can do something like (but its been a while so this might not be absolutely correct) ...

<div id="thing" data-bind=" ... ">
   <ul data-bind="foreach: children">
      <li>
          ... definition of child template
      </li>
</div>

now when I make this call ...
ko.applybindings($("#thing"), someModel);

I should get n number of list items based on what's in the someModel.children property.

I was have been trying to do something similar using kendo like this ...

<div id="thing" data-bind=" ... ">
   <ul data-bind="source: children, template: { name: 'child' }" />
   <script>
      <li>
          ... definition of child template
      </li>
   </script>
</div>

kendo.bind($("#thing"), someModel);

In both contexts the model would contain other things at that root level than just this child list but the knockout one seems to be the only route that allows such a binding scenario unless I'm missing something?

0
T. Tsonev
Telerik team
answered on 03 Dec 2014, 01:43 PM
Hello,

The Kendo UI MVVM framework is based on explicit change notifications using get/set, much like Knockout.
This is why it will automatically convert models to ObservableObject. The model can contain arrays, but can't be an array itself.

The DataSource is an abstraction over remote data accessible over different transports.
Its principal responsibility is synchronizing the model objects (also of type ObservableObject) with a remote service.

Widgets that expect a DataSource will directly handle updating its model objects.
This is handled directly as possible without the help of an MVVM framework.

If you need a simple data-bound widget with entirely custom templates then the ListView might be most suitable.

Widget configuration can't be bound with expressions.
This would mean that the widgets must be able react to changes in any configuration properties.
This is an expensive operation that is currently handled through setOptions.

For binding to arrays see Source binding to array.
I hope this helps.

Regards,
T. Tsonev
Telerik
 
Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
 
Tags
General Discussions
Asked by
Paul
Top achievements
Rank 1
Answers by
Paul
Top achievements
Rank 1
T. Tsonev
Telerik team
Share this question
or