I've got a jQuery Accordion and each panel contains a form. All of the forms are the same, and the inputs share the same IDs, names, and data-bind attributes.
Assuming each form has a different binding context (using ko with:), this is how I would set up the Knockout.js ViewModel if there were two forms.
However, I don't know in advance how many forms there will be. I'm rendering a PartialView (which contains the form) for every form object in the MVC ViewModel's collection of forms.
#model ViewModel
<div class="container-fluid">
<div id="jQueryAccordion">
#foreach (var form in Model.AllForms.ToList())
{
<!-- ko with: items[#form.Key] -->
Html.RenderPartial("_Form", form);
<!-- /ko -->
}
// etc.
How would I set up the Knockout.js ViewModel if I don't know how many forms there are going to be?
I suggest that you can load your partial view dinamically via ajax call & bind data knockout binding accordingly as follows:
//C# XxxController: return partial view:
public ActionResult MyView()
{
return PartialView("_MyView");
}
//Ajax call to load partial view at client side:
$.get('Xxx/MyView', function(view){
_contentHolder.html(view);
ko.applyBinding(self, _contentHolder[0]);
})
You can loop through your model collection and apply knockout binding dynamically.
As Anh Bui suggested, I would create them dynamically in the browser. Using applyBindings with markup you created with ASP.net on the server-side is a bit of a hack and means your working against Knockout, not with it.
It would be better to let Knockout take care of actually creating the forms. This means
supplying it only the data it needs to create each form as JSON
creating a Knockout template for the form markup
looping over the data with the forEach binding
The template:
<script type="text/html" id="form-template">
<form action="/target-url">
<label for="user_name">What's your name?</label>
<input type="text" data-bind="value: user_name" name="user_name" />
<label for="user_location">Where are you from?</label>
<input type="text" data-bind="value: user_location" name="user_location" />
</form>
</script>
Next you output your relevant form data as a JSON array on the server side. I haven't used ASP.net, so I can only offer you pseudo-code here:
<script type="application/javascript">
window.form_json_from_server = "
#foreach (var form in Model.AllForms.ToList())
{
// .. ASP.net JSON output magic goes here
}
";
</script>
so that the end result in your markup looks like
<script type="application/javascript">
window.form_json_from_server = "[
{ user_name: "Foo1", user_location: "Bar1" },
{ user_name: "Foo2", user_location: "Bar2" },
{ user_name: "Foo3", user_location: "Bar3" }
]";
</script>
(note that JS strings cannot contain line breaks. I've formatted it here with line breaks for easier reading)
Now we have our form data formatted as JSON, saved in a Javascript string. Next up: your Knockout view model:
var ViewModel = function ViewModel() {
var that = this,
raw_forms_object;
// we reconstitute our JSON string into a Javascript object
raw_forms_object = JSON.parse(window.form_json_from_server);
// this is where the objects made from our JSON will end up in
this.forms = ko.observableArray([]);
ko.utils.arrayForEach(raw_forms_object, function(f) {
// f contains one of our form objects, such as { user_name: "Foo1", user_location: "Bar1" }
// instead of adding f directly to the array, we make a new object in which the
// properties are observables
var form = {
user_name: ko.observable(f.user_name),
user_location: ko.observable(f.user_location),
};
// add our new form object to our observableArray
// make sure to use 'that', because 'this' is the scope of the arrayForEach callback we're in
that.forms.push(form);
});
}
Now we have an observableArray called 'forms' on our view model with our form objects in it. We use the forEach binding to make as many forms as we have form objects:
<div data-bind="template: { name: 'form-template', foreach: forms }"></div>
All that's left is applying an instance of our view model to the page:
ko.applyBindings( new ViewModel() );
If you like, you can try it out in this runnable code snippet:
var ViewModel = function ViewModel() {
var that = this,
raw_forms_object;
// we reconstitute our JSON string into a Javascript object
raw_forms_object = JSON.parse(window.form_json_from_server);
// this is where the objects made from our JSON will end up in
this.forms = ko.observableArray([]);
ko.utils.arrayForEach(raw_forms_object, function(f) {
// f contains one of our form objects, such as
// { user_name: "Foo1", user_location: "Bar1" }
// instead of adding f directly to the array, we make a new object in which the
// properties are observables
var form = {
user_name: ko.observable(f.user_name),
user_location: ko.observable(f.user_location),
};
// add our new form object to our observableArray
// make sure to use 'that', because 'this' is the scope
// of the arrayForEach callback we're in
that.forms.push(form);
});
}
ko.applyBindings( new ViewModel() );
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script type="text/html" id="form-template">
<form action="/target-url">
<label for="user_name">What's your name?</label>
<input type="text" data-bind="value: user_name" name="user_name" />
<label for="user_location">Where are you from?</label>
<input type="text" data-bind="value: user_location" name="user_location" />
</form>
</script>
<div data-bind="template: { name: 'form-template', foreach: forms }"></div>
<script type="application/javascript">
window.form_json_from_server = '[{"user_name": "Foo1","user_location": "Bar1"},{"user_name": "Foo2","user_location": "Bar2"},{"user_name": "Foo3","user_location": "Bar3"}]';
</script>
Related
I got knockout to work before in MVC, but unfortunately I lost the code, and need help figuring it out.
I am trying to simply put an html page in the ~/wwwsource/ folder of my MVC project, and in that page I would like to demo a simple knockout example.
(Eventually, I actually want to use knockout inside MVC Views, using knockout right alongside Razor if possible but first I just would at least like to get a simple working example going, and extend from there.
I tried the following, which worked in JSFiddle but not in Visual Studio:
<script src="lib/knockout/dist/knockout.debug.js" type="text/javascript">
// Here's my data model
var ViewModel = function (first, last) {
this.firstName = ko.observable(first);
this.lastName = ko.observable(last);
this.fullName = ko.pureComputed(function () {
// Knockout tracks dependencies automatically. It knows that fullName depends on firstName and lastName, because these get called when evaluating fullName.
return this.firstName() + " " + this.lastName();
}, this);
};
ko.applyBindings(new ViewModel("Planet", "Earth"));
</script>
<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>
<h2>Hello, <span data-bind="text: fullName"> </span>!</h2>
You are calling the javascript before the html has been fully rendered. So when ko.applyBindingsis called the html as only partially loaded.
Easiest solution is to wrap the javascript in a document loaded callback using jQuery (which should exist because you're using knockout).
You also have some invalid script tag syntax. Need to close knockout script tag before starting a new one for the page.
<script src="lib/knockout/dist/knockout.debug.js" type="text/javascript">
</script>
<script type="text/javascript">
// Here's my data model
var ViewModel = function (first, last) {
this.firstName = ko.observable(first);
this.lastName = ko.observable(last);
this.fullName = ko.pureComputed(function () {
// Knockout tracks dependencies automatically. It knows that fullName depends on firstName and lastName, because these get called when evaluating fullName.
return this.firstName() + " " + this.lastName();
}, this);
};
$(document).ready(function(){
ko.applyBindings(new ViewModel("Planet", "Earth"));
})
</script>
<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>
<h2>Hello, <span data-bind="text: fullName"> </span>!</h2>
I am new to knockoutJS. I am working on an MVC application where I want to implement knockoutJS but the scenario is bit different.
I have a page where I am showing a list. I have 3 links on the page and on click of them I am adding partial views to page accordingly. What I want to do is that whenever I add values/data to partial views, the list which is on page should be updated with knockout. In other words I want to add value to observable array when I save data from partial view.
Please let me know if this is possible or I should keep it in jquery only.
Here is the code:
Main view:
<input type="button" value="Add Partial View" onclick="LoadPartial();" />
<div id="dvContent"></div>
<h4>People</h4>
<ul data-bind="foreach: people">
<li>
Name at position <span data-bind="text: $index"> </span>:
<span data-bind="text: name"> </span>
Remove
</li>
</ul>
<button data-bind="click: addPerson">Add</button>
<script src="~/Scripts/jquery-1.7.1.js"></script>
<script src="~/Scripts/knockout-2.1.0.js"></script>
<script>
function LoadPartial() {
$.ajax({
url: "/home/index",
dataType:"html",
type: "GET",
success: function (data) {
$("#dvContent").html(data);
}
});
}
</script>
<script>
function AppViewModel() {
var self = this;
self.people = ko.observableArray([
{ name: 'Bert' },
{ name: 'Charles' },
{ name: 'Denise' }
]);
self.addPerson = function () {
self.people.push({ name: "New at " + new Date() });
};
self.removePerson = function () {
self.people.remove(this);
}
}
ko.applyBindings(new AppViewModel());
</script>
Partial View:
<table>
<tr>
<td>Add new Row</td>
<td><input type="button" value="Add" data-bind="click: addPerson"/></td>
</tr>
</table>
Thanks,
JsHunjan
It is easy to accomplish with Knockout. You need to show some code that you have tried though if you want to get some help. I will post a general answer but it isn't going to fix your use case exactly, just basically -
Create an object to hold your new item, you can do this either in the parent or the child view model, but if you do it in the child you need to pass it back to the parent.
Once you hit a save button or add or whatever in the child view model just do a .push() into the observableArray that you created ex... - myObservableArray.push(newItem());
Knockout will recognize all of the changes taking place and perform the actions you want automatically.
Hope this helps.
I'm new to ASP.NET MVC SPA and Knockout.js os maybe it's a simple mistake I made...
Situation: I have two partialviews in my website and I want that every partialview has his own Knockout ViewModel so I won't get a huge ViewModel.
My current ViewModel:
/// <reference path="../_references.js" />
function MobileDeliveriesViewModel() {
var self = this;
// Data
self.currentDelivery = ko.observable();
self.nav = new NavHistory({
params: { view: 'deliveries', deliveryId: null }
});
// Test
self.foo = "FooBar"
self.bar = "BarFoo"
self.nav.initialize({ linkToUrl: true });
// Navigate Operations
self.showDeliveries = function () { self.nav.navigate({ view: 'deliveries' }) }
self.showCustomers = function () { self.nav.navigate({ view: 'customers' }) }
}
function BarFooViewModel() {
var self = this
//MobileDeliveriesViewModel.call(self)
self.bar2 = "BarFooTwo"
}
ko.applyBindings(new MobileDeliveriesViewModel());
ko.applyBindings(new MobileDeliveriesViewModel(), $('#BarFoo')[0]);
ko.applyBindings(new BarFooViewModel(), document.getElementById('BarFoo'));
My Index.cshtml:
<div data-bind="if: nav.params().view == 'deliveries'">
#Html.Partial("_DeliveriesList")
</div>
<div class="BarFoo" data-bind="if: nav.params().view == 'customers'">
#Html.Partial("_CustomersList")
</div>
<script src="~/Scripts/App/DeliveriesViewModel.js" type="text/javascript"></script>
My CustomerPartialView:
<div id="BarFoo" class="content">
<p data-bind="text: bar"></p>
<p data-bind="text: bar2"></p>
<button data-bind="click: showDeliveries, css: { active: nav.params().view == 'deliveries' }">Deliveries</button>
</div>
My DeliveriesPartialView:
<div class="content">
<p data-bind="text: foo"></p>
<button data-bind="click: showCustomers, css: { active: nav.params().view == 'customers' }">Customers</button>
</div>
If I run this, it won't recognize the bar2 propertie in my BarFooViewModel...
I have tried 2 different applyBindings at the end of my ViewModel.
Anybody got an idea or is their a better way/pattern to do this?
are there JS errors on page?
nav.params().view
but params: { view: 'deliveries', deliveryId: null } - it's not function.
and if you want use a few view models on single page - check this http://www.knockmeout.net/2012/05/quick-tip-skip-binding.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+KnockMeOut+%28Knock+Me+Out%29 acticle. you have to use "stopBinding"
It looks like you are applying multiple data bindings to the same sections.
ko.applyBindings(new MobileDeliveriesViewModel();
This will bind to all elements one the page.
ko.applyBindings(new MobileDeliveriesViewModel(), $('#BarFoo')[0]);
this will try to bind to all elements inside the div
ko.applyBindings(new BarFooViewModel(), document.getElementById('BarFoo'));
This will also try to bind to all elements inside the div.
To keep things simple, you should try to bind a single view model to a single html section. I've found that trying to bind two view models in the same html section has been hard to get work correctly and trouble shoot.
Jack128's answer also makes some good points.
I am attempting to utilise KnockoutJS and MVC 4 in order to display a table with ActionLink definitions in the first column of the table. Displaying the data itself is extremely straight-forward and I'm not having any problem there. The problem I have is in the generation of the ActionLink's.
I have taken a look at Use MVC helpers inside jquery.tmpl templates, but the solution there does not utilise knockout templates and inserting the Url into the model object is not feasible (the app domain model objects used to create the view model will be used extensively through out the application).
The table definition:
<table>
<tbody data-bind="template: { name: 'dmuTableDetail', foreach: tables() }"></tbody>
</table>
(tables is an observable array, hence the parens).
The knockout template definition:
<script id="dmuTableDetail" type="text/html">
<tr>
<td>#Html.ActionLink("Details", "Details", "DMUTableCategory", new { #Id = ??? } )</td>
<td data-bind="text:TableId"></td>
<td data-bind="text:TableName"></td>
</tr>
</script>
The View Model definition:
var PageViewModel = function () {
self = this;
self.tables = ko.observableArray([]);
self.readItems = function () {
self.tables(jQuery.parseJSON('[{"TableId":1001, "TableName":"Table#1"},{"TableId":1002, "TableName":"Table#2"}]'));
}
}
$(document).ready(function () {
vm = new PageViewModel();
self.readItems('');
ko.applyBindings(vm);
});
(the actual code performs an Ajax call to retrieve the data, but the code above also demonstrates the issue).
Regardless of what I replace the ??? with, I am unable to get the value of the TableId field to be inserted into the href.
Any help would be greatly appreciated.
Thankyou.
Thankyou Eric, you got me thinking about an anchor element and binding the href attribute.
It seems the answer is a little easier than expected (it usually is!).
The table definition: (same as original question)
<table>
<tbody data-bind="template: { name: 'dmuTableDetail', foreach: tables() }"></tbody>
</table>
The knockout template definition: (change to the binding of the href attribute).
<script id="dmuTableDetail" type="text/html">
<tr>
<td><a data-bind="attr: { 'href': '#Url.Action("Details", new RouteValueDictionary() { { "Controller", "DMUTableCategory" } } )/' + TableId }">Details</a></td>
<td data-bind="text:TableId"></td>
<td data-bind="text:TableName"></td>
</tr>
</script>?
The View Model definition: (same as original question)
var PageViewModel = function () {
self = this;
self.tables = ko.observableArray([]);
self.readItems = function () {
self.tables(jQuery.parseJSON('[{"TableId":1001, "TableName":"Table#1"},{"TableId":1002, "TableName":"Table#2"}]'));
}
}
$(document).ready(function () {
vm = new PageViewModel();
self.readItems('');
ko.applyBindings(vm);
});
You dont actually need to RootValueDictionary but I've included it so people can see how to change the controller the request is sent to.
Knockout binds completely on the client side, which is after MVC has rendered the HTML for your page and sent it back to the original browser.
If you want your Knockout template to be able to use a URL that is generated on the server, then you'll have to employ some clever strategy similar to the following:
CSHTML:
#{
// create a dummy URL that you can use as a template
string hrefFormat = Url.Action("Details", "DMUTableCategory", new { id = "{ID}" });
}
<script type="javascript">
// a global string (but you can put it where ever you need to)
var _hrefFormat = #Html.Raw(hrefFormat)
<script>
JS:
self.readItems = function () {
self.tables(jQuery.parseJSON('[{"TableId":1001, "TableName":"Table#1"},{"TableId":1002, "TableName":"Table#2"}]'));
// loop through the 'tables' and add a new 'href' property to each for binding
ko.utils.arrayForEach(self.tables(), function(table){
table.href = _hrefFormat.replace("{ID}", table.TableId);
});
}
Your KO Tmpl where you bind the 'href' property of each table object to the a tag's href attribute:
<script id="dmuTableDetail" type="text/html">
<tr>
<td><a data-bind="attr: { 'href': href }">Details</a></td>
<td data-bind="text:TableId"></td>
<td data-bind="text:TableName"></td>
</tr>
</script>
I'm fetching an array of viewmodels from my controller using jquery+json. I then build a form where each row in a table represents one viewmodel.
My question is: How should I name each form element so that I can get it to my controller action like this:
public ActionResult Update(MyViewModel[] models)
{
}
Edit: I'm using jquery-tmpl to generate the form, and I'm also trying to figure out how to get an index variable in it (if that's needed for the form generation).
I managed to get it working.
My jquery template:
<script id="wagonTemplate" type="text/x-jquery-tmpl">
<tr>
<td>
<input type="checkbox" value="true" class="wagoncheck" name="wagons[${$item.getIndex()}].IsSelected" />
</td>
<td>
<input type="text" name="wagons[${$item.getIndex()}].WagonId" value="${WagonId}" style="width:120px" />
</td>
<td>
<input type="text" name="wagons[${$item.getIndex()}].WagonNumber" value="${WagonNumber}" style="width:20px" />
</td>
</script>
Method that loads the template:
function loadWagons(trainId, partId) {
$.getJSON('/train/wagons/' + escape(trainId) + '?partNo=' + partId, function (data) {
$wagons = $('#wagons tbody');
$wagons.empty();
// the function used in the template to get an index.
var tmplOptions = {
getIndex: function getIndex() {
return $.inArray(this.data, data);
}
};
$("#wagonTemplate").tmpl(data, tmplOptions).appendTo($wagons);
});
}
In other words:
To get a YourModel[] items argument in your controller action you need to name the items as items[0].MyProperty' where0` should correspond to the index in the array.
To get an index in a jquery template, just use pass a method in the options to the template function. I'm using a slightly modified version of the answer found here. Passing the item as done in that answer is not necessary as this points on the current item.