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
Related
I heard the term 'thymeleaf' as view engine.
But I don't know where it's working in web application please clarify this.
They are more commonly called template engines, and provide tools to display the data contained in your Java objects into dinamically generated HTML.
A quick example so you understand what that means, you have a #Controller with this one mapping:
#GetMapping("/example")
public String getExample(Model model) {
List<ExampleDTO> list = exampleService.findAll();
model.addAttribute("list", list);
return "list-all";
}
ExampleDTO.java has two string attributes "name" and "label". And you have an HTML template (list-all.html) like this:
...
<div th:each="item : ${list}">
Name: <span th:text="${item.name}"></span><br>
Label: <span th:text="${item.label}"></span>
</div>
...
Here th:each and th:text are both evaluated by Thymeleaf and replaced with HTML. th:each will iterate through a collection and generate as many elements as collection.size() while th:text simply replaces the content of the tag with the attribute value of the Java object.
In the end the browser will receive an HTML like the following (imagine the list contained 3 items):
...
<div>
Name: <span>Item 1</span><br>
Label: <span>IT1</span>
</div>
<div>
Name: <span>Item 2</span><br>
Label: <span>IT2</span>
</div>
<div>
Name: <span>Item 3</span><br>
Label: <span>IT3</span>
</div>
...
Thymeleaf is very powerful, more info here.
I am very new to CanJs just started learning, I am struck with below problem not able to debug it.
I have model as below:
Localized = can.Model({
findOne : 'GET /resources/localized'
}, {
});
GET /resources/localized ---> fetches available localized languages.
And I have defined a component as below
can.Component({
tag : 'preferences',
template : initView,
init: function() {
console.log(locales);
},
scope : {
locales: new LocalizedModel.findOne({})
}
});
initView has mustache template as below:
<div class="form-group">
<label>{{dateLayout}}</label>
<select class="form-control" id="lang" name="lang" can-change="save">
{{#list locales.languageOptions}}
<option value="{{name}}">{{name}}</option>
{{/list}}
</select>
</div>
But the problem is locales are not getting populate, I could see network call for /resources/localized, any pointers here could really help to understand this.
Be careful to use the correct naming, especially if you were reading up on both EJS and Mustache building from the CanJS documentation. The helper that iterates over a list is {{#each listref}}...{{/each}} in can.Mustache and can.Stache, where in can.EJS it is <% list(listref, function(item) { %>...<% }) %>. Using {{#list ...}} will likely not produce anything.
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.
I'm using KnockoutJS and have a main view and view model. I want a dialog (the jQuery UI one) to popup with another view which a separate child view model to be bound to.
The HTML for the dialog content is retrieved using AJAX so I want to be able to call ko.applyBindings once the request has completed, and I want to bind the child view model to just the portion of the HTML loaded via ajax inside the dialog div.
Is this actually possible or do I need to load ALL my views and view models when the page initially loads and then call ko.applyBindings once?
ko.applyBindings accepts a second parameter that is a DOM element to use as the root.
This would let you do something like:
<div id="one">
<input data-bind="value: name" />
</div>
<div id="two">
<input data-bind="value: name" />
</div>
<script type="text/javascript">
var viewModelA = {
name: ko.observable("Bob")
};
var viewModelB = {
name: ko.observable("Ted")
};
ko.applyBindings(viewModelA, document.getElementById("one"));
ko.applyBindings(viewModelB, document.getElementById("two"));
</script>
So, you can use this technique to bind a viewModel to the dynamic content that you load into your dialog. Overall, you just want to be careful not to call applyBindings multiple times on the same elements, as you will get multiple event handlers attached.
While Niemeyer's answer is a more correct answer to the question, you could also do the following:
<div>
<input data-bind="value: VMA.name" />
</div>
<div>
<input data-bind="value: VMB.name" />
</div>
<script type="text/javascript">
var viewModels = {
VMA: {name: ko.observable("Bob")},
VMB: {name: ko.observable("Ted")}
};
ko.applyBindings(viewModels);
</script>
This means you don't have to specify the DOM element, and you can even bind multiple models to the same element, like this:
<div>
<input data-bind="value: VMA.name() + ' and ' + VMB.name()" />
</div>
I've managed to bind a custom model to an element at runtime. The code is here: http://jsfiddle.net/ZiglioNZ/tzD4T/457/
The interesting bit is that I apply the data-bind attribute to an element I didn't define:
var handle = slider.slider().find(".ui-slider-handle").first();
$(handle).attr("data-bind", "tooltip: viewModel.value");
ko.applyBindings(viewModel.value, $(handle)[0]);
You should look at the with binding, as well as controlsDescendantBindings http://knockoutjs.com/documentation/custom-bindings-controlling-descendant-bindings.html
I'm working with MVC 3 / Razor for the first time and it seems odd that all the examples and VS scaffolds for create and edit views all have separate HTML views for these concepts.
There is really not much difference between many Create/Edit forms so I was wondering why I can't find examples of people using a single Update form that can be used by both Create and Edit actions.
I have gotten an Update.cshtml view working but was wondering about how it talks to the Edit or Create action method on the controller.
My questions are:
Anyone have a quick answer to talking to the controller, or
Anyone know of a tutorial showing good practice for working this way, or
Is there some good reason for keeping the Create/Edit views separate when the HTML is often the same.
Cheers Dave
This (kind!) of question is asked before: ASP.NET MVC - using the same form to both create and edit
Basically you can create a partial view and include it on your Create and Edit view.
Scott Guthrie has a nice post about Partial Views.
(I've read about this somewhere, but can't find it, I'll update this post when I do find it)
Be mindful that answers to your question should also be driven by business need (and roles). The scaffolding does provide separate functionality, which in some cases is the preferred implementation.
CREATE and EDIT functionality is often pretty much identical from a technical (programming) perspective. This can lead a technical person to think that the functionality should be combined in order to implement a more efficient technical solution. However, any technical implementation must be in response to business need, which might require separation (e.g. by business role) of these concerns.
For example, a business may require that the role which CREATEs business objects is not the same one as EDITs them. In this case, the implemented web pages may not be seen by the same roles (and people) at all.
If you implement CREATE and EDIT using common functionality but the business need is for role separation, you must still implement "role checking" before rendering the required view/partial view/etc. In such cases, separate views can be a preferred implementation.
I do this. I don't know if it's best practice but it can be nice. There are some situations where a completely separate add/edit view could be useful though. Also, if you're using ViewModels then as far as I can tell you're stuck using the same ViewModel for both add and edit. In theory they should both have their own ViewModels.
Here's how this looks for me:
AddVideo.cshtml
#model Multimedia.MediaVideoViewModel
#{
Layout = "~/Views/Shared/LiveSubLayout.cshtml";
}
#section AdditionalHeadContent {
}
<div class="page-header">
<h1>Add a new video</h1>
</div>
<div id="add-video" class="row-fluid">
#Html.Partial("_VideoForm", Model, new ViewDataDictionary { { "ActionKeyword", "Add" } })
</div>
EditVideo.cshtml
#model Multimedia.MediaVideoViewModel
#{
Layout = "~/Views/Shared/LiveSubLayout.cshtml";
}
#section AdditionalHeadContent {
}
#if (ViewBag.Success)
{
<div class="alert alert-success">
<button class="close" data-dismiss="alert">×</button>
<h3><strong>Video saved!</strong></h3><br/>
<div class="btn-group">
Preview this video
#Html.ActionLink("Add Another Video", "AddVideo", "Multimedia", new { Model.Id }, new { #class = "btn" })
#Html.ActionLink("View all media", "Index", "Multimedia", null, new { #class = "btn" })
</div>
<p>or continue editing below...</p>
</div>
}
<div class="page-header">
<h1>Edit video <small>#Model.Title</small></h1>
</div>
<div id="edit-video" class="row-fluid">
#Html.Partial("_VideoForm", Model, new ViewDataDictionary { { "ActionKeyword", "Edit" } })
</div>
_VideoForm.cshtml (partial)
#model Multimedia.MediaVideoViewModel
#{
string actionKeyword = ViewData["ActionKeyword"].ToString();
}
<div class="span6">
#using (Html.BeginForm("editvideo", "multimedia"))
{
<label class="control-label" id="embed-url">Paste video URL here:</label>
<div class="control-group">
#Html.TextBoxFor(model => model.EmbedUrl, new { #class = "span12", id = "video-url", placeholder = "ex: http://www.youtube.com/watch?v=PoAGasPLh30" })
<button class="btn disabled" id="get-video" title="Tooltip">Get Video</button>
</div>
<div class="video-meta">
<h3>Video Information</h3>
<label class="control-label">Title:</label>
<div class="control-group">
#Html.TextBoxFor(model => model.Title, new { #class = "span12", id = "video-title" })
#Html.ValidationMessageFor(model => model.Title, "A title is required", new { #class = "label label-important" })
</div>
<label class="control-label">Description:</label>
<div class="control-group">
#Html.TextAreaFor(model => model.Description, new { #class = "span12", id = "video-description" })
</div>
<h3>Categories</h3>
<div id="tag-search" class="well">
<label class="control-label">Search tags:</label>
<div class="controls"><input type="text" class="typeahead" /></div>
#if (Model != null)
{
foreach (var category in Model.Tags)
{
#Html.Partial("_TagFragment", category)
}
}
</div>
<hr />
#Html.HiddenFor(model => model.Id)
#Html.HiddenFor(model => model.ThumbnailUrl, new { id = "thumb-url" })
<input type="submit" id="video-submit" name="video-submit" class="btn-large btn-primary" value="#actionKeyword video" />
</div>
}
</div>
I edited these down a bit so something might be missing but this should give you the general idea.
here's how i do it, it's not always the best practice (it depends on the case)
1/ combine the controller actions for create and edit
public PartialViewResult Creedit(string id = null)
{
if (id == null)
{
// Create new record (this is the view in Create mode)
return PartialView();
}
else
{
// Edit record (view in Edit mode)
Client x = db.ClientSet.Find(id);
if (x == null) { return PartialView("_error"); }
// ...
return PartialView(x);
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Creedit(Client x)
{
if (x.id == null)
{
// insert new record
}
else
{
// update record
}
}
2/ combine the edit and create views into one view i called Creedit
// if you need to display something unique to a create view
// just check if the Model is null
#if(Model==null){
}
so i have 1 view and 2 actions (1 post and 1 get) instead of 2 views and 4 action.
Look into MVC scaffolding in nuget as well, when it generates the view files it does so explicitly creating a creatandedit partial and having the create page and edit page use that partial.