I have an observable item and I am trying to apply user driven formatting on that observable. I have an integer input box where a user can select how many decimal places, 0-6. I am trying to update the observable to apply decimal places based on the selection in that input box.
I first tried a computed value, which did not work. Returned an error that 'toFixed' was not a function.
this.formattedResult = ko.computed(function () {
var newValue = self.decimalValue();
var precision = self.decimalPlaces();
return newValue.toFixed(precision);
});
I then tried adding a binding handler, which results in 'uncaught object' on the 'toFixed'
ko.bindingHandlers.numericText = {
update: function (element, valueAccessor, allBindingsAccessor) {
var value = new String(ko.utils.unwrapObservable(valueAccessor()).toString());
var precision = ko.utils.unwrapObservable(allBindingsAccessor().precision);
var formattedValue = value.toFixed(precision);
ko.bindingHandlers.text.update(element, function () { return formattedValue; });
},
defaultPrecision: 1
};
Then I tried extending numeric with a numericText, which also results in a 'uncaught object' error on the 'toFixed'.
ko.extenders.numeric = function (target, precision) {
if (precision() != null) {
var precisionValue = target();
console.log(precisionValue);
var precisionDecimal = precisionValue.toFixed(2);
console.log(precisionDecimal);
var result = ko.dependentObservable({
read: function () {
return target().toFixed(precision());
},
write: target
});
result.raw = target;
return result;
}
else
return target;
};
The binding on the HTML elements were all updated to take these changes into account but they did not work. I also tried just simply adding 'toFixed' to the data binding in the HTML and that also did not work.
I found these similar questions but the solutions are not working for me.
Format knockout observable with commas and/or decimal places within html data binding, NOT in viewmodel
Formatting rules for numbers in KnockoutJS
Adding decimal formatting to Knockout number data bindings
This is a very frustrating problem as I thought it would be really simple to add decimal places in a computed function. What am I doing wrong?
I'm not sure if it matters but I am using knockout 3.1
Edit:
I also tried just simply adding 'toFixed' to the data-binding but that also results in an uncaught object.
<p data-bind="text: decimalValue().toFixed(2)"></p>
I didn't end up solving my problem in the way that I had anticipated but I modified the computed column to force the decimal to a float and then added the decimals to that float value.
The final function looks like this:
this.formattedResult = ko.computed(function () {
var newValue = parseFloat(self.decimalValue());
var precision = self.decimalPlaces();
return newValue.toFixed(precision);
});
I don't know why this works but the other does not? Maybe when I assign the value to newValue I'm just assigning an observable to it and the observable doesn't know how to handle toFixed()? Doesn't explain all of the other problems but at least this is fixed for me.
I recommend using the following JavaScript Number constructor which will return NaN if the number is invalid.
this.formattedResult = ko.computed(function () {
// call the number constructor. newValue is now a number or NaN
//we choose a default value as well.
var newValue = isNaN(self.decimalValue()) ? 0 :
Number(self.decimalValue());
// call the number constructor again
var precision = isNaN(self.decimalPlaces()) ? 0 : Number(self.decimalPlaces());
return newValue.toFixed(precision);
});
Related
In AgGrid, I am tyring to use aggregation functions and valuegetter on the same column. It seems that because of valuegetter, my aggreation functions are not working, I just get 0 or null value on aggregation. Could you please check my code for any possible solutions?
Thanks.
{ headerName: "Price", filter: "agNumberColumnFilter", valueGetter: priceValueGetter, allowedAggFuncs: ['avg', 'sum', 'min', 'max']};
function priceValueGetter(params) {
var value = '';
if (params.data) {
var EPrice = params.data.a;
var p1 = params.data.b;
if (EPrice && p1) {
value = (EPrice - p1).toFixed(2);
}
}
return value;
}
Your valueGetter is returning a string.
So the built-in aggregation functions, which expect numbers, are failing.
Try this valueGetter instead:
function priceValueGetter(params): number {
if (params.data && params.data.a && params.data.b) {
return params.data.a - params.data.b
}
return null;
}
This won't format the way that you want, but you can fix that by adding a valueFormatter.
One other piece of advice - given that you have a variable named 'EPrice', I'm assuming that you're dealing with money. You shouldn't depend on Javascript's native 'number' type for money, as it is essentially a 'float', and rounding errors will ensue. There are lots of articles about how to properly handle monetary values in Javascript - I personally use a Javascript port of Java's BigDecimal class, called 'big.js', but there are several other solutions.
Edit - also be careful of using if on a number - it evaluates to false if the number is zero. If that's what you want, fine, but if it isn't, adjust your logic accordingly.
Background
We have much of our data formatted like
var X = {value:'some val',error:'maybe an error',valid:true}
as a result we find ourselves calling X.value ALL the time.
We don't use the .error or .valid nearly as much, but we do use it.
What I want
To quit calling .value everywhere, but to still have access to meta data on a per data point level.
The Question
Is there one of
A) A way to put meta data on a primitive? attaching .error to an int for example? Is it possible for bools or strings?
B) A way to make a class that can be treated as a primitive, providing a specific data member when I do? IE X.value = 5, X+3 returns 8.
C) A better design for our data? Did we just lay this out wrong somehow?
You can set the method toString() to your object and return value.
var X = {
value: 1,
error:'maybe an error',
valid:true,
toString: function() {
return this.value;
}
}
X.value = 5;
console.log(X+3);
You can represent you data as a function object that also has properties:
var X = () => 1;
X.value = 1;
X.error = 'maybe an error';
X.valid = true,
console.log(X()); // 1
console.log(X.valid); // true
For better design you can encapsulate the creation of the data object in another function.
few weeks ago I've started to work with typescript and knockoutJS, I have a specific problem and yet I have solution for it, it's so ugly I can't stand that, but can't get anything better from it, there's too much code to be pasted, but i'll try to describe my problem the best I can:
I have two view models that communicate with the same data model. Let's say that model is an array of Simple Objects called Numbers. Every Number has following properties: Value, isMinValueEnabled, minValue, isMaxValueEnabled, maxValue, isStepEnabled, stepValue, valueFormat. valueFormat might be numeric or percentage (so that value, min, max and step are multiplied by 100). I can activate minimum, maximum and step values and deactivate them. Then save data to the model and do exactly the same (with some restrictions) in another viewModel.
The problem is with those optional parameters and percentage values, because when I'm reading data I firstly check if Number is percentage or not and if every property is Enabled. Then I eventually multiply value by 100 if it is set. I have to do the same operation when I'm saving data, that is check every number for format and is*Enabled and eventually divide by 100. With 3-4 properties there is no problem, but now I have to write few more optional properties that depend's on the format and enabled/disabled state and I'm getting into troubles with ton's of if's statements, I myself can't even read that. Is there some better patter that can be used in this situation?
EDIT
Ok, so things look like this: I have a series of numbers, they can look like:
100, 2 000, 34 000.21, 2.1k, 2.11M, 22% but those are only display values whereas real values should stand like this for the example given: 100, 2000, 34000.21, 2100, 2110000, 0.22. The user can edit the value to anything else, like, let's say has 22% in input and then edit this into 1k. I shall convert 1k to original value which is 1000 and check if minimumValue and maximumValue for that number are set. If they are, I will check, and let's say maxValue is 800, then user input can no longer be 1k, but 0.8k instead because he can not get out of maximumValue. MinimumValue, MaximumValue, StepValue and so on are properties of every single Number. I was playing with ko.pureComputed, but I need to abstract it somehow:
var f = ko.computed(<KnockoutComputedDefine<number>>{
read: ...
write: ...
});
What I have now is totally ugly and looks like this:
export class Variable {
[...]
public inputType: KnockoutObservable<VariableInputType>;
public typeAndFormat: KnockoutObservable<DataTypeFormat>;
public isMinEnabled: KnockoutObservable<boolean>;
public minValue: KnockoutObservable<number>;
public isMaxEnabled: KnockoutObservable<boolean>;
public maxValue: KnockoutObservable<number>;
public isStepEnabled: KnockoutObservable<boolean>;
public stepValue: KnockoutObservable<number>;
public value: KnockoutObservable<number>;
[...]
constructor(...) {
[...]
this.inputType = ko.observable(VariableInputType.Input);
this.typeAndFormat = ko.observable(variable.typeAndFormat || DataTypeFormat.Number);
if (variable.minValue !== null) {
this.isMinEnabled = ko.observable(true);
this.minValue = ko.observable(variable.minValue);
} else {
this.isMinEnabled = ko.observable(false);
this.minValue = ko.observable(null);
}
if (variable.maxValue !== null) {
this.isMaxEnabled = ko.observable(true);
this.maxValue = ko.observable(variable.maxValue);
} else {
this.isMaxEnabled = ko.observable(false);
this.maxValue = ko.observable(null);
}
if (variable.step !== null) {
this.isStepEnabled = ko.observable(true);
this.stepValue = ko.observable(variable.step);
} else {
this.isStepEnabled = ko.observable(false);
this.stepValue = ko.observable(null);
}
if (variable.defaultValue !== null) {
this.value = ko.observable(variable.defaultValue);
} else {
this.value = ko.observable(0);
}
if (this.typeAndFormat() === DataTypeFormat.NumberPercentage) {
this.value(this.value() * 100);
if (this.isMinEnabled()) this.minValue(this.minValue() * 100);
if (this.isMaxEnabled()) this.maxValue(this.maxValue() * 100);
if (this.isStepEnabled()) this.stepValue(this.stepValue() * 100);
}
[...]
this.isMinEnabled.subscribe((v) => { if (v !== true) this.minValue(null) }, this);
this.isMaxEnabled.subscribe((v) => { if (v !== true) this.maxValue(null) }, this);
this.isStepEnabled.subscribe((v) => { if (v !== true) this.stepValue(null)}, this);
[...]
}
public getModifiedVariable() {
[...]
this.originalData.typeAndFormat = this.typeAndFormat();
this.originalData.minValue = this.minValue();
this.originalData.maxValue = this.maxValue();
this.originalData.step = this.stepValue();
this.originalData.defaultValue = this.value();
[...]
if (this.typeAndFormat() === DataTypeFormat.NumberPercentage) {
this.originalData.defaultValue = this.originalData.defaultValue / 100;
if (this.isMinEnabled()) this.originalData.minValue = this.originalData.minValue / 100;
if (this.isMaxEnabled()) this.originalData.maxValue = this.originalData.maxValue / 100;
if (this.isStepEnabled()) this.originalData.step = this.originalData.step / 100;
}
[...]
return this.originalData;
};
[...]
}
The second viewmodel that has even more validation and restrictions looks even worse... I don't really know how I could abstract that so that it would be readable for me and for others.
There are two different problems
you need a custom visualization, that needs formatting, and a custom data input, that needs parsing
you need to add some business logic (validation of values)
The first question can be solved by using an extender. With this technique your observable must store the actual value, not the formatted value. You can use it to add a child observable, which could be called formattedValue. This must be a writable computed observable, which two functions:
read: format the underlying actual value, and return it formatted, so that the user has a beautiful view of the value
write: parse the value received from the user input, and store the result in the underlying actual value
You can find examples of extenders like theses ones: Three Useful Knockout Extenders. The extenders can recevie parameters, so that they can be configured individually (in your case you can set percentage, steps, and so on). Another big example of this technique is the ko.valdiation library.
If you use this technique, in the HTML you need to bind the child observable, instead of the underlying observable with the real value, i.e.:
<input type="text" data-bind="value: vm.someValue.formattedValue"/>
As explained, the formattedValue is a new child observable which formats/parses the value.
The second question can also be solved with writable computed observables. You can add the validation logic in the write method, so that any time the value is modified, it's validated, and rejected or corrected, depending on what you want to do. The computed observable can access other values from the view model, so its implementation should be easy. Of course, the validation logic must access the observables with the actual values. I.e it can completely ignore if the observable is extended or not.
The great advantage of this implementation is that you can implement an test each required functionality independently:
implement and test the parsing/formatting extenders, the format/parse in the extenders
implementa and test the the business logic in the writable computed observables
Once implemented an tested, start using them together.
I'm trying to do some calculations through knockout.js but I'm getting a NaN error.
This is the code:
function AppViewModel() {
aanvoerSl25mm = ko.observable();
lengtePrijs125mm = 1;
totaalAanvoer = parseInt(aanvoerSl25mm*lengtePrijs125mm);
}
ko.applyBindings(new AppViewModel());
totaalAanvoer gives me NaN as output.
Here is a fiddle:
http://jsfiddle.net/FullContCoder/3TUsp/2/
Any help would be fantastic. Thanks!
There are a few problems:
As attila said, aanvoerSl25mm is a function (observables are functions), so you need to get the value out using aanvoerSl25mm()
You haven't provided aanvoerSl25mm with a value, so its current value is undefined. You should create it with a value such as aanvoerSl25mm = ko.observable(0);
You are only calculating the value once (immediately). What you want to do is to make a computed function (see the documentation here). This will mean the computed value will change automatically when the observable changes. For example:
totaalAanvoer = ko.computed(function() {
return parseInt(aanvoerSl25mm()*lengtePrijs125mm);
});
I want to have a Backbone model with float attributes in it but without worrying too much about variable types.
I would like to encapsulate the value parsing right there in the model so I am thinking of overriding the set function:
var Place = Backbone.Model.extend({
set: function(attributes, options) {
if (!_.isEmpty(attributes.latitude)){
attributes.latitude == parseFloat(attributes.latitude);
}
if (!_.isEmpty(attributes.longitude)){
attributes.longitude == parseFloat(attributes.longitude);
}
Backbone.Model.prototype.set.call(this, attributes, options);
}
});
However this seems cumbersome, since I would have a similar logic in the validate method and potentially repeated across multiple models. I don't think the View should take care of these conversions.
So what is the best way of doing it?
Use a validation plugin for your model so that you can validate the input in a generic fashion.
There are several out there including one that I have written:
Backbone.Validator
Backbone.Validation
Then you don't worry about performing data validation anywhere else - your model does it and sends out and error message you can listen for and provide appropriate feedback.
Also, a lat/lng pair can, in rare circumstances, be an integer, such as Greenwich England: 0,0 or the north pole: 90,180. And since JavaScript only has "number" any valid input for parseFloat is also valid for parseInt.
But parseFloat will always return a float.
My solution was to replace Backbone.Model.prototype.set with a preprocessor proxy:
/**
* Intercept calls to Backbone.Model.set and preprocess attribute values.
*
* If the model has a <code>preprocess</code> property, that property will be
* used for mapping attribute names to preprocessor functions. This is useful
* for automatically converting strings to numbers, for instance.
*
* #param Backbone
* the global Backbone object.
*/
(function(Backbone) {
var originalSet = Backbone.Model.prototype.set;
_.extend(Backbone.Model.prototype, {
set: function(key, val, options) {
if(!this.preprocess) {
return originalSet.apply(this, arguments);
}
// If-else copied from Backbone source
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
for(attr in this.preprocess) {
if(_.has(attrs, attr)) {
attrs[attr] = this.preprocess[attr](attrs[attr]);
}
}
return originalSet.call(this, attrs, options);
},
});
})(Backbone);
After this, models with a preprocess property will use it to map attribute names to preprocessor functions. For instance, preprocess: { age: parseInt } means that whenever the age attribute is set, the value will be passed through parseInt before actually setting it. Attributes with no corresponding preprocess entry will not be affected.
Example usage:
var Thing = Backbone.Model.extend({
preprocess: {
mass: parseInt,
created: function(s) { return new Date(s); },
},
});
var t = new Thing({
label: '42',
mass: '42',
created: '1971-02-03T12:13:14+02:00',
});
console.log(t.get('label')+3); // 423
console.log(t.get('mass')+3); // 45
console.log(t.get('created').toLocaleString('ja-JP', { weekday: 'short' })); // 水
Pros
The functionality is available in all models without needing to duplicate code
No need to send { validate: true } in every call to set
No need to duplicate preprocessing in validate, since this happens before validate is called (this might also be a con, se below)
Cons
Some duplication of Backbone code
Might break validation since preprocessing happens before validate is called. JavaScript parsing methods usually return invalid values instead of throwing exceptions, though (i.e. parseInt('foo') returns NaN), so you should be able to detect that instead.