Frage

I need to initialize third party library - videojs - every time the viewmodel activates. However, the problem is library requires a dom element, which is not attached yet when durandal's activate callback is being executed.

//viewmodel
define (["jquery", "knockout", "videojs"], function ($, ko, videojs) {
  var

    activate = function (videoSource) { //route param
      var videoTag = $('#video').get(0); //undefined
      var playerInstance = new videojs.Player(videoTag); //error

      playerInstance.src(videoSource); //loads video from route parameter
      playerInstance.play(); //starts to play
    };

    return {
      activate: activate
    }
});

//view
<section id="page-quiz-answer" class="page">
    <video id="video"></video>
</section>
War es hilfreich?

Lösung

The DOM is not yet available in the #activate callback. You would need to access the DOM element in the #attached or #compositionComplete callback. You can read here about the signatures of those callbacks, along with their intended purposes.

//viewmodel
define (["jquery", "knockout", "videojs"], function ($, ko, videojs) {
    var attached = function (view) { //route param
      var $view = $(view);
      var $videoTag = $view.find('#video'); //undefined
      var playerInstance = new videojs.Player($videoTag); //error

      playerInstance.src(videoSource); //loads video from route parameter
      playerInstance.play(); //starts to play
    };

    return {
      attached: attached
    }
});

ANOTHER APPROACH

You could also write a custom Knockout binding and encapsulate the logic of accessing the DOM element there.

I would actually suggest this approach as I'm suspicious of the point where you pass in a DOM reference to videojs.Player(). This screams for encapsulation in a custom binding.

[EDIT]

EXAMPLE OF USAGE OF CUSTOM BINDING

Consider this usage in the view:

<div id='video' data-bind=player: {instance: playerInstance, source: videoSource}></div>

With this, you can then move all of the logic in the #attached function above into the custom binding. If you need to dynamically control the playerInstance, you can create an object literal with observable properties in your viewmodel, something like this:

var playerOptions = {
        action: ko.observable('play'),
        player: new videojs.Player(),
        source: ko.observable(videoSource)
    };

and change the binding to this:

<div id='video' data-bind=player: {options: playerOptions}></div>

Your custom binding would look something like this:

ko.bindingHandlers.player = {
    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        var options = ko.unwrap(valueAccessor),
            $element = $(element);            

        var playerInstance = options.player;
        playerInstance.source = options.source();
        playerInstance.do(options.action());
    }
};

I'm not sure how your Player is connected to the DOM. I can see only that you're passing in a DOM element. But inside this binding, you have access to the element itself.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top