Shawn Johnson
Shawn Johnson

Reputation: 45

Knockout JS, live updating data from websocket

I am trying to load data from a websocket to show on a gauge using knockout and gaugeMeter.js. I keep getting the "cannot apply bindings multiple times to the same element" error. Here is the code HTML

<div class="GaugeMeter gaugeMeter" id="PreviewGaugeMeter" data-bind="gaugeValue: Percent" data-size=350 data-theme="Orange" data-back="Grey"
  data-text_size="0.4" data-width=38 data-style="Arch" data-animationstep="0" data-stripe="3"></div>

JS

// Bind new handler to init and update gauges.
      ko.bindingHandlers.gaugeValue = {
        init: function(element, valueAccessor) {
            $(element).gaugeMeter({ percent: ko.unwrap(valueAccessor()) });
        },
        update: function(element, valueAccessor) {
          
            $(element).gaugeMeter({ percent: activePlayerData.boost });
            
        }
      };

      // Create view model with inital gauge value 15mph
      // Use observable for easy update.
      var myViewModel = {
        Percent: ko.observable(15)
      };
      
      ko.applyBindings(myViewModel);

The activePlayerData.boost is the data I am getting from the websocket and need to update the value, it always shows the first value but everything after that is giving the error. I am really lost with the knockout stuff as I am very new to coding.

Upvotes: 0

Views: 198

Answers (1)

Tomalak
Tomalak

Reputation: 338316

A minimal, working sample for your use case is below. You can run it to see what it does:

// simple gaugeMeter jQuery plugin mockup
$.fn.gaugeMeter = function (params) {
  return this.css({width: params.percent + '%'}).text(params.percent);
}

// binding handler for that plugin
ko.bindingHandlers.gaugeValue = {
  update: function(element, valueAccessor) {
    var boundValue = valueAccessor();
    var val = ko.unwrap(boundValue); // or: val = boundValue();
    $(element).gaugeMeter({ percent: val });
  }
};

// set up viewmodel
var myViewModel = {
  Percent: ko.observable(15)
};
ko.applyBindings(myViewModel);
  
// simulate regular value updates
setInterval(function () {
  var newPercentValue = Math.ceil(Math.random() * 100);
  myViewModel.Percent(newPercentValue);
}, 1500);
div.gaugeMeter {
  background-color: green;
  height: 1em;
  color: white;
  padding: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div class="gaugeMeter" data-bind="gaugeValue: Percent"></div>

Things to understand:

  • The update part of the binding handler is called whenever the bound observable (i.e. Percent) changes its value. You don't strictly need the init part if there is nothing to initialize.

  • The valueAccessor gives you the thing that holds the bound value. Typically, but not necessarily this is an observable - like in your case. Once you have called valueAccessor(), you still need to open (aka "unwrap") that observable to see what's inside. This is why my code above takes two steps, valueAccessor() and ko.unwrap().

    • When would it ever not be an observable? - You are free to bind to literal values in your view (data-bind="gaugeValue: 15"), or to non-observable viewmodel properties (data-bind="gaugeValue: basicEfficiency") when the viewmodel has {basicEfficiency: 15}. In all cases, valueAccessor() will give you that value, observable or not.
    • Why use ko.unwrap(boundValue) instead of boundValue()? - The former works for both literal values and for observables, the latter only works for observables. In a binding handler it makes sense to support both use cases.
  • Once an update arrives, e.g. via WebSocket, don't try to re-apply any bindings or re-initialize anything.

    All you need to do is to change the value of the respective observable in your viewmodel. Changing an observable's value works by calling it: myViewModel.Percent(15); would set myViewModel.Percent to 15.

    If 15 is different from the previous value, the observable will take care of informing the necessary parties (aka "subscribers"), so all required actions (such as view updates) can happen.

Upvotes: 1

Related Questions