2

I'm using jquery accordion in a KO bindingHandler, I have to populate the DOM, used by accordion UI, using ajax by an app requirement.

this.faqList = ko.observableArray();
$.ajax({
  url: 'getFaqs'
}).done(function( data ) {
  that.faqList(data);
});

My bindingHandler should be as simple as

ko.bindingHandlers.koAccordion = {
  init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
    $(element).accordion(valueAccessor());

  },
  update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
      $(element).accordion(valueAccessor());
  }
};

The update method is called when the observableArray (faqList) changes, but at that moment UI accordion needs the DOM structure already populated which is not true, seems KO creates after the update method is called. How could I achieve call update after the DOM structure is populated with the new contents?

Here's the default DOM.

<ul class="question-list" data-bind="koAccordion: {
          active: false,
          autoHeight: false, 
          collapsible: true}">
  <!-- ko foreach: faqList -->
    <li>
      <div class="header">
        <span class="rigth-arrow"></span>
        <a href="#" data-bind="text: title"></a>
      </div>
      <div class="content">
        <h2 data-bind="text: title"></h2>
        <div data-bind="text: content"></div>
      </div>
    </li>
  <!-- /ko -->
  </ul>

CodePen

Brigand
  • 84,529
  • 20
  • 165
  • 173
opaucara
  • 41
  • 2
  • 7

2 Answers2

3

koAccordion.update is useless because you never update that binding value: it's static in the markup and contains no observables. foreach binding provides afterRender callback for your purposes:

var vm = function() {
    // ...
    this.initAccordion = function(element) {
        $(element).accordion(accordionOptions);
    };
};


<!-- ko foreach: { data: faqList, afterRender: initAccordion } -->

However, in this case you have to move accordion options out of binding values. If you insist on having custom binding one way to do this is to delegate work to foreach binding, inserting afterRender into binding values on the way:

ko.bindingHandlers.koAccordion = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel,
                   bindingContext) {
        var newValue = ko.computed(function() {
            var value = ko.unwrap(valueAccessor());
            value.afterRender = function(element) {
                $(element).accordion(value);
            };
            return value;
        });
        return ko.bindingHandlers.foreach.init(element, newValue,
            allBindingsAccessor, viewModel, bindingContext);
    },
    update: function(element, valueAccessor, allBindingsAccessor, viewModel,
                     bindingContext) {
        return ko.bindingHandlers.foreach.update(element, valueAccessor,
            allBindingsAccessor, viewModel, bindingContext);
    }
};

Using it like this:

<ul class="question-list" data-bind="koAccordion: {
      data: faqList,
      active: false,
      autoHeight: false, 
      collapsible: true }">
   <li>...bindings...</li>
</ul>

See demo page I forked from the one in the post: http://codepen.io/anon/pen/IJpuj

rkhayrov
  • 10,040
  • 2
  • 35
  • 40
  • "If you insist on having custom binding" AfterRender and DOM dependecy in VM is a antipattern so custom binding is much better – Anders Aug 07 '13 at 08:01
  • Great, now, I had to add some code in order to bind accordion when that last item added was rendered – opaucara Aug 07 '13 at 18:36
1
This is my final code
ko.bindingHandlers.koAccordion = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  var newValue = ko.computed(function() {
    var value = ko.unwrap(valueAccessor());
    value.afterRender = function(renderedElement, loopEntryObject) {
      if ( loopEntryObject == value.data()[value.data().length - 1] ) {
        if ($(element).data('accordion')) {
          $(element).accordion('destroy');
        }
        $(element).accordion(value);
      }
    };
    return value;
  });
  return ko.bindingHandlers.foreach.init(element, newValue, allBindingsAccessor, viewModel, bindingContext);
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
  return ko.bindingHandlers.foreach.update(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
}
opaucara
  • 41
  • 2
  • 7