I have a rather large ASP.NET MVC web application which uses KnockoutJS extensively throughout. Over the years I've created many templates for the application, however they all reside in various web pages using named script tags. It's almost become unbearable to manage them on the various views. I'd like to figure out a way to consolidate these templates into their own html files so that they are more manageable.
I'd like to know if there are any libraries out there that supports this concept? I don't want to reinvent the wheel, as I'm sure someone has already ran into this problem in the past and solved it. Here is a quick overview of what I'm dealing with...
Basically, I have a lot of content that resembles the following markup. Notice that I have my templates defined in the same page as my actual content markup:
[[ HOME/INDEX.CSHTML ]]
<h1>Customers</h1>
<div data-bind="template: {name: 'personTmpl', foreach: customers}"></div>
<h1>Products</h1>
<div data-bind="template: {name: 'productTmpl', foreach: products}"></div>
<script type="text/html" id="personTmpl">
Name: <span data-bind="text: name" />
Address: <span data-bind="text: address" />
</script>
<script type="text/html" id="productTmpl">
Description: <span data-bind="text: description" />
Category: <span data-bind="text: category" />
</script>
<script type="text/javascript">
$(function(){
var json = {
customers: [ { name: 'Joe', address: 'Denver, CO' } ],
products: [ { name: 'Skis', category: 'Outdoors' } ]
};
var vm = new CustomViewModel(json);
ko.applyBindings(vm);
});
</script>
[[ END HOME/INDEX.CSHTML ]]
What I'd like to do is store the personTmpl and productTmpl in their own html file and pull them into the view as needed. This would allow me to only have the content markup in my cshtml file. Plus it would enable me to use the templates from anywhere (ie. Customers\Index, Products\Show, Home\Index, etc..).
I would expect that it would require some custom js on each page, but I think this is a small price to pay for cleaning up the clutter and making my templates available to all views. I could see me using some server side tags on each page or something like this (merely an example):
#section templates {
#Content.Template("Person.tmpl", Url)
#Content.Template("Product.tmpl", Url)
}
<h1>Customers</h1>
<div data-bind="template: {name: 'personTmpl', foreach: customers}"></div>
<h1>Products</h1>
<div data-bind="template: {name: 'productTmpl', foreach: products}"></div>
<script type="text/javascript">
$(function(){
var json = #Html.Raw(Json.Encode(ViewBag.PageData)));
var vm = new CustomViewModel(json);
ko.applyBindings(vm);
});
</script>
With storing them into their own templates, I could even query the content dynamically for tooltips and dialogs using old fashion $.get('/tmpl/Person.tmpl', renderFunc).
Again, I don't mind creating my own implementation, but I'm convinced there is a solution out there already. Anybody?
Thanks in advance!!
My recommendation would be to look at the external template engine here: https://github.com/ifandelse/Knockout.js-External-Template-Engine
It allows you to place your templates in external files, reference them in your binding just as you normally would using the name parameter, and uses some conventions or configuration to determine the exact path to find the template file.
The external template engine is a pretty robust solution. I have also recently been using require.js with its text plugin for this purpose as well. More info in this answer: knockout.js loading templates at runtime
If you want to render them in-line, then I suppose a helper function could load the file and wrap it in a script tag with a non-JS type.
Related
The documentation on source binding has an aside which states:
Important: A single root element should be used in the template when
binding to an array. Having two first level DOM elements will result
in an erratic behavior.
However, I'm finding that this is the case even for non arrays.
I have the following HTML, which sets up two div's populated by two templates. The only difference is that the working template wraps that databound spans in a div.
<html>
<head>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://cdn.kendostatic.com/2013.3.1119/js/kendo.all.min.js"></script>
<title>JS Bin</title>
<script id="broken-template" type="text/x-kendo-template">
Foo: <span data-bind="text: foo"></span><br/>
Foo Again: <span data-bind="text: foo"></span>
</script>
<script id="working-template" type="text/x-kendo-template">
<div>
Foo: <span data-bind="text: foo"></span><br/>
Foo Again: <span data-bind="text: foo"></span>
</div>
</script>
</head>
<body>
<div id="broken-div" data-template="broken-template" data-bind="source: this">
</div>
<br/>
<br/>
<div id="working-div" data-template="working-template" data-bind="source: this">
</div>
</body>
</html>
And the JavaScript simply creates a view model with a single property and binds it to both divs:
var viewModel = kendo.observable({foo: "bar"});
kendo.bind($("#broken-div"), viewModel);
kendo.bind($("#working-div"), viewModel);
In both cases, only the first root element and it's children are being bound properly. This suggests that every time I databind to template with more than one element I need to make sure it is wrapped in a single root.
Is this behavior documented somewhere? Is there a bug in Kendo or in my sample code? An explanation for why Kendo requires a single root would be great to hear as well.
(Sample code as a jsfiddle)
It's not documented except in the one place you mentioned. Such is the state of Kendo UI documentation - it's less than complete. I've been using Kendo UI for three years and as far as I can tell you, this is its default behavior and not a bug. Unfortunately, it's one of the many quirks you simply learn (stumble upon) from experience.
I would like to know how to modify the example grouped listview that comes with kendo mobile.
The list view shows both flat view and grouped view. How do you make the items in the list view clickable so they will navigate to a different web page when clicked?
I've tried creating a template with an anchor tag and href and it works in IE but does nothing when clicked on the android phone.
//The Template
<script type="text/x-kendo-tmpl" id="template">
<div class="myclass">
#:name#
</div>
</script>
//The data binding
function mobileListViewDataBindInitGrouped() {
$("#grouped-listview").kendoMobileListView({
dataSource: kendo.data.DataSource.create({ data: groupedData, group: "letter" }),
template: kendo.template($("#template").html()),
fixedHeaders: true
});
}
Thanks
After some trial and error I have found that if i remove the div tag in the template, the anchor tags work correctly. Not sure if this is a bug or by design.
Thanks Whizkid747 for your help.
//Updated Template (removed <div>)
<script type="text/x-kendo-tmpl" id="template">
#:name#
</script>
I am struggling with declarative setting grid column to a external template
Here's my template
<script type="text/x-kendo-template" id="someTemplate">
<div>
<label> ${firstName}</label>
<label>${lastName}</label>
</div>
</script>
and here's the grid declaration
<div data-role="grid" data-bind="source: people" data-columns='[
{"field": "firstName",
"title": "Full Name",
"template": "kendo.template($("#someTemplate"))"
}
]'></div>
And here's JS Fiddle reproducing my problem:
JSFiddle repro
You have 2 mistakes in your code :
you have to make your template from the html of the script element
you have to call directly kendo.template(...) as it is a function and not between quotes.
This gives such code :
"template": kendo.template($("#someTemplate").html())
See this jsfiddle : http://jsfiddle.net/bSGdW/9/
After hours of expirements I've found out that ....
template: kendo.template($("\\#check-results-template").html())
so just watch out the '#' wherever use kendo things !!
I'm trying to create a pinterest pin it button.
Below is the code:
<img border="0" src="//assets.pinterest.com/images/PinExt.png" title="Pin It" />
<script type="text/javascript" src="//assets.pinterest.com/js/pinit.js"></script>
As you see there are three variables:
url : http%3A%2F%2Fwww.domain.name%2Fproduct%2F#Model.ProductDetails.URLName
media: http%3A%2F%2Fwww.domain.name%2Fproduct%2F#Model.ProductDetails.Image
description: #Model.ProductDetails.ProductDescription
In all three variables some data is coming from ViewModel using #Model
But only #Model.ProductDetails.ProductDescription is working
And the other two are not working maybe because they are part of a bigger string.
The razor parser thinks that the first two values are email addresses or similar and so leaves them as plain text. In order to inform the parser that they should be evaluated you need to use an Explicit Expression, eg #(Model.Property), so your variables would be:
url : http%3A%2F%2Fwww.domain.name%2Fproduct%2F#(Model.ProductDetails.URLName)
media: http%3A%2F%2Fwww.domain.name%2Fproduct%2F#(Model.ProductDetails.Image)
description: #(Model.ProductDetails.ProductDescription)
And your snippet would be:
<img border="0" src="//assets.pinterest.com/images/PinExt.png" title="Pin It" />
<script type="text/javascript" src="//assets.pinterest.com/js/pinit.js"></script>
I generally keep Phil Haack's Razor Quick Reference guide bookmarked!
There's a ton of info on the interwebs about how to handle dynamic views (via ajax calls) with Knockout, but is there a best practice for dynamic viewmodels?
For instance, say I have a single page app that renders (via ajax) different types of forms (with different input fields) based on role, user choices, contexts, etc. Not only would I use templates for each form, but I'd like to do the same for the viewmodel, since each viewmodel may have many very different properties and it wouldn't be practical to have one massive viewmodel for every possible template.
I'm a bit of a rookie with ko, and it may not be meant to be used in this fashion. Any advice is much appreciated.
A popular way to do this type of thing is to have a main view model that hosts sub-view models.
Here is a really basic example of defining "model" objects that have a template and associated data.
function Model(key, template, data) {
this.key = key;
this.template = ko.observable(template);
this.data = data;
}
var viewModel = {
models: ko.observableArray([
new Model("user", "userTmpl", { first: "Bob", last: "Smith" }),
new Model("item", "itemTmpl", { name: "MyItem", description: "Here are some details" })
]),
selectedModel: ko.observable()
};
ko.applyBindings(viewModel);
Then, you could use it like:
<select data-bind="options: models, optionsText: 'key', optionsCaption: 'select a model...', value: selectedModel"></select>
<hr />
<div data-bind="with: selectedModel">
<div data-bind="template: { name: template(), data: data }"></div>
</div>
<script id="userTmpl" type="text/html">
<span data-bind="text: last"></span>, <span data-bind="text: first"></span>
</script>
<script id="itemTmpl" type="text/html">
<h3 data-bind="text: name"></h3>
<div data-bind="text: description"></div>
</script>
http://jsfiddle.net/rniemeyer/29kWf/
Obviously, you wouldn't likely bind the selection of the model in a select, but it helps show how it can work. Rather than an array your models could be an object with the property names matching the key.
The "data" in the "model" objects would be your sub-view models.
I'm Facing the same problem.
Try Knockout Namespaces