Reputation: 7471
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?
Upvotes: 3
Views: 3101
Reputation: 10328
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
forEach
bindingThe 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>
Upvotes: 2
Reputation: 293
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.
Upvotes: 3