Knockout min/max validation not working - javascript

I am using a custom binding handler that I found at https://www.moonlightbytes.com/blog/useful-knockout-js-binding-handlers
It works very well to format input as currency. However, it also stops my Knockout min/max validation from working. I need a min of 1 and max of 200. Does anyone why this is occuring?
Custom Binding
function formatCurrency(symbol, value, precision) {
return (value < 0 ? "-" : "") + symbol + Math.abs(value).toFixed(precision).replace(/(\d)(?=(\d{3})+\.)/g, "$1,");
}
function rawNumber(val) {
return Number(val.replace(/[^\d\.\-]/g, ""));
}
ko.bindingHandlers.currency = {
symbol: ko.observable("$"),
init: function (element, valueAccessor, allBindingsAccessor) {
//only inputs need this, text values don't write back
if ($(element).is("input") === true) {
var underlyingObservable = valueAccessor(),
interceptor = ko.computed({
read: underlyingObservable,
write: function (value) {
if (value === "") {
underlyingObservable(null);
} else {
underlyingObservable(rawNumber(value));
}
}
});
ko.bindingHandlers.value.init(element, function () {
return interceptor;
}, allBindingsAccessor);
}
},
update: function (element, valueAccessor, allBindingsAccessor) {
var symbol = ko.unwrap(allBindingsAccessor().symbol !== undefined ? allBindingsAccessor().symbol : ko.bindingHandlers.currency.symbol),
value = ko.unwrap(valueAccessor());
if ($(element).is("input") === true) {
//leave the boxes empty by default
value = value !== null && value !== undefined && value !== "" ? formatCurrency(symbol, parseFloat(value), 2) : "";
$(element).val(value);
} else {
//text based bindings its nice to see a 0 in place of nothing
value = value || 0;
$(element).text(formatCurrency(symbol, parseFloat(value), 2));
}
}
};
ViewModel observable
self.PriceAdvanced = ko.observable("").extend({ required: true, min: 1, max: 200 });
Html
<input class="form-control max225" type="text" id="PriceAdvanced" name="PriceAdvanced" data-bind="currency: PriceAdvanced" size="23" placeholder="$0.00" />

Found the answer here, and it worked perfectly:
First, Create the custom binding, such as...
ko.bindingHandlers.yourBindingName = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// This will be called when the binding is first applied to an element
// Set up any initial state, event handlers, etc. here
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// This will be called once when the binding is first applied to an element,
// and again whenever any observables/computeds that are accessed change
// Update the DOM element based on the supplied values here.
}
};
... then register the custom binding with knockout validation:
ko.validation.makeBindingHandlerValidatable('yourBindingName');

Related

NotifySubscribers is not refreshing UI (Writable Computed)

