Knockout JS, live updating data from websocket - javascript

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.

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.

Related

Knockout append observables w/ characters

I have observables in my KnockoutJS app and their values are being fetched from an array that's contained globally in the app, eg:
self.observable = ko.observable(App[0].Observable_Value);
For ease, let's say Observable_Value = 10.
This is working as you'd expect it to and the <input> that self.observable is binded to has the correct value in it by default; 10.
What I want to do now is to append the observable with a % in the <input> so the displayed output in the input field will be 10%.
I simply want to append the input value with a % because I need the observable to be readable as a number and not a string to prevent NaN errors later on in the app. The app relies heavily on numbers.
I've tried doing this as a computed function with read/write & parseFloat but to no success.
Any ideas?
I think that the best option in this case would be a custom binding handler.
I simply want to append the input value with a % because I need the observable to be readable as a number and not a string
That is precisely what a binding handler could do - preserve the original value of the observable, but change the way that it is displayed.
I've tried doing this as a computed function
Although a computed function could do the trick, it's usually unecessary unless you want to actually work with the return value from the computed. In this case, since you just want to change the visual display, you don't need a another variable representing the same value.
Here is a very basic one that just puts a % sign in front of the observable's value. See fiddle
ko.bindingHandlers.percent = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var value = ko.unwrap(valueAccessor());
$(element).text('%' + value);
}
};
You can add a read/write computable that will add the percent for display, then remove it when setting the underlying value (fiddle):
ko.observable.fn.formatAsPercent = function() {
var base = this;
return ko.computed({
read: function() {
return base() + "%";
},
write: function(newValue) {
base(parseInt(newValue.replace('%', '')));
}
});
};
function ViewModel() {
var self = this;
self.number = ko.observable(10); // actual number
self.percent= self.number.formatAsPercent(); // used for binding to show %
}
var my = { vm : new ViewModel() };
ko.applyBindings(my.vm);

KnockoutJs v3 - _ko_property_writers = undefined

I'm trying to get my custom binding to work with both observables and plain objects. I followed the answer in this question:
writeValueToProperty isn't available
However, if I look at the object returned if I execute the allBindingsAccessor, the property '_ko_property_writers' is undefined.
Does anyone know if this has changed at all in version 3 of knockout?
edit
Sorry I should have stated, I am trying to 'write' the value back to the model, in an observable agnostic way
This was helpful for me:
ko.expressionRewriting.twoWayBindings.numericValue = true;
ko.bindingHandlers.numericValue = {
...
}
It is defined after specifying binding as two-way.
So I can use something like that inside my custom binding:
ko.expressionRewriting.writeValueToProperty(underlying, allBindingsAccessor, 'numericValue', parseFloat(value));
writeValueToProperty is defined internally as:
writeValueToProperty: function(property, allBindings, key, value, checkIfDifferent) {
if (!property || !ko.isObservable(property)) {
var propWriters = allBindings.get('_ko_property_writers');
if (propWriters && propWriters[key])
propWriters[key](value);
} else if (ko.isWriteableObservable(property) && (!checkIfDifferent || property.peek() !== value)) {
property(value);
}
}
The standard way to do this is with ko.unwrap as described here: http://knockoutjs.com/documentation/custom-bindings.html
For example:
ko.bindingHandlers.slideVisible = {
update: function(element, valueAccessor, allBindings) {
// First get the latest data that we're bound to
var value = valueAccessor();
// Next, whether or not the supplied model property is observable, get its current value
var valueUnwrapped = ko.unwrap(value);
// Grab some more data from another binding property
var duration = allBindings.get('slideDuration') || 400; // 400ms is default duration unless otherwise specified
// Now manipulate the DOM element
if (valueUnwrapped == true)
$(element).slideDown(duration); // Make the element visible
else
$(element).slideUp(duration); // Make the element invisible
}
};
In that example valueUnwrapped is correct whether the user bound to an observable or a normal object.

Knockoutjs if/shim binding

I want to create a custom binding that behaves like the if binding, but instead of removing the element entirely, it replaces it with another element of the same height whenever it should be removed.
I'm struggling to find a way of doing this that isn't hacky. I don't know enough about the internals of knockout to go about this in an educated way.
Any pointers greatly appreciated.
Thanks!
You could write your own binding:
ko.bindingHandlers.shim = {
update: function(element, valueAccessor, allBindings) {
// First get the latest data that we're bound to
var value = valueAccessor();
// Next, whether or not the supplied model property is observable, get its current value
var shim = ko.unwrap(value);
if (shim) {
var shimEl = $(element).data('shim');
// Create the shim element if not created yet
if (!shimEl) {
shimEl = $('<div />').addClass('shim').appendTo(element);
// Equal the height of the elements
shimEl.height($(element).height());
$(element).data('shim', shimEl);
}
shimEl.show();
} else {
var shimEl = $(element).data('shim');
if (shimEl) {
shimEl.hide();
}
}
// You can also trigger the if-binding at this point
// ko.bindingHandlers.if.update(element, valueAccessor, allBindings);
}
};
Then use it like this:
<div data-bind="shim: [condition]"></div>

KnockoutJS custom binding calling click event during bind, not on click

