Search code examples
angularjsangularjs-directivedatepickerbootstrap-datepicker

Angular Bootstrap DatePicker Directive (Eternicode version) not staying open


I'm working on integrating Eternicode's Bootstrap Datepicker into my app. I wasn't sure how to connect it to my model so I found a directive that uses it from here. Now I've got the datepicker on my page but it won't stay open when I click on the input field. The datepicker opens and closes almost instantly. If I click on an adjacent field and tab into the datepicker field, it stays open as would be expected. I'm also having problems getting it the appear where I would like it to be. When it does open, it's under some other elements and out of place. It should open directly below the input field. See screenshot below.

misplaced

In my controller I have:

$scope.dateOptions = {
    format: 'dd/mm/yyyy',
    autoclose: false,
    clearBtn: true,
    container: '#datepicker',
    showOnFocus: true,
    startDate: new Date(),
    todayBtn: true,
    todayHighlight: true,
    weekStart: 1,
    zIndexOffset: 5000
}
$scope.newPost = {
    token: $scope.token,
    post: $scope.post,
    posts: {
        twitter: null,
        facebook: null,
        linkedin: null
    },
    attachment_url: $scope.attachment_url,
    media_url: $scope.media_url,
    services: {
        twitter: $scope.twitter,
        facebook: $scope.facebook,
        linkedin: $scope.linkedin
    },
    dateObject: today,
    timeObject: today
};

Then in my directive I have:

.directive('bDatepicker', function(){
    return {
        require: '?ngModel',
        restrict: 'A',
        link: function ($scope, element, attrs, controller) {
            var updateModel, onblur;

            if (controller != null) {

                updateModel = function (event) {
                    element.datepicker('hide');
                    element.blur();
                };

                onblur = function () {
                    //we'll update the model in the blur() handler
                    //because it's possible the user put in an invalid date
                    //in the input box directly.
                    //Bootstrap datepicker will have overwritten invalid values
                    //on blur with today's date so we'll stick that in the model.
                    //this assumes that the forceParse option has been left as default(true)
                    //https://github.com/eternicode/bootstrap-datepicker#forceparse
                    var date = element.val();
                    return $scope.$apply(function () {
                        return controller.$setViewValue(date);
                    });
                };

                controller.$render = function () {
                    var date = controller.$viewValue;
                    if (angular.isDefined(date) && date != null && angular.isDate(date))
                    {
                        element.datepicker().data().datepicker.date = date;
                        element.datepicker('setValue');
                        element.datepicker('update');
                    } else if (angular.isDefined(date)) {
                        throw new Error('ng-Model value must be a Date object - currently it is a ' + typeof date + ' - use ui-date-format to convert it from a string');
                    }
                    return controller.$viewValue;
                };
            }
            return attrs.$observe('bDatepicker', function (value) {
                var options;
                options = { }; //<--- insert your own defaults here!
                if (angular.isObject(value)) {
                    options = value;
                }
                if (typeof (value) === "string" && value.length > 0) {
                    options = angular.fromJson(value);
                }
                return element.datepicker(options).on('changeDate', updateModel).on('blur', onblur);
            })
        }
    }
});

Lastly in my view I have:

<input b-datepicker="{{dateOptions}}" ng-model="newPost.dateObject" id="datepicker">

I created a fiddle, but I can't get it to work right. I'm getting an Uncaught Error: [$injector:modulerr] error that I can't figure out how to resolve. Here is the link: https://jsfiddle.net/kxge3cqf/

