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

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

Related

How do I set "no value" on the server to be compatible with Backbone?

What I mean by this is I want to use the defaults object literal of Models, which calls this code from underscore:
Temp Code
var Feed = Backbone.Model.extend({
defaults: {
name: '',
picture: '', // I need this set when picture is 0
time: '',
tweet: ''
// h_file, view_picture
}
});
From Underscore
_.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) {
if (source) {
for (var prop in source) {
if (obj[prop] === void 0) obj[prop] = source[prop];
}
}
});
return obj;
};
It will only set a default if a value is set to undefined. However JSON does not support sending undefined, nor do I see this as default value in the mysql table I use.
Obviously, I can use an intermediary value, which is what I'm currently doing. I just store the number 0 as a default for no value and send this via JSON, and then just do a basic if/else.
But this is in-efficient and I would imagine there is a better 1 to 1 correlation I could use.
I would use the built in parse method otherwise I do not see a way around it. It's a difficult predicament, because what would you consider a value that would result in your default value being set? I came up with a quick solution that will filter out ALL values that are considered falsy. You can build on the below.
parse: function(data) {
// This should return values that are truthy
// So for eg if a property has a value of 0 or null it will not be returned,
// thus leaving in your default values
return _.filter(data, function(obj) { return obj; });
}
I found a request for it here as well https://github.com/jashkenas/underscore/issues/157

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

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 Two-way Binding: Number Formatted as String

I have a bunch of amounts in my view model which are observables, and I want them stored as numbers because they are used in several calculations. However, when I bind them to textboxes in my view, I want them shown as a specially formatted string (1234.5678 => "1,234.57"). What is the best way to accomplish two-way binding in this situation, since I can't just use the value binding with my observable?
The user needs to be able to enter "1,234.56" or "1234.56" in the textbox which will store the number value 1234.56 in the observable, and if I change the number value (3450) via javascript, the textbox value needs to be updated to the new value, but formatted as a string ("3,450").
I appreciate the help!
I would implement something like the following:
JavaScript:
function ViewModel() {
var self = this;
self.dollarAmount = ko.observable();
self.formattedDollarAmount = ko.computed({
read: function() {
return applyFormat(self.dollarAmount()); // pseudocode
},
write: function(value) {
var parsedDollarAmount = parseFloat(value);
if(!isNaN(parsedDollarAmount)) {
self.dollarAmount(parsedDollarAmount);
}
else {
// recommend adding validation of some sort
// so getting here is less likely
alert("invalid dollar amount");
}
},
owner: self
});
}
HTML:
<input type="text" data-bind="value: formattedDollarAmount" />
See the documentation on computed observables for more info: http://knockoutjs.com/documentation/computedObservables.html
see :
http://jsfiddle.net/Ty8PG/14/
ko.bindingHandlers.numericValue = {
init : function(element, valueAccessor, allBindingsAccessor) {
var underlyingObservable = valueAccessor();
var interceptor = ko.computed({
read: underlyingObservable,
write: function(value) {
underlyingObservable(rawNumber(value));
}
});
ko.bindingHandlers.value.init(element, function() { return interceptor }, allBindingsAccessor);
},
update : function(element, valueAccessor, allBindingsAccessor) {
element.value = number_format(valueAccessor()(), 2);
}
};
Original POST :
https://groups.google.com/forum/#!topic/knockoutjs/gvhJt4iSOLk

Backbone.js - custom setters

Imagine a simple backbone model like
window.model= Backbone.Model.extend({
defaults:{
name: "",
date: new Date().valueOf()
}
})
I'm trying to find a way to always make the model store the name in lower-case irrespective of input provided. i.e.,
model.set({name: "AbCd"})
model.get("name") // prints "AbCd" = current behavior
model.get("name") // print "abcd" = required behavior
What's the best way of doing this? Here's all I could think of:
Override the "set" method
Use a "SantizedModel" which listens for changes on this base model and stores the sanitized inputs. All view code would then be passed this sanitized model instead.
The specific "to lower case" example I mentioned may technically be better handled by the view while retrieving it, but imagine a different case where, say, user enters values in Pounds and I only want to store values in $s in my database. There may also be different views for the same model and I don't want to have to do a "toLowerCase" everywhere its being used.
Thoughts?
UPDATE: you can use the plug-in: https://github.com/berzniz/backbone.getters.setters
You can override the set method like this (add it to your models):
set: function(key, value, options) {
// Normalize the key-value into an object
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
attrs = {};
attrs[key] = value;
}
// Go over all the set attributes and make your changes
for (attr in attrs) {
if (attr == 'name') {
attrs['name'] = attrs['name'].toLowerCase();
}
}
return Backbone.Model.prototype.set.call(this, attrs, options);
}
It would be a hack, because this isn't what it was made for, but you could always use a validator for this:
window.model= Backbone.Model.extend({
validate: function(attrs) {
if(attrs.name) {
attrs.name = attrs.name.toLowerCase()
}
return true;
}
})
The validate function will get called (as long as the silent option isn't set) before the value is set in the model, so it gives you a chance to mutate the data before it gets really set.
Not to toot my own horn, but I created a Backbone model with "Computed" properties to get around this. In other words
var bm = Backbone.Model.extend({
defaults: {
fullName: function(){return this.firstName + " " + this.lastName},
lowerCaseName: function(){
//Should probably belong in the view
return this.firstName.toLowerCase();
}
}
})
You also listen for changes on computed properties and pretty much just treat this as a regular one.
The plugin Bereznitskey mentioned is also a valid approach.

Categories

Resources