Binding to multiple PartialViews in knockout

enter image description here

I have a jQuery accordion and each panel contains a form. All forms are the same, and the inputs have the same identifiers, names, and attributes data-bind

.

Assuming each form has a different binding context (using ko with:

), I would set the Knockout.js ViewModel if there were two forms.

However, I don't know in advance how many forms there will be. I am presenting a PartialView (which contains a form) for each form object in the MVC ViewModel 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 can I customize the Knockout.js ViewModel if I don't know how many forms there will be?

+3


source to share


2 answers


As An Bui suggested, I would create them dynamically in the browser. Using applyBindings

with markup generated with ASP.net on the server side is a bit of a hack and means you're working against the knockout, not with it.

It would be better if Knockout took care of creating the forms. It means

  • giving it only the data it needs to create each form as JSON
  • creating a knockout template for form markup
  • data concatenation with binding forEach

Template:

<script type="text/html" id="form-template">
  <form action="/target-url">
    <label for="user_name">What 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>

      

Then you output the data of the corresponding form as a JSON array on the server side. I have not used ASP.net, so I can only offer you pseudocode:

<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 lines cannot contain line breaks. I have formatted it here with line breaks for easier reading)



We now have the form data, formatted as JSON, stored in a Javascript string. Next: your knockout presentation 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);
  });
}

      

We now have an observable array called "shapes" on our view model with our shape objects. We use anchoring forEach

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 in this 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 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>
      

Run codeHide result


+2


source


I suggest that you can load your partial view dynamically with an ajax call and bind the data binding to the knockout like this:

//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 collection of models and apply knockout binding dynamically.

+3


source







All Articles