3

I wanted to use the awesome slick carousel (http://kenwheeler.github.io/slick/) with Knockout. After looking at some of the other questions (https://stackoverflow.com/questions/26368176/slick-js-carousel-not-working-with-knockout-template-binding) it seems like the way to go would be to use a custom binding, but I'm having trouble getting it to load properly - it seems like the slick carousel is initializing before the DOM has completely loaded so it doesn't connect the last slide to the first slide. I also looked at knockout js custom binding called after internal dom elements rendered and am trying to use child binding contexts to have the custom binding force the child elements to bind first.

Here is the html markup:

<div data-bind = "slick">
    <!-- ko foreach: results -->
      <div>
        <result-thumbnail params="result:$data"></result-thumbnail>
      </div>
    <!-- /ko -->
</div>

result-thumbnail is a separate knockout component that formats the data I wanted to present in the carousel. In the custom bindings file:

ko.bindingHandlers.slick = {
init: function(element, valueAccessor, allBindingsAccessor, data, bindingContext) {
    var options = {
        infinite:true,
        slidesToShow: 3,
        slidesToScroll: 1,
    };

    var childBindingContext = bindingContext.createChildContext(
        bindingContext.$rawData, 
        null, 
        function(context) {
            ko.utils.extend(context, valueAccessor());
        });
    ko.applyBindingsToDescendants(childBindingContext, element);

    var local = ko.utils.unwrapObservable(valueAccessor());
    ko.utils.extend(options, local);
    $(element).slick(options);
    return { controlsDescendantBindings: true };
},
update: function(element, valueAccessor, allBindingsAccessor, data, context) {
  }
};

A few things I was considering: - Should I move the slick initialization to the update function? - Does the slick custom binding I wrote need to be bound to a different element? - If I were changing the value of the results observable, would I need to update slick by destroying then recreating the carousel?

Any help would be appreciated! I am still learning about custom bindings and if any one has had experience with getting the slick carousel to work with Knockout that would be great to hear about too.

Community
  • 1
  • 1
A Hsu
  • 33
  • 1
  • 6

1 Answers1

1

Disclaimer: I have never used slick.js.

You're right in thinking that the carousel is initializing before the DOM has completely loaded.

If you search on stackoverflow you'll find many options on how to call a function when all elements have finished rendering - the one I like best is described here: https://stackoverflow.com/a/19941134/3620458.

Once you have such a callback you can use it to set an observable in your main viewModel to true, like this:

var allElementsRendered = ko.observable(false);
var yourAfterRenderFunction = function () {
    allElementsRendered(true);
}

You can then pass that observable as a parameter to your 'slick' binding, like this:

<div data-bind = "slick: allElementsRendered">

Whenever the allElementsRendered() observable changes, the 'update' function in your custom binding will be called - so just move the initialization logic there, like this:

    update: function(element, valueAccessor, allBindingsAccessor, data, context) {
        var shouldInit = ko.unwrap(valueAccessor());

        if (shouldInit) {
            var options = {
                infinite:true,
                slidesToShow: 3,
                slidesToScroll: 1,
            };

            $(element).slick(options);
        } else {
            $(element).slick("unslick");
        }
    }

Handling updating the results observableArray may require some additional work - what you can do is subscribe to it and set the allElementsRendered() to false any time it changes, like this:

var resultsSubscription = results.subscribe(function(newValue){
    allElementsRendered(false);
})

This, combined with the above-mentioned afterRenderFunction should hopefully keep your carousel updated.

Community
  • 1
  • 1
Bragolgirith
  • 2,167
  • 2
  • 24
  • 44
  • Thank you for the help! I tried this out yesterday and I was able to have the slick carousel initialize after all child div elements were rendered, but I think I have to do a little more playing around with how the slick carousel renders so that I can continue to update the carousel with the new results. Once I get the final solution I will add it here. – A Hsu Apr 16 '15 at 16:31
  • 5
    Did you find out the final solution? :) I am interested in how you did it :) – Bjørn May 12 '16 at 07:55
  • The missing part in above answer is when to set allElementsRendered to true. In order to set this to true only after all the child elements are rendered. Add afterRender handler to be called after every child element is rendered. refer this https://stackoverflow.com/a/14254390 and set allElementsRendered to true when the last child element is rendered. I have tried will post the cleaner sample. – Pandurang Patil Apr 16 '20 at 08:04