Search code examples
javascriptknockout.jsknockout-3.0

Equivalent for optionsAfterRender on a foreach binding in knockout


In Knockout, the "options" binding has an optional argument `optionsAfterRender which is used to modify the individual option tags as they are inserted. You can find more details in the documentation.

For one of my select lists, I am having to use a foreach binding instead, because it requires a two-level binding using optgroup.

<select  data-bind="foreach: { data: availableData,
                              value: editDataId">
  <optgroup data-bind="attr: {label: groupTitle}, foreach: groups">
    <option data-bind="value: id, attr: {title: name }"></option>
  </optgroup>
</select>

However, I would also like to modify these options after binding.

There is an afterRender option for foreach, but it seems to behave very differently than optionsAfterRender. I was able to get it to execute against all the options in the select list as follows:

<select  data-bind="foreach: { data: availableData,
                              value: editDataId,
                              afterRender: doStuff">
  <optgroup data-bind="attr: {label: groupTitle}, foreach: groups">
    <option data-bind="value: id, attr: {title: name }"></option>
  </optgroup>
</select>

And then in the viewmodel

doStuff(elements, data): void {
    for (let entry of elements[1].querySelectorAll("option")) {
        ko.applyBindingsToNode(entry, {disable: true }, entry);
    }
}

(I don't really want to disable them all - it's just a simple test) - but this lead to the curious result of the entire select list not rendering at all. On inspection the nodes are still there - in the HTML and they can be traveresed and logged - but they're not appearing on the page.

This isn't because they're disabled. Something in the binding is causing the whole thing to go screwy. Is there a way I can mimic the operation of optionsAfterRender on this foreach binding?


Solution

  • Aside from fixing the un-closed foreach binding typo I also found I needed to change the option's "title" binding to "label" to get the individual option text to show up. Other than that your code seems to function as expected.

    function viewModel(){
      var self = this;
      self.editDataId = 1;
      self.availableData = [
        { groupTitle: 'group1', groups: [ {id: 1, name: 'item1'}, {id: 2, name: 'item2'} ] } ,
        { groupTitle: 'group2', groups: [ {id: 3, name: 'item3'}, {id: 4, name: 'item4'} ] } 
      ];
    	
      self.doStuff = function(elements, data) {
        for (let entry of elements[1].querySelectorAll("option")) {
          ko.applyBindingsToNode(entry, { disable: true }, entry);
        }
      }
    }
    
    ko.applyBindings(new viewModel());
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    
    <select  data-bind="foreach: { data: availableData,
                                  value: editDataId,
                                  afterRender: doStuff }">
      <optgroup data-bind="attr: {label: groupTitle}, foreach: groups">
        <option data-bind="value: id, attr: {label: name}"></option>
      </optgroup>
    </select>