I`m attempting to bind an observable array of people two a two column responsive layout with click events using knockoutjs.
I have created a custom binding called TwoCol that loops through the array, and appends nodes to the DOM to create my suggested layout, but the click events are giving me trouble when I try to apply them in a custom binding nested in a loop.
I have played with it quite a bit, and encountered all types of results, but where I`m at now is calling my ~click~ event during binding, rather than on click.
http://jsfiddle.net/5SPVm/6/
HTML:
<div data-bind="TwoCol: Friends" id="" style="padding: 20px">
JAVASCRIPT:
function FriendsModel() {
var self = this;
this.Friends = ko.observableArray();
this.SelectedFriend = "";
this.SetSelected = function (person) {
alert(person);
self.SelectedFriend = person;
}
}
function isOdd(num) {
return num % 2;
}
ko.bindingHandlers.TwoCol = {
update: function (elem, valueAccessor) {
var i = 0;
var rowDiv;
var vFriends = ko.utils.unwrapObservable(valueAccessor());
$(elem).html('');
while (i < vFriends.length) {
//create row container every other iteration
if (!isOdd(i)) {
rowDiv = document.createElement("div");
$(rowDiv).addClass("row-fluid");
elem.appendChild(rowDiv);
}
//add column for every iteration
var colDiv = document.createElement("div");
$(colDiv).addClass("span6");
rowDiv.appendChild(colDiv);
//actual code has fairly complex button html here
var htmlDiv = document.createElement("div");
var htmlButton = vFriends[i]
htmlDiv.innerHTML = htmlButton;
colDiv.appendChild(htmlDiv);
//i think i need to add the event to the template too?
//$(htmlDiv).attr("data-bind", "click: { alert: $data }")
//it seems that the SetSelected Method is called while looping
ko.applyBindingsToDescendants(htmlDiv, { click: friends.SetSelected(vFriends[i]) });
i++;
}
return { controlsDescendantBindings: true };
}
}
var friends = new FriendsModel();
friends.Friends.push('bob');
friends.Friends.push('rob');
friends.Friends.push('mob');
friends.Friends.push('lob');
ko.applyBindings(friends);
I don't think you're using ko.applyBindingsToDescendants correctly. I admit I'm a little confused as to the meaning of some of the values in your code, so I may have interpreted something incorrectly.
Here's a fiddle where I think it's working the way you intended:
http://jsfiddle.net/5SPVm/7/
http://jsfiddle.net/5SPVm/8/
Notice if manually control descendant bindings (return { controlsDescendantBindings: true };), you need to set that up in the init callback, instead of update. The update callback is too late for that.
Quick rundown of the changes (edited):
Moved the controlsDescendantBindings into the init binding callback
Added the necessary parameter names to the binding param list to access additional values.
I re-enabled the html.attr call. Notice that now, because the binding context is set to the actual item, the SetSelected method doesn't exist at that level anymore, so it is necessary to use $parent.SetSelected.
$(htmlDiv).attr("data-bind", "click: $parent.SetSelected")
Fixed the ko.applyBindingsToDescendants call. This method takes a binding context, which is created from the current binding context, and also takes the element to apply the binding to. You don't want to reapply the binding, which is why this whole thing needs to be in the init handler.
var childBindingContext = bindingContext.createChildContext(vFriends[i]);
ko.applyBindingsToDescendants(childBindingContext, colDiv);

Knockout.js: array parameter in custom binding

i try to write a custom list-binding. This is what i have so far:
var myArr = ko.observableArray();
myArr.push("foo");
myArr.push("bar");
var view = {
matches: myArr
}
ko.bindingHandlers.matchList = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
// gives me 0
console.log(valueAccessor().length);
// gives me 2
console.log(valueAccessor()().length);
},
};
// Activates knockout.js
ko.applyBindings(view);
My html binding looks as follow:
<div data-bind="matchList: matches"></div>
Why do i have to use the second pair of parentheses to get into my array?
The valueAccessor is a function that returns what was passed to the binding. It is wrapped in a function, so that it is not evaluated immediately.
A typical pattern would be to do:
var value = ko.utils.unwrapObservable(valueAccessor());
ko.utils.unwrapObservable will safely handle both observables and non-observables and return the value. So, if it is an observable, then it will return yourValue(), otherwise it will just return yourValue. This allows your binding to support binding against either observables or plain properties.
In addition, some bindings need to deal with the observable itself and some bindings need to deal with the value of the observable. So, the observable is not unwrapped, by default. So, valueAccessor() returns your observable (which is a function) and then it is up to you to decide if you want to unwrap it to get the value or possibly set the value of it.
I think the confusing thing here is that the valueAccessor passed to init is different from the parameter of the same name passed to update. In init, it's a function that returns functions that in turn returns your array. Check out this sample code from their documentation. I added two console logs at the end that should show you the function that valueAccessor() returns:
var myArr = ko.observableArray();
myArr.push("foo");
myArr.push("bar");
var view = {
matches: myArr
}
ko.bindingHandlers.matchList = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
var value = ko.utils.unwrapObservable(valueAccessor()); // Get the current value of the current property we're bound to
$(element).toggle(value); // jQuery will hide/show the element depending on whether "value" or true or false
console.log(value);
console.log(valueAccessor().toString());
}
};
// Activates knockout.js
ko.applyBindings(view);

Categories

Resources