I'm wanting to format a date in knockout's template.
The date is currently being returned as
2013-07-04T00:00:00
I would like it to be displayed like
07/04/2013
Here is the binding I'm using
<td data-bind="text: FirstDate">
Are their default formatting properties in Knockout's template?
There is nothing built in regarding date formatting or formatting in general in Knockout. The text binding just converts the property value to string so if you want custom formatting you need to do it yourself.
Working with dates is not so easy in JavaScript so you are probably better with using a third party library like moment.js for this. It is very simple to use and it can format your dates with the format method. There is built in format 'L' for your required Month numeral, day of month, year formatting.
You can use moment js in your view-model or directly in your binding like:
<td data-bind="text: moment(FirstDate).format('L')">
Or you can create a custom binding handler which encapsulates this formatting logic.
Note: Make sure to use () on your FirstDate property if it is an ko.observable inside your data-binding expression to get its value.
I use moment.js in a modified version of Stephen Redd's date extender.
It looks like this, which is a little cleaner than calling a function in a data bind.
<input type="text" data-bind="value: dateOfBirth.formattedDate" />
You can also use MomentJs to create an extender:
ko.extenders.date = function (target, format) {
return ko.computed({
read: function () {
var value = target();
if (typeof value === "string") {
value = new Date(value);
}
return moment(value).format("LL");
},
write: target
});
}
viewmodel:
self.YourDate = ko.observable().extend({ date: true });
http://momentjs.com/docs/#/displaying/format/
Related
I have no experience of using knockout.js. I've inherited a booking form which uses a mixture of knockout and C# ASP.NET. We recently upgraded the database to the latest version of MySQL, which is a lot more particular about the format in which the date is entered than the older version. In most cases I've been able to get around this by tweaking either C# or SQL, but in one part of the form I haven't found a solution. The dates are prepopulated in the form input in dd/mm/yyyy format. I want to change this to yyyy-mm-dd so that I can use date inputs rather than text inputs
There are two form inputs which look like this
<input type="text" size="10" maxlength="10" class="normaltext" data-bind="attr:{id:'returnDate-'+Id},value:FlightInfo.ReturnDate, enable:$root.FlightsAdvanced() == 'true' && !FlightInfo.OwnFlight()">
The other one, obviously, is for the departure date. They appear to be populated from the following function in a javascript file.
function FlightInfo(depDate, retDate) {
this.OwnFlight = ko.observable(false);
this.FirstCode = ko.observable("");
this.SecondCode = ko.observable("");
this.DepartureDate = ko.observable(depDate);
this.ReturnDate = ko.observable(retDate);
this.ReqTransferIn = ko.observable(0);
this.ReqTransferOut = ko.observable(0);
}
If I replace ko.observable(retDate) with a hard coded date, eg 2020-09-09, and submit the form then it works, however if I hard code the same date directly into the text input in place of value:FlightInfo.ReturnDate then it doesn't - the form takes the date in the wrong format from knockout and an error is thrown.
I've tried using moment.js as suggested in this question, but it didn't work for me. How can I reformat depDate and retDate as yyyy-mm-dd
I eventually solved this in the C# layer by changing a few instances of .ToShortDateString() to .ToString("yyyy-MM-dd").
Using moment.js is the right way to achieve this, when creating flightInfo, two observables should be modified like this:
this.DepartureDate = ko.observable(moment(depDate).format('YYYY-MM-DD'));
this.ReturnDate = ko.observable(moment(retDate).format('YYYY-MM-DD'));
if these changes do not reflect in your text input, than you should debug if you have moment.js included and activated in proper manner.
In dhtmlx gantt, you can format input date (from string or any type) into javascript date. This date is used to draw chart. Base on the dhtmlx document, you can replace converter (api_date) by custom function:
I override the function as follow:
gantt.templates.api_date = function(date){
throw "It is called";
};
but, it never is called.
api_date template and config are no longer used. We will update the information in the documentation. Please use xml_date as hadi.mansouri suggested.
I don`t know why this function is not called. I also confuse for this, because official documents of DHMTLX Gantt (as you mentioned, api_date) say it should works.
However I found that if you override xml_date it will work as you want. Although it is named xml_date but it works for json data also.
So could use following snippet:
gantt.templates.xml_date = function(date){
// Your customized code.
// return a Date object.
};
Date objects that are modified using setDate method arent getting updated in template.
In template:
<p>{{date | date:'mediumDate'}}</p>
In component:
nextDay(){
this.date.setDate(this.date.getDate()+1);
}
But when I call nextDay function, the template isnt updated with the new value.
The only way I could get the change detection working was doing this:
nextDay(){
var tomorrow = new Date();
tomorrow.setDate(this.date.getDate()+1);
this.date = tomorrow;
}
Are there a better way to accomplish this same task?
I think that is the right way, to change the reference of the date variable. From the docs here we have:
The default change detection algorithm looks for differences by comparing bound-property values by reference across change detection runs.
So if the date reference remains the same, nothing will happen. You need a new Date reference and that's why the second version of nextDay() works.
If you remove the formatting pipe you will see that still only the second version of nextDay() works.
I have an AngularJS application where I want to edit a Date object that is being exposed by ASP Web API through BreezeJS. When I try to edit this field in a HTML form it doesn't quite work. It seems to edit part of the object, or doesn't bind to the date input type at all.
I am wondering what the best way would be to edit Date fields in HTML forms with BreezeJS. I can't find any resources about how one would solve this problem in a proper way.
My own guesses would be:
Extend the Breeze entity with an extra field that is based on the original date with a getter/setter.
Not using a date field in the WebAPI, but that doesn't feel right.
Code
In my Angular controller I'm getting the object from Breeze through a repository pattern:
datacontext.session.getById(id)
.then(function (data) {
return vm.session = data;
});
After that the vm.session is filled with the Breeze object, like this:
[{
"$id":"1",
"$type":"TestApp.Model.Session, TestApp.Model",
"Id":3,
"Name":"Second session",
"StartDate":"2014-10-12T00:00:00.000",
}]
Now in my HTML form I'm binding like this:
<input type="date" ng-model="vm.session.startDate" placeholder="Start Date">
It's currenyly showing this date as "Sun Oct 12 2014 02:00:00 GMT+0200 (Romance Daylight Time)" if I use a type "text" instead of "date" in the input field.
And the casing of the ng-model field is correct since I'm using this to create camelCase fields:
breeze.NamingConvention.camelCase.setAsDefault();
So this is fairly straightforward... I'll try to make a JSFiddle or Plunker asap
Breeze maintains date properties as JavaScript Date objects when that's what you say they are in your metadata.
Breeze does not have its own Date type.
The HTML5 <input type="date" .../> date picker is not widely supported yet (see here) so I'm assuming you're relying on the Angular directive to help you.
That directive is designed to bind to a string, not a date object. This probably explains why the wheels are coming off.
I am sorely tempted to argue that the Ng directive should be smarter than this. It should do what it takes to bind to a Date object as well as a date string. I just might file that as a bug.
As you discovered, the HTML input tag "just works" if the type is "text". Obviously the date display and data entry are not what you want.
I think the ideal solution would be to have a directive that translates between date object and string value seamlessly. Probably could be done by decorating the existing Ng directive itself. Something to look into; we can always use your help; this is open source :-).
Meanwhile, you can create a defined property on your customSession type constructor.
See "Custom Constructors" in the documentation. Such a property would wrap the startDate property (startDateAsString?), parsing text in and formatting as string on the way out.
Here is a general purpose function to produce a Date wrapper property:
// Example: addDateWrapperProperty(SessionCtor, 'startDate', 'startDateAsString');
function addDateWrapperProperty(ctor, propName, wrapperPropName) {
Object.defineProperty(ctor.prototype, wrapperPropName, {
get: function () { return this[propName].toString(); },
set: function (value) { this[propName] = value; },
enumerable: true,
configurable: true
});
}
Use it to define your startDateAsString wrapper property and then bind to this property when editing (bind to the real property for readonly display).
Because you don't want Breeze to serialize this, add it to the ctor AFTER registering that ctor with Breeze metadata as shown below.
I don't know for certain that this will give you the behavior you're looking for. I'd need to see your jsFiddle or plunkr to be sure. But I think it will do the trick. Let us know!
I'm certainly not saying this is the ultimate answer. I'm not sure what that answer is ... although I'm thinking about a directive as I said.
Hope this gets you out of the jam for now. Sorry it's such a PITA. Who knew Ng wouldn't understand Date objects?
Example
Here's an example extracted from an actual Breeze test:
///// Test it //////
var OrderCtor = function () { }; // do-nothing custom constructor
// register the ctor FIRST, then add the property
manager.metadataStore.registerEntityTypeCtor('Order', OrderCtor);
addDateWrapperProperty(OrderCtor, 'orderDate', 'orderDateAsString');
// create new order with a test date
var testDate = new Date(2014,0,1,13,30); // 1/1/2014 1:30 pm
var order = em.createEntity('Order', {
companyName: "test cust",
orderDate: testDate
});
// confirm can read the date through the wrapper property
expect(order.orderDate.value).to.equal(testDate.value,
"'order.orderDate' should return " + testDate);
expect(order.orderDateAsString).to.equal(testDate.toString(),
"'order.orderDateAsString' should return " + testDate.toString());
var testDate2 = new Date();
// update orderDate by way of the wrapper property
order.orderDateAsString = testDate2.toString();
// confirm the update worked
expect(order.orderDate.value).to.equal(testDate2.value,
"after update, 'order.orderDate' should return " + testDate2);
expect(order.orderDateAsString).to.equal(testDate2.toString(),
"after update, 'order.orderDateAsString' should return " + testDate2.toString());
// confirm it is not a breeze-tracked property
var propInfo = order.entityType.getProperty('orderDateAsString');
expect(propInfo).to.equal(null, "'Order.orderDateAsString' is not known to metadata ");
This might sound odd, but I have a repeater which looks like this.
<tr ng-repeat="task in tasks">
<td>{{getFriendlyDate(date)}}</td>
</tr>
I can put this function on the scope like this.
$scope.getFriendlyDate = function(date){
/* Code to convert ISO date to readable date */
return date;
};
That works fine, but I need to use this function in lots of templates so I don't want to have to copy and paste it into lots of controllers.
One option is to create a service to do this and put a simple wrapper on the scope in each controller, but that's not going to save me much time.
Another option is to put the function on the root scope and access it directly in my templates, but I'd prefer it to live in my utilities library (I have a $utils library created using a factory).
I'm wondering if it's possible to call a service to do the date conversion directly from the template?
It sounds like you want a filter. Create the filter once:
angular.module("myModule").filter('friendlyDate', function() {
return function(date) {
/* Code to convert ISO date to readable date */
return date;
};
});
And then in your HTML everywhere:
<tr ng-repeat="task in tasks">
<td>{{date | friendlyDate}}</td>
</tr>