Sidebp
Sidebp

Reputation: 780

Calling a view function after view model update in Knockout.JS

I have created a simple content rotator using the excellent Cycle.js plugin, this gets it's content from my Knockout.JS view model property (appViewModel.mediaPlayer in this case):

e.g.

  var testData = [{ url: "media/1.png" }, { url: "media/2.jpg"}];

    var appViewModel =
    {
        mediaPlayer: new ko.observableArray(testData)
    };

    ko.applyBindings(appViewModel);

This is rendered in the view using JQuery Tmpl e.g.

 <script id="mediaPlayerTemplate" type="text/template">
        <img src=${url} />
    </script>

<div class="adContainer" data-bind="template: {name: 'mediaPlayerTemplate', foreach: mediaPlayer}">
    </div>

To start the image transition I simply call the Cycle plugins method "cycle":

 $('.adContainer').cycle({
            fx: 'fade',
            timeout: 1000,
            speed: 500
        });

This works great, however when I update my view model media player content the cycle plugin stops working...this is not a problem as a simple call to cycle() will start it again.

However, my question is, where is the best place to make the call to cycle() to update the view? I figured I could subscribe to the mediaPlayer changes, simply calling the method when required however this would mean that I have to put the JQuery element/view logic in the View Model which feels wrong (perhaps I'm trying to be too purist!).

In short, how do I trigger functions in the view from the view model without the view model knowing about the views function? In Silverlight/WPF this was possible with triggers in XAML but I'm not sure how to achieve the same separation using Knockout.JS

Upvotes: 2

Views: 2172

Answers (1)

Artem
Artem

Reputation: 3700

I think it is better to do this using bindinghandlers, this will also allow you properly dispose plugin when content is re-rendered.

Your example (it will change cycle after 5 seconds):

http://jsfiddle.net/nickolsky/uXDUA/2/

HTML:

<script id="mediaPlayerTemplate" type="text/template">
        <img src=${url} />
    </script>

<div data-bind="template: {name: 'mediaPlayerTemplate', foreach: mediaPlayer}, cycle: { fx: 'fade', timeout: 1000, speed: 500 }, cycleLinked: mediaPlayer">
    </div>

JAVASCRIPT:

ko.bindingHandlers.cycle = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        $element = $(element);
        var options = ko.utils.unwrapObservable(valueAccessor()) || {};
        //console.log('cycle_init');
        var _starting = false;

        var _cycle = function() {
            if(_starting) return;
            _starting = true;
            //console.log('cycle_create');
            $element.hide();
            setTimeout(function() { $(element).cycle(options); $element.show(); _starting = false; }, 0);
        };

        var subscription = allBindingsAccessor().cycleLinked.subscribe(_cycle);
        //handle disposal
        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
            //console.log('cycle_destroy');
            $element.cycle("destroy");
            subscription.dispose();
        });
        _cycle();

    }
};


var testData = [{
    url: "http://cloud.github.com/downloads/malsup/cycle/beach1.jpg"},
{
    url: "http://cloud.github.com/downloads/malsup/cycle/beach2.jpg"}];

var appViewModel = {
    mediaPlayer: new ko.observableArray(testData)
};

ko.applyBindings(appViewModel);

setTimeout(function() {
 appViewModel.mediaPlayer.push({url: "http://cloud.github.com/downloads/malsup/cycle/beach3.jpg"});
appViewModel.mediaPlayer.push({url: "http://cloud.github.com/downloads/malsup/cycle/beach4.jpg"});
    }, 5000);​

Upvotes: 3

Related Questions