Reputation: 780
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
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