I have a writable computed inside a custom binding to format an observable. However, when the users remove the formatted mask, the read function wont trigger, since the observable is removing all non digits, ok, its working as it should be, so i added the notifySubcribers to call read everytime, even if the observable value didnt change and its working, but the ui element isnt refreshing the new value (read return)
Follow the code:
ko.bindingHandlers.conta = {
init : function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var observable = valueAccessor(), formatted = ko.computed({
read : function() {
var val = ko.unwrap(valueAccessor());
if (!val) return val;
if (val.toString().length < 6) {
val = zeroPad(val, 6);
observable(val);
}
return val.toString().slice(0, val.toString().length - 1) + "-" + val.toString().slice(val.toString().length - 1);
},
write : function(value) {
if (value) {
value = zeroPad(value, 6);
}
observable(value.replace(/\D/g, ''));
observable.notifySubscribers();
}
});
if ($(element).is('input')) {
ko.applyBindingsToNode(element, {
numbersOnly : true,
maxLength : 10,
value : formatted
});
} else {
ko.applyBindingsToNode(element, {
numbersOnly : true,
text : formatted
});
}
return {
controlsDescendantBindings : true
};
}
};
Can someone give me some directions here?
Thanks :D
Extend the formatted computed obs with notify: always:
ko.bindingHandlers.conta = {
init : function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var observable = valueAccessor(), formatted = ko.computed({
...
}).extend({ notify: 'always' });
...
This will force it to notify always even if there is no changes.

Knockout custom binding doesn't update the computed function

I have a custom binding for an html editable field..
I changed it to use another custom binding now (HtmlValue), because EditableText had an error when updating the values (both custom bindings are included in the jsfiddle).
Anyone knows how to fix this?
This is the code that doesn't update the value:
ko.bindingHandlers.htmlValue = {
init: function (element, valueAccessor, allBindingsAccessor) {
ko.utils.registerEventHandler(element, "keyup", function () {
var modelValue = valueAccessor();
var elementValue = element.innerHTML;
if (ko.isWriteableObservable(modelValue)) {
modelValue(elementValue);
}
else { //handle non-observable one-way binding
var allBindings = allBindingsAccessor();
if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers'].htmlValue) allBindings['_ko_property_writers'].htmlValue(elementValue);
}
}
)
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()) || "";
if (element.innerHTML !== value) {
element.innerHTML = value;
}
}
};
You can try it out here: http://jsfiddle.net/DMf8r/
There is a bunch of problems with the way the view model is constructed and with the bindings themselves...
The tax_total computed should be declared after lines because it accesses lines and Knockout executes tax_total as soon as the computed is created.
this needs to be passed into the computed so that this inside the computed is the view model
elem needs to be defined in the $.each() call
To loop the underlying array in $.each(), you need to use this.lines() instead of this.lines
The values inside lines need to be observables, otherwise the computed would not be notified of changes.
The span is using a value binding, it should be text.
There might have been more problems but it's hard to keep track of what all the changes were...
this.lines = ko.observableArray([
{ unit_price: ko.observable(5.0), tax_rate: ko.observable(21.00) },
{ unit_price: ko.observable(5.0), tax_rate: ko.observable(21.00) }]);
this.add_line = function () {
this.lines.push({ unit_price: ko.observable(5.0), tax_rate: ko.observable(21.00) });
}.bind(this);
this.tax_total = ko.computed(function () {
var total = 0; //this.subtotal()
$.each(this.lines(), function (index, elem) {
total += (elem.unit_price() * (elem.tax_rate() / 100));
});
return total;
}, this);
<span data-bind="text: tax_total">1.02</span>
Fiddle: http://jsfiddle.net/DMf8r/1/

Using Knockout.js how do bind a Date property to a HTML5 date picker?

(this only works in Chrome at the moment as most browsers don't yet implement date picker for input type="date")
In the following example MyDate starts out as a Date object with the current date, but this isn't picked up by the date input (which expects its format to be a string in format YYYY/MM/DD).
Once you've picked a date in the picker then MyDate becomes a string in format above.
How can you bind this so MyDate stays a javascript Date and is interpreted by the input control correctly?
See See http://jsfiddle.net/LLkC4/3/ :-
<input data-bind="value : MyDate" type="date">
<hr>
<span data-bind="html: log" />
<script>
var viewModel = {
MyDate : ko.observable(new Date()),
log : ko.observable(""),
logDate : function () {
this.log(this.log() + this.MyDate() + " : " +
typeof(this.MyDate()) + "<br>");
}
};
viewModel.MyDate.subscribe(function (date) {
viewModel.logDate();
});
ko.applyBindings(viewModel);
viewModel.logDate()
</script>
While #amakhrov answer will work (and would be even better if used writeable computed observable like sujested by #Stijn) I decided to do this using Custom Bindings.
The main advantage of doing this is reusability - I just have to use data-bind="datePicker : MyDate" whereever I want to tie this in. I can also modify other properties of the input element so this could be really useful if binding to complex jQuery (and other) controls.
(Read here for more pro/cons about the 3 choices to do this sort of thing)
HTML
<input data-bind="datePicker : MyDate" type="date">
JS
ko.bindingHandlers.datePicker = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
// Register change callbacks to update the model
// if the control changes.
ko.utils.registerEventHandler(element, "change", function () {
var value = valueAccessor();
value(new Date(element.value));
});
},
// Update the control whenever the view model changes
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = valueAccessor();
element.value = value().toISOString();
}
};
var viewModel = {
MyDate : ko.observable(new Date())
};
ko.applyBindings(viewModel);
See http://jsfiddle.net/LLkC4/5/
You can use the computed vartiable for the date object in your model:
In html:
<input data-bind="value : rawDate" type="date">
In code:
var currentDate = (new Date()).toISOString().split('T')[0];
// this is used instead of MyDate in the data binding
rawDate : ko.observable(currentDate),
...
// and then set up the dependent variable
viewModel.MyDate = ko.computed(function () {
var val = this.rawDate();
if (typeof val === 'string') val = new Date(val);
return val;
}, viewModel)
Please see the demo: http://jsfiddle.net/gcAXB/1/
Here's a solution that is working for me with the latest knockoutjs, based off of the link below and modified to have a custom init function to handle updating ko.computed properties as your date value changes.
Note that utils.formatDate is just a utility function to format the date in whatever string you want, so just replace that with your own date formatting code, whether you use momentjs or something else.
ko.bindingHandlers.date = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
ko.utils.registerEventHandler(element, 'change', function () {
var value = valueAccessor();
if (element.value !== null && element.value !== undefined && element.value.length > 0) {
value(element.value);
}
else {
value('');
}
});
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = valueAccessor();
var valueUnwrapped = ko.utils.unwrapObservable(value);
var output = '';
if (valueUnwrapped !== null && valueUnwrapped !== undefined && valueUnwrapped.length > 0) {
output = utils.formatDate(valueUnwrapped);
}
if ($(element).is('input') === true) {
$(element).val(output);
} else {
$(element).text(output);
}
}
};
<div>
<label>Date of Birth:</label>
<input type="text" data-bind="date: dateOfBirth, format: 'DD MMM YYYY'" />
</div>
BINDING AND FORMATTING DATES USING KNOCKOUT AND MOMENT JS
These days is so much easier with Moment.js
this.sessionDate = ko.observable(moment().format('YYYY-MM-DD'));
this.getFormattedDate = () => { return moment(this.sessionDate()'YYYY-MM-DD').format('MM/DD/YYYY') }; // Note this is ES2015 syntax
In your html you can bind it with
<input class="form-control" name="date" type="date" id="date" data-bind="value: sessionDate">
And display it formatted as
<p data-bind="text : getFormattedDate()">Loading Date</p>
No need to create custom bindings, and you can use a shim for older browsers.
The same as this custom binding, but using momentJS:
ko.bindingHandlers.datePicker = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
// Register change callbacks to update the model
// if the control changes.
ko.utils.registerEventHandler(element, "change", function () {
var value = valueAccessor();
value(moment(element.value).format());
});
},
// Update the control whenever the view model changes
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = valueAccessor();
element.value = moment(value()).format("YYYY-MM-DD");
}
};
Based off of Ryan's answer above, this works a little nicer with newer ko/chrome widgets. It also strips the time part of the date.
ko.bindingHandlers.datePicker = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
// Register change callbacks to update the model
// if the control changes.
ko.utils.registerEventHandler(element, "change", function () {
var value = valueAccessor();
var target_date = element.valueAsDate;
var truncated = new Date(target_date.getFullYear(), target_date.getMonth(), target_date.getDate());
value(truncated);
});
},
// Update the control whenever the view model changes
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = valueAccessor();
var unwrapped = ko.utils.unwrapObservable(value());
if(unwrapped === undefined || unwrapped === null) {
element.value = '';
} else {
element.valueAsDate = unwrapped;
}
}
};
From HTML 5 - Input type date formatting on iOS
There are two formats at play:
displayed format
internal format exposed to JavaScript and sent to the server
You cannot change the display format. It's up to the browser to decide
how the date is presented to the user (in practice it's determined by
system's locale).
You cannot change the internal format either. It's always ISO8601,
regardless of browser/locale.
You'll have to pre-populate it with that specific format, and you can add a computed observable to parse it into a Date object, so you can read it at other places in your application.
If you also want to write to it from JS, you could set up a writeable computed observable and parse the input to see if it's a string from the input field, or a Date object from your JS.
This worked for me
ko.bindingHandlers.momentDate = {
_parseDateTime: function (element, valueAccessor) {
var value = valueAccessor();
var valueUnwrapped = ko.utils.unwrapObservable(value);
var datetime = moment(valueUnwrapped);
var date = moment($(element).val(), 'YYYY-MM-DD');
datetime = datetime.set({
'year': date.get('year'),
'month': date.get('month'),
'date': date.get('date')
});
value(datetime.toDate());
},
init: function (element, valueAccessor) {
function bind() {
ko.bindingHandlers.momentDate._parseDateTime(element, valueAccessor);
}
$(element).change(bind).blur(bind);
},
update: function (element, valueAccessor) {
var value = valueAccessor();
var valueUnwrapped = ko.utils.unwrapObservable(value);
var date = moment(valueUnwrapped);
$(element).val(date.format('YYYY-MM-DD'));
}
};
<input type="date" data-bind="momentDate: $data.Date" class="form-control"/>
This is probably late but it might help someone out there. I'm using Luxon's DateTime object to easily manage dates and timezones in my application. This works with DateTime objects:
ko.bindingHandlers.dateTime = {
// Register change callbacks to update the model if the control changes.
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
ko.utils.registerEventHandler(element, 'change', function () {
const value = valueAccessor()
const datetime = DateTime.fromFormat(element.value, 'yyyy-MM-dd')
value(datetime)
})
},
// Update the control whenever the view model changes
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
const value = valueAccessor()
element.value = ko.unwrap(value).toFormat('yyyy-MM-dd')
}
}
Usage:
// viewmodel
const today = DateTime.fromFormat(DateTime.now().toFormat('yyyy-MM-dd'), 'yyyy-MM-dd')
this.date = ko.observable(today)
// template
<input type="date" data-bind="dateTime: date">

Knockout custom click binding, return true to prevent click hijack?

So we all know return true manually will allow default click action on element with a click binding, but what if I have custom binding as following:
ko.bindingHandlers.action = {
init: function(element, valueAccessor, allBindingsAccessor, context) {
var options = valueAccessor();
var params = options.slice(1);
//wrap it in function, with parameter binding
var newValueAccessor = function() {
return function() {
options[0].apply(context, params);
};
};
ko.bindingHandlers.click.init(element, newValueAccessor, allBindingsAccessor, context);
}
};
which takes N arguments from a binding:
action: [handle, 'open', $index()]
how does one allow click to go through? return true in handle does not work in this case.
Your actual click handler is defined here:
return function() {
options[0].apply(context, params);
};
Just change it so it returns the value of the provided sub-handler:
return function() {
return options[0].apply(context, params);
};

Invoke custom method on value changed

Is there a way to invoke one time some method on value changed? I created wrapper for bindingHandlers.value that invoke this method:
var update = bindingHandlers.value.update;
bindingHandlers.value.update = function(element, valueAccessor, allBindingAccessor, viewModel) {
var newValue = ko.utils.unwrapObservable(valueAccessor());
var elementValue = ko.selectExtensions.readValue(element);
var valueHasChanged = (newValue != elementValue);
update(element, valueAccessor, allBindingAccessor, viewModel);
if (valueHasChanged) {
myMethod();
}
}
Unfortunatelly when I change some value myMethod is called two times becuase come dependencyObservable is also changed. Any ideas?
If you just want to subscribe to a value changed, you can subscribe to any observable:
var viewModel = { property: ko.observable() };
viewModel.property.subscribe(function(newValue) {
//do stuff
});
To subscribe to all properties of an object you could do something like:
function subscribeAll(viewModel) {
for(var propertyName in viewModel) {
if(viewModel[propertyName].subscribe === 'function') {
viewModel[propertyName].subscribe(function(newValue) {
//do stuff
}
}
}
}

Categories

Resources