Knockout append observables w/ characters - javascript

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);

Related

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.
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.

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.

Knockout computed and input validation

I am fairly new to knockout and am trying to figure out how to put two pieces that I understand together.
I need:
Items that are dependent on each other.
Input value validation on the items.
Example:
I have startTime in seconds, duration in seconds, and stopTime that is calculated from startTime + duration
startTime cannot be changed
duration and stopTime are tied to input fields
stopTime is displayed and entered in HH:MM:SS format
If the user changes stopTime, duration should be calculated and automatically updated
If the user changes duration, stopTime should be calculated and automatically updated
I can make them update each other (assume Sec2HMS and HMS2Sec are defined elsewhere, and convert between HH:MM:SS and seconds):
this.startTime = 120; // Start at 120 seconds
this.duration = ko.observable(0);
// This dependency works by itself.
this.stopTimeFormatted = ko.computed({
read: function () {
return Sec2HMS(this.startTime + parseInt(this.duration()), true);
},
write: function (value) {
var stopTimeSeconds = HMS2Sec(value);
if (!isNaN(stopTimeSeconds)) {
this.duration(stopTimeSeconds - this.startTime);
} else {
this.duration(0);
}
},
owner: this
});
Or, I can use extenders or fn to validate the input as is shown in the knockout docs:
ko.subscribable.fn.HMSValidate = function (errorMessage) {
//add some sub-observables to our observable
var observable = this;
observable.hasError = ko.observable();
observable.errorMessage = ko.observable();
function validate(newValue) {
var isInvalid = isNaN(HMS2Sec(newValue));
observable.hasError(isInvalid ? true : false);
observable.errorMessage(isInvalid ? errorMessage : null);
}
//initial validation
validate(observable());
//validate whenever the value changes
observable.subscribe(validate);
//return the original observable
return observable;
};
this.startTime = 120; // Start at 120 seconds
this.duration = ko.observable(0);
this.stopTimeHMS = ko.observable("00:00:00").HMSValidate("HH:MM:SS please");
But how do I get them working together? If I add the HMSValidate to the computed in the first block it doesn't work because by the time HMSValidate's validate function gets the value it's already been changed.
I have made it work in the first block by adding another observable that keeps track of the "raw" value passed into the computed and then adding another computed that uses that value to decide if it's an error state or not, but that doesn't feel very elegant.
Is there a better way?
http://jsfiddle.net/cygnl7/njNaS/2/
I came back to this after a week of wrapping up issues that I didn't have a workaround for (code cleanup time!), and this is what I have.
I ended up with the idea that I mentioned in the end of the question, but encapsulating it in the fn itself.
ko.subscribable.fn.hmsValidate = function (errorMessage) {
var origObservable = this;
var rawValue = ko.observable(origObservable()); // Used for error checking without changing our main observable.
if (!origObservable.hmsFormatValidator) {
// Handy place to store the validator observable
origObservable.hmsFormatValidator = ko.computed({
read: function () {
// Something else could have updated our observable, so keep our rawValue in sync.
rawValue(origObservable());
return origObservable();
},
write: function (newValue) {
rawValue(newValue);
if (newValue != origObservable() && !isNaN(HMS2Sec(newValue))) {
origObservable(newValue);
}
}
});
origObservable.hmsFormatValidator.hasError = ko.computed(function () {
return isNaN(HMS2Sec(rawValue()));
}, this);
origObservable.hmsFormatValidator.errorMessage = ko.computed(function () {
return errorMessage;
}, this);
}
return origObservable.hmsFormatValidator;
};
What this does is creates another computed observable that acts as a front/filter to the original observable. That observable has some other sub-observables, hasError and errorMessage, attached to it for the error states. The rawValue keeps track of the value as it was entered so that we can detect whether it was a good value or not. This handles the validation half of my requirements.
As for making two values dependent on each other, the original code in my question works. To make it validated, I add hmsValidate to it, like so:
this.stopTimeFormatted = ko.computed({
read: function () {
return Sec2HMS(this.startTime + parseInt(this.duration()), true);
},
write: function (value) {
this.duration(HMS2Sec(value) - this.startTime);
},
owner: this
}).hmsValidate("HH:MM:SS please");
See it in action here: http://jsfiddle.net/cygnl7/tNV5S/1/
It's worth noting that the validation inside of write is no longer necessary since the value will only ever be written by hmsValidate if it validated properly.
This still feels a little inelegant to me since I'm checking isNaN a couple of times and having to track the original value (especially in the read()), so if someone comes up with another way to do this, I'm all ears.

Knockout.js modify value before ko.observable() write

I've got a working viewmodel with numerous variables.
I use autoNumeric (http://www.decorplanit.com/plugin/) for text formatting in textbox. I'd like to use the input field's observed data in a computed observable, but if the observable textfield with the formatting gets modified, the formatting also gets saved in the variable.
How can I only observe the value of the input field without the formatting?
I think the best way to this could be a getter/setter to the observable, and remove the formatting when the value is set. I couldn't find a solution in knockout's documentation to write get/set methods for ko.observable(), and ko.computed() can not store a value.
I don't want to use hidden fields, or extra variables.
Is this possible without it?
Solution, as seen on http://knockoutjs.com/documentation/extenders.html
ko.extenders.numeric = function(target, precision) {
//create a writeable computed observable to intercept writes to our observable
var result = ko.computed({
read: target, //always return the original observables value
write: function(newValue) {
var current = target(),
roundingMultiplier = Math.pow(10, precision),
newValueAsNum = isNaN(newValue) ? 0 : parseFloat(+newValue),
valueToWrite = Math.round(newValueAsNum * roundingMultiplier) / roundingMultiplier;
//only write if it changed
if (valueToWrite !== current) {
target(valueToWrite);
} else {
//if the rounded value is the same, but a different value was written, force a notification for the current field
if (newValue !== current) {
target.notifySubscribers(valueToWrite);
}
}
}
});
//initialize with current value to make sure it is rounded appropriately
result(target());
//return the new computed observable
return result;
};
And later on
function AppViewModel(one, two) {
this.myNumberOne = ko.observable(one).extend({ numeric: 0 });
this.myNumberTwo = ko.observable(two).extend({ numeric: 2 });
}
You can use ko.computed() for this. You can specify a write option, see Writeable computed observables
Example (taken from knockout documentation):
function MyViewModel() {
this.price = ko.observable(25.99);
this.formattedPrice = ko.computed({
read: function () {
return '$' + this.price().toFixed(2);
},
write: function (value) {
// Strip out unwanted characters, parse as float, then write the raw data back to the underlying "price" observable
value = parseFloat(value.replace(/[^\.\d]/g, ""));
this.price(isNaN(value) ? 0 : value); // Write to underlying storage
},
owner: this
});
}
ko.applyBindings(new MyViewModel());

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