So we have a model with a 'time' in it, formatted like '14:00:00'. We are using angular-boostrap's timepicker, but it expects a JS date object or an ISO datetime string.
My first thought was to do a directive that would add $parser/$formatter to ngModel controller that would convert it on the way in and way out to the format we need. This works smashingly for output, but not for input.
I've made a fiddle here http://jsfiddle.net/HB7LU/1820/
myApp.directive('timespan',function($filter){
var dateFilter = $filter('date');
function parser(data){
return dateFilter(data,'HH:mm:ss');
};
function formatter(data){
var converted = '2000-01-01T' + data + '-06:00';
return converted;
};
return {
require: 'ngModel',
link:function(scope,element,attrs,ctrl){
ctrl.$parsers.unshift(parser);
ctrl.$formatters.unshift(formatter);
}
};
});
As you cans when you open the fiddle the 12:00:00 isn't reflected by the timepicker. If you look at the console its complaining that ngModel is in the wrong format. If you change the time with the picker you'll see that $scope.myModel.time is in the correct format.
How do I get the ngModel to the correct format before it gets to timepicker?
timepicker in angular-bootstrap expects the $modelValue to be a date (it passes it to the Date() constructor and then parses hours and minutes from the object). Since it writes its own $render() function, based on $modelValue, your formatters aren't being used.
I'm afraid you're not going to get what you want without doing some surgery on the angular-bootstrap code. It would likely be easier to write a controller with two input fields (for hours and minutes).
A possible solution is to create directive for your Model Value input. This directive would
receive date in the same format as timepicker - timestamp or Date object and display it any way you want.
I think timepicker must use $viewValue in render method. Therefore the best solution is this surgery on the angular-bootstrap code.
Change $modelValue to $viewValue in render function.
Related
I'm improving and automating certain things in an old web app. One of them is the date format. We used to handle these with jquery functions and now we are doing it with angularjs.
For the input we use a directive and it works perfect. The problem occurs when it is not used, the directive is not executed and the value of the model is left without the proper value.
Directive:
app.directive('formatDate', function($filter) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, modelCtrl) {
// format text (model to view)
modelCtrl.$formatters.push(function(value) {
if(value !== "" && typeof value !== "undefined" && value !== null){
return value.split("-").reverse().join("/");
}
});
// format text (view to model)
modelCtrl.$parsers.push(function(value) {
if(value !== "" && typeof value !== "undefined" && value !== null){
var date = new Date(value.split("/").reverse().join("-"));
date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
return date;
}
});
}
};
});
Issue:
When you load the value from webservice, for example: "invoice.date" comes from the database with the format "yyyy-mm-dd". It is loaded in the input with format "dd/mm/yyyy" and if the input is edited the model value is a "Date object" thanks to the directive. But if no field is edited, the value remains "yyyy-mm-dd" and that string causes errors. (Note: they use webservices with jpa and I can not change anything in backend)
How to format the value before sending it without doing a manual verification and analyzing the value in each function? Can I use $watch over each of the variables without causing a conflict with the directive or an infinite loop? Angular has some other way to automate this?
Thanks for any help.
I have seen this many times, it is quite common and you are using the time because jpa or the database changes the date because Timezone, right?
ok, then it comes from a webserice so it probably comes in json format. You can simply convert to a date object before assigning it to the model value and thus always use this format. Another way is to convert it before sending it and you've already said that's what you want to avoid.
There are many things that can be done. To sum up:
(choose one)
Convert the format upon receipt and before assigning it. either
manual, by simple javascript or any other method.
On the reverse, before sending, make the format change.
To avoid doing it manually, look for it with functions or events, if
$ watch can be used to detect in which format it comes and change it
correctly. So if it is assigned programmatically, it will also work.
There are other methods to filter and elaborate the response but I
think it would be too much for this case. As Jorge said, there are some plugins, tools and more that can be added. But I always try to avoid overloading with so many things.
The problem is to try to automate with angularjs, because there are many ways and finding the "right" way is difficult since each project is different and each person has a different way of thinking.
I have a javascript plugin that convert persian date . everything works well except one thing .
I've added a default value for input like : $scope.myDate='13930101' ;
But i'd like to update it when user click on new date . How can i update the $scope ? should i create a directive for that ?
Thanks
Plunker
This is the really nice thing about Angular, you don't have to watch simple variables to update them in the $scope. Take out ng-change="newDate()" on your date field. When you use ng-model on an input field, any changes made to that input field will be reflected in the variable you set it to.
The other thing you can do is move your onClick function into the angular ng-click function you created:
$scope.showDate = function() {
// alert($scope.myDate);
PersianDatePicker.Show('thisDate', $scope.myDate);
};
That will grab the current value of $scope.myDate and pass it into the plugin:
Plunker
I'm currently refactoring an old directive of ours, which basically displays a date in the local date format, while keeping an ISO date as the underlying model. I rely on ngModel controller, parsers, formatters and so on. It works mostly as expected, except for one thing.
One datepicker can have a startBoundary argument. When set, if the startBoundary date is in the future, then the current value of the directive should be set to this startBoundary, and the value displayed in the input field updated as well, of course. But so far, even though my modelValue is correctly updated, the value displayed in the field isn't. I've though of a digest issue, but no luck with this so far.
Here's a codepen demonstrating what happens : http://codepen.io/pabuisson/pen/dPRNbb
Any idea how I could solve this ? I don't get it. Thanks a lot guys !
You just need to call $render:
modelCtrl.$setViewValue moment.utc(start).format( dateFormat )
modelCtrl.$render()
Demo: http://codepen.io/anon/pen/xbrPbB
So I'm attempting to deal with dates with mongodb / mongoose / angular.
I was trying to use the as a date picker. But it requires yyyy-MM-dd format. Where the dates generated in by a mongoose schema:
created: {
type: Date,
default: Date.now
},
Those dates are this format: 2014-12-13T22:23:20.633Z
So I looked around for how people are handling binding to the data model when there is a conversion required.
I came up with the following directive.
'use strict';
angular.module('clients').directive('mongooseDateFormat', ['$filter',
function ($filter) {
return {
require: 'ngModel',
link: function (scope, element, attrs, ngModelController) {
ngModelController.$parsers.push(function (data) {
console.log(data);
data = $filter('date')(data, 'yyyy-MM-dd');
console.log(data);
return data; //converted
});
ngModelController.$formatters.push(function (data) {
console.log(data); // gets 2015-01-12T00:00:00.000Z
data = $filter('date')(data, 'yyyy-MM-dd');
console.log(data); // converts to 2015-01-11
return data; //converted
});
}
};
}
]);
So I included the console.log functions where I'm testing the values and show above in the code some sample dates in the comments.
You can see that 2015-01-12T00:00:00.000Z becomes 2015-01-11.
So the value that the sends into the filter is with the 0 time stamp and the $format(date)(data, "yyyy-MM-dd") command removes the timestamp but changes the day.
(..sigh.. expletives removed )
Being new to having to care about date formats this is just mind blowing. Is the problem that I'm not using a timezone? By that I mean that mongodb and mongoose are not generating a timezone? Why would a date with a zero time round down to the previous date when you attempt to format it?
I could continue complaining about how odd this is and make myself sound stupid when someone tells me the easy answer. I'll just post and see if anyone knows.
<input type="text" data-mongoose-date-format data-ng-model="client.mydate">
or
<input type="date" data-mongoose-date-format data-ng-model="client.mydate">
They both bind after you enter the date and it is converted and return a date one day less.
It's because Z at the end of your time string tells that this is UTC and date filter converts date to your local timezone. If you use Angularjs 1.3.* you can add timezone parameter:
$filter('date')(data, 'yyyy-MM-dd', 'UTC');
I'm using the meanjs.org boiler plate as well, and I've been trying to work through this same problem for several hours now. I was storing it as a Date, but couldn't use it with ng-model because of the above mentioned formatting error. So I created the directive above, only to find that I need angular 1.3 to get the time zone param for $filter if I don't want it to automatically subtract 8 hours. But I found it's a real pain to upgrade angular to 1.3 because lots other stuff breaks, so I decided to revert back to 1.2 instead of following that rabbit hole.
But, I kid you not, I was able to circumvent the entire problem by simply changing the mongoose datatype from Date to String in my server model. This eliminates the automatic time zone conversion, but still allows you to use the date filter in your data bindings.
{{show.date | date: 'mediumDate'}}
Despite being stored in mongo as a string, it still formats perfectly, without the pesky automatic timezone conversion.
Also, when the date is stored as a string, it surprisingly needs no conversion at all when using in the edit form.
<input type="date" data-ng-model="show.date" id="date" class="form-control" placeholder="Show date" required>
Works just fine, you don't need a directive.
I realize from a data schema point of view, it's always preferable to store dates as type Date. But, to me, this is a much simpler solution until the boilerplate comes out of the box with angular 1.3, which will make it much easier to avoid the automatic time zone conversion of stored dates.
I am using Angular UI datepicker in my project.
The control has an option "datepicker-popup" which allows me to set up te format I want to display the date in. However the date is bound to my model as a date object and not as a formatted string.
The rest of my code simply requires the date as a string in the correct (yyyy-MM-dd) format.
At the moment wehenever I need to use the date, I format it to the correct string before passing it along.
This works for now since The code base is pretty small but is there a better way to somehow bind the date to my model as a string so that someone forgetting to format the date before using it doesn't break the system.
A plunker for the sample code can be found here.
I was thinking maybe I would need to set up a watch or something but was not too sure what the correct solution would be.
I think that I found better solution for this. You can use your own parser for datepickerPopup directive. Example which works for me (you have to add this directive to the application):
angular.module('myApp')
.directive('datepickerPopup', function (){
return {
restrict: 'EAC',
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
ngModel.$parsers.push(function toModel(date) {
return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
});
}
}
});
Each when you will select the date on date picker you will have the String object with formatted date: 'yyyy-MM-dd'. Hope it helps.
No, currently AngularUI and many other frameworks use the Date object to bind information. You need to format the date to a string each time you want it as a string. The way to do this is to create a function like
$scope.getMyDateAsString = function(){
return myDate.toString(); // or however you format your string.
};
Then anytime you want to get the string you can call this function. You CAN create a watcher
$scope.$watch($scope.myDateModel, function(newVal, oldVal){
$scope.myDateAsString = $scope.getMyDateAsString();
});
This way, anytime the datepicker changes value, you change the value of the string.
You can format your dates after picking, using Cordova Plugin Datepicker and Moment.js, this solution works for me:
$scope.pickSinceDate = function(){
pickDate($scope.filter.since).then(function(date){
$scope.since = moment(date).format('YYYY-MM-DD');
});
});
Hope it helps.