How can I compute two dates using Knockout JS? - javascript

I'm trying to use Knockout computed to concatenate two dates as I change them.
From the examples, it seems I do not need to use valueUpdate: 'input'. But nothing is happening when I change the dates (using Bootstrap datepicker). Any ideas to what I'm missing?
Here's my fiddle.
And my code:
<div class="input-append date">
<input type="text" data-bind="value: fromDate, valueUpdate: 'input'" class="date from-date" />
<span class="add-on">to</span>
<input type="text" data-bind="value: toDate, valueUpdate: 'input'" class="date to-date" />
</div>
Dato: <span class="date" data-bind="date"></span>
function dateModel() {
var self = this;
self.fromDate = ko.observable('12.09.2014');
self.toDate = ko.observable();
self.validPeriod = ko.computed(function () {
return self.fromDate + " - " + self.toDate;
}, self);
}
ko.applyBindings(dateModel());

Because your date picker objects are of type koObservable, you need to treat the objects as functions, so your validPeriod function should look like:
self.validPeriod = ko.computed(function () {
return self.fromDate() + " - " + self.toDate();
}, self);
The Knockout.js documentation for observables states:
To read the observable’s current value, just call the observable with
no parameters. In this example, myViewModel.personName() will return
'Bob', and myViewModel.personAge() will return 123.
Then i would suggest using the text data binding for the date span element to call the calculation function:
<span class="date" data-bind="text: validPeriod"></span>
Thanks to the comment of #Buck Doyle and some research, it seems that Knockout.js needs special handling regarding datetime picker controls, as showed in this SO post - jQuery UI datetimepicker and Knockout.js - one possible solution to your problem would be to implement the custom datetime picker handling for KOjs.
This page - custom bindings with KOjs explains very good (with example) how to bind the datetime picker control.

Related

Attach validation rule to form field programmatically

I am using VeeValidate to do some validation on a form made with Vue.js. I have it set up to display a span with the error message related to the input where the error occurred.
<div class="input-group">
<input type="date"
class="form-control"
name="panelData.AnalysisDate"
data-vv-as="Analysis Date"
v-model="panelData.AnalysisDate"
v-validate="'required|date_format:YYYY-MM-DD'">
</div>
<span v-show="errors.has('panelData.AnalysisDate')" class="redText">{{errors.first('panelData.AnalysisDate')}}</span>
All of the inputs are set up the same way and they are all working correctly.
The issue arises when I try to add a validation rule to the above input that requires a date-between rule that uses a year from today's date as the max value.
date_between:{min,max}
The v-validate attribute takes in a string of the validation rules delimted by a |. There is a way to dynamically add rules via the validator instance that gets automatically attached to the Vue instance.
$validator.attach({field}, {rules list}, {options})
I tried to do the code below in both the 'created' and 'mounted' life cycle hooks and neither yielded the results I am looking for.
var today = new Date();
var yearFromToday = new Date(today.getFullYear() + 1, today.getMonth(), today.getDate());
var yearFromTodayStr = yearFromToday.toISOString().substring(0, 10);
//'this' refers to the current view instance
//'panelData' is the name of an object in my component's data object
this.$validator.attach('panelData.AnalysisDate', 'date_between:2001-01-01,' + yearFromTodayStr, {
prettyName: 'Analysis Date'
});
The annoying thing is, the code works because if I use the console (chrome) to insert my code, it gives me the desired results once everything is rendered on the screen. I am not sure if I am using the correct lifecycle hooks.
The way I got around this feels hacky but I couldn't get it to work with my original approach.
For date fields that required a dynamic range I ended up using the directive style rules string and concatenated a computed property.
For example:
computed: {
ninetyNineYearsAgo() {
return new Date().getFullYear() - 99;
},
eighteenYearsAgoFormatted() {
let eighteenYearsAgo = new Date().getFullYear() - 18;
let todayISODate = new Date().toISOString().split('T')[0];
return eighteenYearsAgo + '-' + todayISODate.split('-')[1] + '-' + todayISODate.split('-')[2];
}
}
<div class="input-group">
<input type="date"
class="form-control"
name="panelData.AnalysisDate"
data-vv-as="Analysis Date"
v-model="panelData.AnalysisDate"
v-validate="'date_format:YYYY-MM-DD|date_between:' + ninetyNineYearsAgo +'-01-01,'+ eighteenYearsAgoFormatted + ',true'">
</div>

Two way binding using value set by AJAX - Observable doesn't notify when setting control's value

