简体   繁体   中英

KnockoutJS foreach binding: cannot apply bindings multiple times to same element

While I have seen this question posed on numerous occasions, I have yet to come across a satisfactory answer as it relates to my simple situation.

Quite simply, I am using knockoutjs in my view and I would like to populate a list with data which is to be updated periodically.

I cannot seem to reconcile when the bindings should be applied. At first I thought that it would be enough to just apply bindings on window load but my list fails to update as it's unclear how to affectuate the change in the view. If I attempt to invoke a binding again, I get the multiple bindings error. I guess the key is I don't understand how the view is supposed to update automatically.

I have recreated what I am trying to do below. In the resulting html form, I have a clickable button which is meant to display 2 different arrays when clicked but the second does (array: ['Tic','Toc']) does not appear.

Here is my sample html:

<!DOCTYPE html>
<html>

<head>
  <title>Knockout Test</title>
</head>

<body>

  <div>
    <p>Stuff Here:</p>
    <ul id = "junk" data-bind="foreach: myList">
       <li>
        <span data-bind="text: $data"></span>
       </li>
    </ul>
  </div>

  <div class="row" id="launch">
    <button type="submit" id="go">GO</button>
  </div>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
  <script type="text/javascript" src="../static/assets/js/kotest.js"></script> 

</body>

</html>

and here is my JS:

var data = ['Ping','Pong'];
var go = document.getElementById('go');
var cnt = 0;

function prepData(queryResults) {
    listings = queryResults;
    // line below gives me an error on 2nd try - but how should this work otherwise?
    ko.applyBindings(new viewModel(), document.getElementById("junk")); 
}

function viewModel() {
  var self = this;  
  self.myList = ko.observableArray(listings);  
}

go.addEventListener('click',function() {   
   if ( cnt > 0 ) {
      data = ['Tic','Toc'];
   }   
   cnt++;   
   console.log("data is: "+data+"...");
   prepData(data); // would prefer to call: viewModel(data) instead but not sure how this would work(?)
});

I have a fiddle here which illustrates only the first array is displayed:

https://jsfiddle.net/devEngine/fkxgk7rc/1/

Can anyone show me what changes I would need to make to have my view properly update with the second data array, please? Any help would be much appreciated.

Heres your model, as you can see I got rid of the event listener, it can be done in knockout more efficiently for this scenario, better to keep logic in one place.

var data = ['Ping','Pong'];
function viewModel() {
  var self = this;  
  self.myList = ko.observableArray(data); 

  self.addToList = function (listData) {
     for (var i = listData.length; i-- > 0;)
        self.myList.push(listData[i]);
  }

  self.replaceList = function (listData) {
     self.myList(listData);
  }
}

Global Variables are key:

var viewModel = new viewModel();
ko.applyBindings(viewModel, document.getElementById("body")); 

HTML for buttons:

<div class="row" id="launch">
   <button data-bind="click: addToList.bind($data, ['Tic','Toc'])">Add</button>
   <button data-bind="click: replaceList.bind($data, ['Tic','Toc'])">Replace</button>
</div>

Working Fiddle https://jsfiddle.net/fkxgk7rc/3/

First off, you need to preserve a handle to your viewModel within a scope that is available to prepData . Then, all you have to do is viewModel.myList.removeAll() to clear out the observable array and fill it back up with whatever data is.

Once the view model is set in the UI, you must manipulate it (or give it funcs that you can call so it can update itself). That's the whole point of having observables--they change, and the UI changes as a result.

If you create an instance of your view model and then promptly let it go, you cannot manipulate it afterwards.


var data = ['Ping','Pong'];
var go = document.getElementById('go');
var cnt = 0;
var viewModel = new viewModel();
ko.applyBindings(viewModel, document.getElementById("junk")); 
// snip
go.addEventListener('click',function() {   
   if ( cnt > 0 ) {
      data = ['Tic','Toc'];
   }   
   cnt++;   
   console.log("data is: "+data+"...");
   viewModel.myList.remove();
   for(var i = 0; i < data.length; i++)
       viewModel.myList.push(data[i]);
});

You can fix the error if add ko.cleanNode(...) before ko.applyBindings(...) , but it is bad way

answer of @tyler_mitchell is good solution https://stackoverflow.com/a/45080213/4433565

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM