Join the Stack Overflow Community
Stack Overflow is a community of 6.4 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

I'm using Angular UI Bootstrap Datepicker: https://angular-ui.github.io/bootstrap/#/datepicker

When I render form using data received from the server, there is problem with datetime fields. My input datepicker looks like this:

<form name="itemForm">
    <input type="datetime" class="form-control" id="startedAt" name="startedAt"
           ng-model="item.startedAt"
           ng-click="open($event, 'startedAt')"
           uib-datepicker-popup="yyyy-MM-dd"
           is-open="datepickers.startedAt"
    />
</form>

My server returns response datetime as JSON string:

{    
   ...
   startedAt: "2015-05-29T02:00:00+0200"
}

When I assign response data to the model $scope.item = response;, datepicker input field is rendered correctly (correct date is selected and it's properly formatted in format I selected). The problem is that validation does not pass. I get:

itemForm.startedAt.$invalid == true

I noticed that data bound to the datepicker field should be Date object and not string (when I select new date from the datepicker, $scope.item.startedAt is a Date)

I managed to work around this issue and do this in the controller:

$scope.item = response;
$scope.item.startedAt = new Date($scope.item.startedAt);

It works this way... But I wouldn't like to manually convert string do date every time I get a response from the server. I tried to create a directive, that I can assign to the datepicker input field so it converts the ng-model for me:

.directive("asDate", function () {
    return {
        require: 'ngModel',
        link: function (scope, element, attrs, modelCtrl) {

            modelCtrl.$formatters.push(function (input) {

                var transformedInput = new Date(input);

                if (transformedInput != input) {
                    modelCtrl.$setViewValue(transformedInput);
                    modelCtrl.$render();
                }

                return transformedInput;
            });
        }
    }
})

Well it works, because now I can see Date object, when I output model in my view: {{item.startedAt}}. However still validation fails! I suspect this is some problem with me understanding how data flows between model and the view, and how UI Bootstrap hooks into it.

Also when I change my directive from $formatters.push to $formatters.unshift, validation works OK, but datepicker does not format my datetime (insted of nicely formattet yyyy-MM-dd I see ISO string inside the input)

share|improve this question
1  
Aww man we are having the exact same problem here!!! – Victor Parmar Nov 18 '15 at 14:58
    
@VictorParmar currently I'm doing this the way around - converting response string to Date object when receiving from server. And converting Date object to string when sending to server. All this done in Angular controller manually. Maybe I will just extract this logic as Angular service, but I don't think its possible to do with directive – rsobon Nov 18 '15 at 15:11
2  
yeah join the club - we ended up doing the same thing :) – Victor Parmar Nov 19 '15 at 15:32

This broke as of Angular.UI.Bootstrap v0.13.2 (8-2-2015) Downgrading to 0.13.1 works, which is where I'm stuck today.

Wesleycho says this was done intentionally https://github.com/angular-ui/bootstrap/issues/4690

I'm ready for other date pickers that support strings if anyone has a suggestion

...soon after posting this I went down a non-angular path that I'm not proud of, but it works for both HTML5 type="date" and uib-datepicker-popup. I have a regular expression that determines if a string resembles one of the two serialized date formats I've seen, and then I have a recursive javascript function to traverse a json tree and replace those strings with Date(). You would call it just before you put in $scope (or viewmodel) ...

$http.get("../api/comm/" + commId
    ).success(function (resp) {
        fixDates(resp);
        vm.comm = resp;
    });

(I need not check the string length, but I figured it would spare some cpu cycles by not running the regex if the string is obviously not a date)

//2015-10-01T00:00:00-04:00
//2015-11-20T18:15:56.6229516-05:00
var isDate = new RegExp("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{7})?-\\d{2}:00");

function fixDates(json) {
    for (i in json)
        if (typeof (json[i]) == "object")
            fixDates(json[i]);
        else if (typeof (json[i]) == "string" && (json[i].length == 25 || json[i].length == 33) && isDate.test(json[i]))
            json[i] = new Date(json[i]);
};
share|improve this answer
    
when you are using javascript for-in loop you probable should also use hasOwnProperty() – rsobon Dec 2 '15 at 16:27
up vote 0 down vote accepted

As this is intentional behaviour of angular-ui-bootstrap datepicker (https://github.com/angular-ui/bootstrap/issues/4690), I ended up using Angular service/factory and moment library.

Service dateConverter can be injected globally to intercept all HTTP requestes/responses or only in desired controllers.

Here I use Restangular library to handle request to REST API, hence the response.plain() method which takes only object properties, and not Restangular methods/properties.

var Services = angular.module('app.Services', []);

Services
    .factory('dateConverter', ['dateFilter', function (dateFilter) {

        var dateConverter = {};

        dateConverter.prepareResponse = function (response) {
            for(prop in response.plain()) {
                if (response.hasOwnProperty(prop)) {
                    if(moment(response[prop], moment.ISO_8601, true).isValid()) {
                        response[prop] = new Date(response[prop]);
                    }
                }
            }
            return response;
        };

        dateConverter.prepareRequest = function (item) {
            for(prop in item.plain()) {
                if (item.hasOwnProperty(prop)) {
                    if(angular.isDate(item[prop])){
                         item[prop] = dateFilter(item[prop] , "yyyy-MM-ddTHH:mm:ssZ")
                    }
                }
            }
            return item;
        };

        return dateConverter;
    }])
;
share|improve this answer

you can transform String to Date in restangular transformer, something like this

RestangularConfigurer
  .addElementTransformer('<RESTRESOURCENAME>', false, function (element) {
      element.createDate = new Date(element.createDate);
      return element;
  })
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.