So, I have a simple page below where the rate field is set by AJAX. However the other calculated fields do not seem to get updated in the view model by KnockoutJS unless I type in the actual value for rate. How can this be resolved?
HTML:
<div>
Date: <input type="text" data-bind="value: date" id="txtDate" /><br />
Enter Amount: <input type="text" data-bind="value: amount" /><br />
Rate: <input type="text" data-bind="value: rate" id="txtRate" /><br />
VAT: <input type="text" data-bind="value: vat" /><br />
Total Amount: <input type="text" data-bind="value: totalAmount" />
</div>
JS
<script type="text/javascript">
$(function(){
$('#txtDate').change(function(){
$.getJSON('/api/Common/GetRate', { param: $(this).val() }).success(function (data) {
$('#txtRate').val(data);
});
});
});
function AppViewModel() {
var self = this;
self.date = ko.observable('');
self.amount = ko.observable(0);
self.rate = ko.observable(0);
self.vat = ko.pureComputed(function () {
return self.amount() * 0.1;
}, this);
self.totalAmount = ko.pureComputed(function () {
return self.amount() + self.vat();
}, this);
}
ko.applyBindings(new AppViewModel());
</script>
You're mixing up jQuery and Knockout, which is always a bad idea.
On the ajax call success, update the observable value, which is the KO way. To do so you need to create the view model before doing the ajax call, and store it in a variable, for example:
var vm = AppViewModel();
Then, on the AJAX call success callback, update the observable value, i.e.:
vm.rate(data);
You should always do it in this way: update the bound value, and the control will reflect the changes. As you've seen trying to do it the other way round is problematic, apart from more difficult.
It doesn't matter if you apply the binding before of after the callback.
And a final note: when you define computed observable the second parameter is to bind the callback function to it. I.e. it will be the value of this inside the function. So, it would make sense to do this:
self.vat = ko.pureComputed(function () {
return this.amount() * 0.1;
}, self);
but what you're doing is pointless. You can omit that second parameter if you don't use this inside the computed function.
NOTE: apart from being a bad idea, the reason why updating the value using jQuery doesn't work is because knockout bindings use events to be able to update observables. When you use jQuery to set the value, no events are triggered, so the observable isn't updated. Please, see the docs for value binding, and pay special attention to valueUpdate parameter description.

Knockout.js validation

