-->

Knockout, noUiSlider, custom bindings

2019-08-14 13:14发布

问题:

I am trying to meld knockout and noUiSlider with a custom binding. I have found similar code for the jQuery-UI slider and Knockout and used that as a basis.

If I use the sliderko custom binding below, the noUisliders do not update the observables. I get NaN, and nothing in the input fields.

If I use the slider custom binding below with noUiSlider events directly, then everything works. The slider custom binding doesn't perform well, probably due to tracking the noUiSlider update event. However, that's the only way I could figure out how to get the slider to continuously update the input fields.

I'd like to use Knockout's registerEventHandler, but I'm not sure how to get that to work.

// noUiSlider
ko.bindingHandlers.slider = {
  init: function(element, valueAccessor, allBindingsAccessor) {
    var options = allBindingsAccessor().sliderOptions || {};
    noUiSlider.create(element, options);

    // works with with noUiSlider but not with knockout event bindings
    element.noUiSlider.on('set', function(values, handle) {
      var observable = valueAccessor();
      observable(values[handle]);
    });

    // works with with noUiSlider but not with knockout event bindings
    element.noUiSlider.on('update', function(values, handle) {
      var observable = valueAccessor();
      observable(values[handle]);
    });

    ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
      element.noUiSlider.destroy();
    });
  },
  update: function(element, valueAccessor) {
    var value = ko.utils.unwrapObservable(valueAccessor());
    element.noUiSlider.set(value);

  }
};

// using knockout event handlers
ko.bindingHandlers.sliderko = {
  init: function(element, valueAccessor, allBindingsAccessor) {
    var options = allBindingsAccessor().sliderOptions || {};
    noUiSlider.create(element, options);

    ko.utils.registerEventHandler(element, 'set', function(values, handle) {
      var observable = valueAccessor();
      observable(values[handle]);
    });

    ko.utils.registerEventHandler(element, 'update', function(values, handle) {
      var observable = valueAccessor();
      observable(values[handle]);
    });

    ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
      element.noUiSlider.destroy();
    });
  },
  update: function(element, valueAccessor) {
    var value = ko.utils.unwrapObservable(valueAccessor());
    element.noUiSlider.set(value);

  }
};

// initialization for NoUiSlider - saves from adding stuff into page
var sillysv = {
  start: [10],
  step: 0.01,
  range: {
    'min': 0,
    'max': 100
  }
};

var sillysp = {
  start: [5],
  step: 0.01,
  range: {
    'min': 0,
    'max': 100
  }
};

var ViewModel = function() {
  var self = this;
  self.savings = ko.observable();
  self.spent = ko.observable();
  self.net = ko.computed(function() {
    return self.savings() - self.spent();
  });
};

ko.applyBindings(new ViewModel());
<link href="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/8.1.0/nouislider.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/8.1.0/nouislider.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<h2>Slider Demo</h2>
Savings:
<input data-bind="value: savings" />
<div style="margin: 10px" data-bind="slider: savings, sliderOptions: sillysv"></div>

Spent:
<input data-bind="value: spent" />
<div style="margin: 10px" data-bind="slider: spent, sliderOptions: sillysp"></div>

Net: <span data-bind="text: net"></span>

回答1:

If you're using jQuery there is no difference between binding events with jQuery or ko registerEventHandler. This function is for internal use, and is able to make bindings that work fine in different browsers. If jQuery is available, this function uses jQuery binding.

The second problem, updating the observable so many times, should be solved by throttling the upsated in the observable. You can do that using the rateLimit ko extender directly in your view model:

var ViewModel = function() {
  var self = this;
  self.savings = ko.observable()
    .extend({ rateLimit: { method: "notifyWhenChangesStop", timeout: 400 } });;
  self.spent = ko.observable()
    .extend({ rateLimit: { method: "notifyWhenChangesStop", timeout: 400 } });;
  self.net = ko.computed(function() {
     return self.savings() - self.spent();
}

You can also modify the custom binding if you want to extend the observables in the init. if you do so, you should check that the observable is not already extended: Check if extension was applied to observable.