Search code examples
loopsforeachknockout.jsnested-loopsknockout-mvc

knockoutjs data-bind not working in inner loop


Case 1: -- Working

HTML

Test::<span data-bind="text: $parent.testText"></span>
<button  data-bind="click: $parent.testbtn"></button>

JS

numberOfClicks : ko.observable(0),
incrementClickCounter : function() {
 alert('click called..');
 var previousCount = this.numberOfClicks();
 this.numberOfClicks("Testtttt....");
}

Case 2: -- Working

HTML

<!-- ko foreach: { data: loop1(), as: 'styleColor'} -->
 Test::<span data-bind="text: $parent.testText"></span>
 <button  data-bind="click: $parent.testbtn"></button>
<!-- /ko -->

Case 3: -- Not working

HTML

<!-- ko foreach: { data: loop1(), as: 'styleColor'} -->
 <div class="standard-color" data-bind="foreach: $parent.loop2()">                    
     Test::<span data-bind="text: $parent.testText"></span>
     <button  data-bind="click: $parent.testbtn"></button>
 </div>
<!-- /ko -->

When I click on button, js function not calling.


Solution

  • You should be able to nest multiple foreach loops, even if they get their data from a parent layer. You have to realize that each of those loops creates a new layer in the binding context. Getting the right $parent can become a tricky exercise...

    My advice is to move some of the looping logic to your viewmodel.

    Here's an example that does a nested loop:

    (function() {
      const colors = ["red", "yellow", "blue"];
      const styles = ["glossy", "matte"];
    
      const selectedColor = ko.observable("red");
      const selectedStyle = ko.observable("glossy");
    
      const select = (color, style) => {
        selectedColor(color);
        selectedStyle(style);
      }
    
      ko.applyBindings(
        { colors, styles, selectedColor, selectedStyle, select }
      );
    }());
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    
    <h1>Your selection: <span data-bind="text: selectedColor"></span>, <span data-bind="text: selectedStyle"></span></h1>
    
    <ul data-bind="foreach: { data: colors, as: 'color' }">
      <!-- ko foreach: { data: $parent.styles, as: 'style' } -->
      <li data-bind="
        click: $parents[1].select.bind(null, color, style),
        text: color + ', ' + style"></li>
      <!-- /ko -->
    </ul>

    Here's an example that moves some logic to JS:

    (function() {
      const colors = ["red", "yellow", "blue"];
      const styles = ["glossy", "matte"];
    
      const options = colors.flatMap(color =>
        styles.map(style => ({ color, style, label: `${color}, ${style}` }))
      )
    
      const selectedOption = ko.observable(options[0]);
    
      ko.applyBindings(
        { options, selectedOption }
      );
    }());
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    
    <h1 data-bind="with: selectedOption">
      Your selection: <span data-bind="text: label"></span>
    </h1>
    
    <ul data-bind="foreach: options">
      <li data-bind="
        click: $parent.selectedOption,
        text: label"></li>
    </ul>