I'm new to Knockout.js tech where I Googled many site to resolve my situation where I couldn't find a better option.
How to validate a date using Knockout.js? Where in my case if the user typed the date in a wrong format say for e.g if the valid date format is dd-M-yyyy but the user is typing M-dd-yyyy it should automatically convert it to a valid date format.
My sample code is this,
self.dateOfBirth = ko.observable(0).extend({
required: { message: 'Date of Birth is required', params: true },
date: { format: 'dd-M-YYYY' ,
message: 'Not a valid date format',params:true
}
My HTML design is this,
<input class="form-control" id="dateOfBirth" autocomplete="off" placeholder="Date of Birth" type="text" data-bind="value: dateOfBirth, format:'dd-M-YYYY', valueUpdate: 'afterkeydown'">
Take a look at "Writable computed observables" example 3 and example 4 on the official Knockout documentation site.
Example:
this.formattedDate = ko.pureComputed({
read: function () {
return this.date();
},
write: function (value) {
// convert date to dd-M-YYYY, then write the
// raw data back to the underlying "date" observable
value = convertDate(value); // add your own conversion routine
this.date(value); // write to underlying storage
},
owner: this
});
Also consider using the textInput binding, instead of value combined with valueUpdate, for consistent cross-browser handling.
Consider using Knockout event and capture its change event and then use moment.js library to convert it into any date format you like.
In HTML:
<input class="form-control" id="dateOfBirth" autocomplete="off" placeholder="Date of
Birth" type="text" data-bind="value: dateOfBirth, event: { change: dataChanged},
valueUpdate: 'afterkeydown'">
In javascript:
Inside your viewModel
//this function will be called when the date will be changed
self.dataChanged = function(){
//using moment .js here change the format to dd-M-YYYY as desired
var validFormat = moment(self.dateOfBirth()).format('dd-M-yyyy');
self.dateOfBirth(validFormat);
}
for further details check moment.js liberary

Javascript - loop through datepickers and set date

I don't know Javascript at all. I have a form where there can be any number of datepicker textboxes. When the user selects a date in the first datepicker textbox, then I want all the remaining datepicker textboxes to have that same date.
Does this require a function?
Edit: I tried to create a function, but I don't know javascript at all!
function UpdateValuationDates(event) {
$valuationDatePicker = $(event.target);
var valuationDate = $valuationDatePicker.datepicker("getDate");
if (valuationDate != null) {
//loop through all items
document.getElementById("dateValuationDate").Text
$valuationDatePicker.datepicker("setDate", valuationDate);
$valuationDatePicker.trigger('change');
}
}
So I think this can be ignored. I have also read that there is a datepicker on selected event:
$(".date").datepicker({
onSelect: function(dateText) {
display("Selected date: " + dateText + "; input's current value: " + this.value);
}
});
So I guess I need to edit this code to populate the rest of the textboxes, but how to find out at runtime how many there are?
The HMTL has a repeater with the datepicker repeated x number of times:
<abc:DatePicker runat="server" ID="dateValuationDate"
With the help of html's input type=date and some basic classes' knowledge, you can do that.. Considering you have following Date pickers:
<input type="date" class="dateTime">
<input type="date" class="dateTime">
<input type="date" class="dateTime">
Now you simply need to listen to a change in any one of there values:
$(".dateTime").on("change", function(){
and when the change occurs, get the changed value and set all other date pickers to that new value:
$(".dateTime").val($(this).val());
So it'll be something like this:
$(document).ready(function(){
$(".dateTime").on("change", function(){
$(".dateTime").val($(this).val());
});
});
See the DEMO here
EDIT: Considering you're new to JavaScript, here's how i'm getting the reference to all those elements, through .className, as they all have same class name so for each event (change, update value) they all will be referenced.

Knockout computed gives Function expected error in IE only

I'm getting a "SCRIPT5002: Function expected" that only happens in IE. I'm currently testing against version 9. It happens when I use a previously defined computed observable inside of another computed observable.
My application is a bit more complex than this, so I've reproduced the error with the much simpler code below. The error happens on the line z = self.subtotal(); when you enter a number in for Number 1, Number 2, and Number 3 (and tab out).
This error does not occur in Chrome or Firefox and I've googled for quite a while. Hopefully someone can help un-stick me.
Here is the link to the jsfiddle: http://jsfiddle.net/kCmTg/
Here is the javascript:
function putVars() {
self = this;
self.number1 = ko.observable();
self.number2 = ko.observable();
self.subtotal = ko.computed(function () {
return parseFloat(self.number1()) + parseFloat(self.number2());
}, self, { deferEvaluation: true });
self.number3 = ko.observable();
self.number4 = ko.observable();
self.total = ko.computed(function () {
var x, y, z;
x = self.number3();
y = self.number4();
z = self.subtotal();
return parseFloat(x) + parseFloat(y) + parseFloat(z);
}, self, { deferEvaluation: true });
}
$(function () {
ko.applyBindings(new putVars());
});
Here is the html:
<h4>Calc 1</h4>
<label for="Number1">Number 1: </label><input id="Number1" type="text" data-bind="value: number1" />
<label for="Number2">Number 2: </label><input id="Number2" type="text" data-bind="value: number2" />
<label for="Subtotal"><b>Subtotal: </b></label><input id="Subtotal" type="text" data-bind="value: subtotal" readonly="readonly" />
<hr />
<h4>Calc 2</h4>
<label for="Number3">Number 3: </label><input id="Number3" type="text" data-bind="value: number3" />
<label for="Number4">Number 4: </label><input id="Number4" type="text" data-bind="value: number4" />
<label for="Total"><b>Total:</b> </label><input id="Total" type="text" readonly="readonly" data-bind="value: total" />
This has a similar cause to this one: knockout.js Computed observable called twice in Internet Explorer and is caused by the fact that in IE<10, Knockout has some special code to deal with getting an autocomplete value from the field. It does this even if the field is read-only as in your case. It does check, however, for the autocomplete attribute. So you could fix it like this:
<input id="Subtotal" type="text" data-bind="value: subtotal" autocomplete="off" readonly="readonly" />
There is also a bug in Knockout at play here--that it will overwrite a computed observable passed to a two-way binding. This is already fixed in the development version of Knockout to be released as version 2.3.0 (probably in April 2013). To work around this, you can pass the value of the observable to the binding instead of the computed observable itself, like this:
<input id="Subtotal" type="text" data-bind="value: subtotal()" readonly="readonly" />
It appears you've discovered a bug in IE or KnockoutJS, likely exposed by Knockout's bindings, where Knockout is pushing a value into an observable, but in IE9, it overwrites the property.
This doesn't occur on IE10, suggesting it's a bug in IE9. I'd guess Knockout has something where it's checking if some value is a writable observable function and it's being reported incorrectly on IE9.
Interestingly, if you change KO computed to use a read/write, the error ceases:
self.subtotal = ko.computed({
read: function() {
return parseFloat(this.number1()) + parseFloat(this.number2());
},
write: function(val) { }
}, self);
Perhaps that is a sufficient work-around?
Please check to see if you observable has a value being passed as a parameter or not. for example budgetlineviewmodel.total = total(5). I have tried using budgetlineviewmodel.total = 5. but with no success.
Knockout Observables are nothing but functions internally and have you have to pass a value inside brackets which internally return the same value.
Chrome is lenient in allowing budgetlineviewmodel.total() to tolerate null value when no parameter is passed.
hope this helps.
Cheers !
budgetlineviewmodel.Save = function () {
var currentTotalAmt = $('#txtTotal').val();
budgetLineViewModel.Total(currentTotalAmt);
}

Categories

Resources