2
votes

I have an ASP.NET MVC 4 View that uses KnockoutJs (version 2.3.0). The page loads just fine and any Existing elements with the data-bind attribute work as expected with KnockoutJs. The problem is that if I add HTML to the page that contains a data-bind for an observable already contained within the ViewModel that has been bound, it seems like it isn't subscribed even though it has the correct data-bind attribute. The HTML being added via Ajax is a MVC PartialView.

HTML (Starting)

<input type="text" id="FullName" data-bind="value: FullName" />    
<input type="text" id="Units" data-bind="value: Price" />
<input type="text" id="Price" data-bind="value: Units" />

<div id="AdditionalData"></div>

KO ViewModel

var ViewModel = function() {
    var self = this;
    self.FullName = ko.observable('Bob');
    self.Units = ko.observable(@Model.Units);
    self.Price = ko.observable(@Model.Price);
    self.SomeValue = ko.observable();
    self.CalculatedCost = ko.computed(function() {
       return self.Units * self.Price;
    };
};

ko.applyBindings(new ViewModel());

AJAX (within a .js file)

APP.GetPartialView = function () {
    var _formValues = $("form#MyForm").serializeArray();
    var _url = $Url.resolve("~/Shared/_PartialViewName/?" + $.param(_formValues));
        function _success(html) {
            $("#AdditionalData").html(html);
        };

    $.ajax({
        url: _url,
        data: _formValues,
        cache: false,
        dataType: "html",
        success: _success
     });
};

MVC Controller: PartialViewResult

[HttpGet]
public virtual PartialViewResult _PartialViewName(AccountViewModel model)
{
    return PartialView(model);
}  

MVC PartialView HTML

@model APP.AccountViewModel

<fieldset>
    @Html.Hidden("SomeValue", new { data_bind="value: SomeValue" })
    <ul>
       <li>
           @Html.LabelFor(m => m.FullName)
           <span data-bind="text: FullName"></span>
       </li>
       <li>
           @Html.LabelFor(m => m.CalculatedCost)
           <span data-bind="text: CalculatedCost"></span>
       </li>
    </ul>
</fieldset>

HTML (After Ajax Call)

<input type="text" id="FullName" data-bind="value: FullName" />    
<input type="text" id="Units" data-bind="value: Price" />
<input type="text" id="Price" data-bind="value: Units" />

<div id="AdditionalData">
    <fieldset>
        <label for="SomeValue" data_bind="value: SomeValue" />
        <input type="hidden" id="SomeValue" name="SomeValue" data_bind="value: SomeValue" />
        <ul>
           <li>
               <label for="FullName" />
               <span data-bind="text: FullName"></span>
           </li>
           <li>
               <label for="CalculatedCost" />
               <span data-bind="text: CalculatedCost"></span>
          </li>
        </ul>
    </fieldset>
</div>

The newly added html will not receive the text for the FullName and the ko computed field for Calculated Cost will not write the value for the observable of the same name.

So, the question is how to tell Knockout that these elements are just "late to the party" but have the correct data-bind credentials for ViewModel observables that are already bound?

Updated: I have updated the code references above for the question.

1
Why not just apply bindings again ? Generally speaking, you should use templates and add this new markup using ko bindings. github.com/ifandelse/Knockout.js-External-Template-Enginevittore
@vittore It is my understanding that you can only call ko.applyBindings() once per DOM load. I have tried that and it throws the error stating you can't do that more than once. Regarding the templates: it seems that the templates are geared more for repeating data. The example above is a simplified version of what I'm trying to accomplish, but I do need to pass model data from the Controller to the PartialView Result and not quite sure how to reconstruct this in a KO template fashion. It just seems that a "rebind" is all that 'should' be needed opposed to templates, but taking suggestions.piercove

1 Answers

1
votes

when knockout comes in append, next, innerHTml all these stuff go out of the window. The problem is you are going about it the wrong way. You want to use knockout to getaway from such fragment injections.

View Model above is your model:

    // This is the model
var Person = function(data) {
    var self = this;
    self.FullName = ko.observable(data.FullName);
    self.Address = ko.observable(data.Address);
};

// Your viewModel now uses Person to create People:
var ViewModel = function(){
    var self = this;
    self.People = ko.observableArray()
    self.addPeople = function(data){
        for( i = 0; i < data.length; i++ ){
            self.People.push(new Person(data[i]));
        }
    };
    self.addSam = function(){
        self.People.push(new Person({"FullName" : "Sam", "Address" : "Some address"}));
    };
    self.AddNewPerson = function(data){
        self.People.push(new Person(data));
    }
}

// Now I can create a new instance of VM:
var vm = new ViewModel();
vm.addPeople([{"FullName" : "Jon", "Address" : "Some address"},{"FullName" : "Pete", "Address" : "Some address"}]);

ko.applyBindings(vm);

vm.AddNewPerson({"FullName" : "Marry", "Address" : "Some address"})

You can add new elements simply by calling internal methods or call them outside somewhere else in your code

<ul data-bind="foreach: People">
    <li data-bind="text: FullName"></li>
</ul>

<a data-bind="click: addSam" href="#">Click me to add Sam</a>