Here is a sample from `social_media_posts.json:

[
    {
        id: 50,
        company_id: 94,
        employer_id: 3,
        post: "testing without attachments",
        attachment_url: null,
        twitter: true,
        facebook: false,
        linkedin: false,
        post_date: "Fri, Oct 30, 2015",
        post_time: " 2:50 PM EDT"
    },
    {
        total_count: 1
    }
]

Here is a sample from groups.json:

{
    groups: {
        1: {
            id: 1,
            name: "Human Resources",
            created_at: "2015-10-27T16:23:07.287Z",
            updated_at: "2015-11-01T16:11:43.329Z",
            company_id: 94
        },
        2: {
            id: 2,
            name: "Marketing",
            created_at: "2015-11-01T15:32:28.988Z",
            updated_at: "2015-11-01T16:11:43.354Z",
            company_id: 94
        }
    }
}

Here is a sample from contacts.json:

{
    contacts: {
        1: {
            id: 1,
            first_name: "Foo",
            last_name: "Bar",
            email: "[email protected]",
            created_at: "2015-10-27T16:24:00.832Z",
            updated_at: "2015-11-01T16:11:52.426Z",
            company_id: 94
        }
    }
}

Solution

  • This seems like your issue

    $event.stopPropagation();
    

    angular.module('peskyDatepicker', ['ui.bootstrap'])
    .controller('DatepickerDemoCtrl', ['$scope',
    function ($scope) {
      
      var vm = this;
      
      vm.valuationDate = new Date();
      vm.valuationDatePickerIsOpen = false;
      vm.opens = [];
      
      $scope.$watch(function () {
           return vm.valuationDatePickerIsOpen;
       },function(value){
          vm.opens.push("valuationDatePickerIsOpen: " + value + " at: " + new Date());
       });
      
      vm.valuationDatePickerOpen = function ($event) {
        
          if ($event) {
              $event.preventDefault();
              $event.stopPropagation(); // This is the magic
          }
          this.valuationDatePickerIsOpen = true;
      };
    }]);
    <!DOCTYPE html>
    <html ng-app="peskyDatepicker">
    
      <head>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.0/ui-bootstrap-tpls.min.js"></script>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" />
        <link rel="stylesheet" href="style.css" />
        <script src="script.js"></script>
      </head>
    
      <body>
        <div ng-controller="DatepickerDemoCtrl as vm">
          <pre>Selected date is: <em>{{vm.valuationDate | date:'fullDate' }}</em>
          </pre>
          <div class="row">
            <div class="col-md-3">
              <p class="input-group">
                <span class="input-group-btn">
                  <button type="button" class="btn btn-default" 
                          ng-click="vm.valuationDatePickerOpen()">
                    <i class="glyphicon glyphicon-calendar"></i>
                  </button>
                </span>
                <input type="text" class="form-control" 
                       datepicker-popup="mediumDate" 
                       is-open="vm.valuationDatePickerIsOpen" 
                       ng-click="vm.valuationDatePickerOpen()" 
                       ng-model="vm.valuationDate" />
                <span class="input-group-btn">
                  <button type="button" class="btn btn-default" 
                          ng-click="vm.valuationDatePickerOpen($event)">
                    <i class="glyphicon glyphicon-calendar"></i>
                  </button>
                </span>
              </p>
            </div>
          </div>
          <p class="col-md-3">
            The left button <em>doesn't</em> work (opens the picker and closes right away).<br />
            The right button <em>does</em> work (opens the picker and stays open).<br />
            And it's all down to <code>$event.stopPropagation();</code> - funny huh?</code>
          </p>
          <ul>
            <li ng-repeat="open in vm.opens">{{open}}</li>
          </ul>
        </div>
      </body>
    
    </html>

    And there's a blog for this issue as well http://blog.johnnyreilly.com/2015/05/angular-ui-bootstrap-datepicker-weirdness.html

    Will try to figure out what's the issue in your code exactly, by the time you may have a look at it.

    Edit1

    why don't you use default type="date" like

    <input type="date" ng-model="date" value="{{ date | date: 'yyyy/MM/dd' }}" />
    <br />{{ date | date: 'dd/MM/yyyy' }} 
    

    Edit2

    ok tried your code, took out only date related part, It works fine.

    So there must be something else in you code which is causing this. Here's the code which I tried, If it helps

    angular.module('ui.bootstrap.demo', ['ngAnimate']);
    angular.module('ui.bootstrap.demo').controller('DatepickerDemoCtrl', function($scope,$filter) {
        $scope.dateOptions = {
            format: 'dd/mm/yyyy',
            autoclose: false,
            clearBtn: true,
            // container: '#datepicker',
            showOnFocus: true,
            startDate: new Date(),
            todayBtn: true,
            todayHighlight: true,
            weekStart: 1,
            zIndexOffset: 5000
        }
    });
    
    angular.module('ui.bootstrap.demo').directive('bDatepicker', function() {
        return {
            require: '?ngModel',
            restrict: 'A',
            link: function($scope, element, attrs, controller) {
                var updateModel, onblur;
    
                if (controller != null) {
    
                    updateModel = function(event) {
                        console.log($scope.dateObject);
                        element.datepicker('hide');
                        element.blur();
                    };
    
                    onblur = function() {
                        //we'll update the model in the blur() handler
                        //because it's possible the user put in an invalid date
                        //in the input box directly.
                        //Bootstrap datepicker will have overwritten invalid values
                        //on blur with today's date so we'll stick that in the model.
                        //this assumes that the forceParse option has been left as default(true)
                        //https://github.com/eternicode/bootstrap-datepicker#forceparse
                        var date = element.val();
                        return $scope.$apply(function() {
                            return controller.$setViewValue(date);
                        });
                    };
    
                    controller.$render = function() {
                        var date = controller.$viewValue;
                        if (angular.isDefined(date) && date != null && angular.isDate(date)) {
                            // element.datepicker().data().datepicker.date = date;
                            element.datepicker('update', date);
                            element.datepicker('setValue');
                            element.datepicker('update');
                        } else if (angular.isDefined(date)) {
                            throw new Error('ng-Model value must be a Date object - currently it is a ' + typeof date + ' - use ui-date-format to convert it from a string');
                        }
                        return controller.$viewValue;
                    };
                }
                return attrs.$observe('bDatepicker', function(value) {
                    var options;
                    options = {}; //<--- insert your own defaults here!
                    if (angular.isObject(value)) {
                        options = value;
                    }
                    if (typeof(value) === "string" && value.length > 0) {
                        options = angular.fromJson(value);
                    }
                    return element.datepicker(options).on('changeDate', updateModel).on('blur', onblur);
                })
            }
        }
    });
    <!doctype html>
    <html ng-app="ui.bootstrap.demo">
    
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.5.0/js/bootstrap-datepicker.min.js"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular-animate.js"></script>
        <script src="example.js"></script>
        <!-- <link href="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet"> -->
        <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.5.0/css/bootstrap-datepicker.standalone.min.css" rel="stylesheet">
    </head>
    
    <body ng-controller="DatepickerDemoCtrl">
        <input b-datepicker="{{dateOptions}}" ng-model="dateObject" id="datepicker" >
        </br>
        Date: {{dateObject}}
    </body>
    
    </html>

    If you may provide some git repo or a working plunk/fiddle I can figure out what exactly is wrong. But at the moment this is all I have. Hope it helps