24

I have a form where there is a need for me to have 2 or more date fields for different things. I tried the Angular UI Bootstrap which works fine when I have only 1 date field in the form. But it stops working if I have multiple date fields and I dont know the easier method to get this to work.

This is my HTML sample:

 <label>First Date</label>  
    <div class="input-group">
     <input type="text" class="form-control" datepicker-popup="{{format}}" name="dt" ng-model="formData.dt" is-open="opened" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
      <span class="input-group-btn">
        <button class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
      </span>
    </div>      

 <label>Second Date</label>  
    <div class="input-group">
     <input type="text" class="form-control" datepicker-popup="{{format}}" name="dtSecond" ng-model="formData.dtSecond" is-open="opened" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
      <span class="input-group-btn">
        <button class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
      </span>
    </div>     

My JS is:

myApp.controller('DatePickrCntrl', function ($scope) {

      $scope.today = function() {
        $scope.formData.dt = new Date();
      };
      $scope.today();

      $scope.showWeeks = true;
      $scope.toggleWeeks = function () {
        $scope.showWeeks = ! $scope.showWeeks;
      };

      $scope.clear = function () {
        $scope.dt = null;
      };

      // Disable weekend selection
      $scope.disabled = function(date, mode) {
        return ( mode === 'day' && ( date.getDay() === 0 || date.getDay() === 6 ) );
      };

      $scope.toggleMin = function() {
        $scope.minDate = ( $scope.minDate ) ? null : new Date();
      };
      $scope.toggleMin();

      $scope.open = function($event) {
        $event.preventDefault();
        $event.stopPropagation();

        $scope.opened = true;
      };

      $scope.dateOptions = {
        'year-format': "'yy'",
        'starting-day': 1
      };

      $scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'shortDate'];
      $scope.format = $scope.formats[0];

});

I implemented based on the sample here. The problem I have here is:

1) When one of the date field is clicked, the pop-up datepicker is messed up and seems to show 2 datepicker in 1.

2) When I remove is-open="opened" attribute, the pop-up window seems to work fine. But without is-open="opened", the ng-click="open($event) for the button doesnt work.

3) Since each of the date fields have different ng-models, I am unable to set default dates for any date fields except for the first one with ng-model="formData.dt"

The only long way to resolve this that I can think of is to separate the controller for each date field.

I would like to know how others implement multiple date fields in 1 form itself when using Angular UI Bootstrap.

Mo.
  • 26,306
  • 36
  • 159
  • 225
Neel
  • 9,352
  • 23
  • 87
  • 128
  • 1
    One way that might make this more manageable is to make a directive for your date picker and pass your models into two instances of those. It would handle the scope of your 'open' function without opening both date picker calendars at once. – Shoreline Jul 31 '14 at 14:26

6 Answers6

42

I have 30 in one form one controller no problem. use the same concept if you need it on ng-repeat.

 <label>First Date</label>  
    <div class="input-group">
     <input type="text" class="form-control" datepicker-popup="{{format}}" 
            name="dt" ng-model="formData.dt" is-open="datepickers.dt" 
            datepicker-options="dateOptions" ng-required="true" 
            close-text="Close" />
      <span class="input-group-btn">
        <button class="btn btn-default" ng-click="open($event,'dt')">
            <i class="glyphicon glyphicon-calendar"></i></button>
      </span>
    </div>      

 <label>Second Date</label>  
    <div class="input-group">
     <input type="text" class="form-control" datepicker-popup="{{format}}" 
            name="dtSecond" ng-model="formData.dtSecond" 
            is-open="datepickers.dtSecond" datepicker-options="dateOptions" 
            ng-required="true" close-text="Close" />
      <span class="input-group-btn">
        <button class="btn btn-default" ng-click="open($event,'dtSecond')">
            <i class="glyphicon glyphicon-calendar"></i></button>
      </span>
    </div>     

myApp.controller('DatePickrCntrl', function ($scope) {
      $scope.datepickers = {
        dt: false,
        dtSecond: false
      }
      $scope.today = function() {
        $scope.formData.dt = new Date();

        // ***** Q1  *****
        $scope.formData.dtSecond = new Date();
      };
      $scope.today();

      $scope.showWeeks = true;
      $scope.toggleWeeks = function () {
        $scope.showWeeks = ! $scope.showWeeks;
      };

      $scope.clear = function () {
        $scope.dt = null;
      };

      // Disable weekend selection
      $scope.disabled = function(date, mode) {
        return ( mode === 'day' && ( date.getDay() === 0 || date.getDay() === 6 ) );
      };

      $scope.toggleMin = function() {
        $scope.minDate = ( $scope.minDate ) ? null : new Date();
      };
      $scope.toggleMin();

      $scope.open = function($event, which) {
        $event.preventDefault();
        $event.stopPropagation();

        $scope.datepickers[which]= true;
      };

      $scope.dateOptions = {
        'year-format': "'yy'",
        'starting-day': 1
      };

      $scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'shortDate'];
      $scope.format = $scope.formats[0];

});


 // ***** Q2 ***** somemodel can be just an array [1,2,3,4,5]
 <div ng-repeat="o in somemodel">
 <label>Date Label</label>  
    <div class="input-group">
     <input type="text" class="form-control" datepicker-popup="{{format}}"
            name="dt{{o}}" ng-model="datepickers.data[o]" 
            is-open="datepickers.isopen[o]" datepicker-options="datepickers.option" 
            ng-required="true" close-text="Close" />
      <span class="input-group-btn">
        <button class="btn btn-default" ng-click="open($event,o)">
            <i class="glyphicon glyphicon-calendar"></i></button>
      </span>
    </div>
  </div>


myApp.controller('DatePickrCntrl', function ($scope) {

      $scope.datepickers = {
        data: {},
        options: {
            'year-format': "'yy'",
            'starting-day': 1
        },
        isopen: {}
      }
      $http.get("get/data/for/some/model", function(result) {
         $scope.somemodel = result;
         for (var i = 0; i < result.length; i++) {
           $scope.datepickers.isopen[result] = false;
           $scope.datepickers.data[result] = new Date(); //set default date.
         }
      });

  // fill in rest of the function
});
Simon Martin
  • 4,203
  • 7
  • 56
  • 93
wayne
  • 3,410
  • 19
  • 11
  • Thats excellent. That works and its better than me adding the `DatePickrCntrl` controller for each input separately. I do have 2 more questions though. (Q1) In this method, how can I show the today date for other date fields too? For instance, atm only `formData.dt` has today's date and `formData.dtFollowUp` is empty (Q2) If I am using the same concept in ng-repeat, how can I add different ng-model names for each repeats? I use these ng-models to post data to a php file. – Neel Mar 08 '14 at 17:08
  • 2
    actually i never put id or name attribute in ng-repeat for control. you only deal with ng-model. id and name is redundant. like the example name="dt{o}". angular create 1 extra $watch for interpolation. as you know more watches affect page performance – wayne Mar 08 '14 at 17:36
  • Am I crazy to think that, when adding a directive to your template, you should not be forced to implement a method in your controller ? I think this stuff should be encapsulated better. I have opened this: https://github.com/angular-ui/bootstrap/issues/3157, hopefully Pawel and the gang agree, and we can get something properly encapsulated to work with – Cosmin Jan 05 '15 at 20:40
19

Simpler Solution. Requires only modding the HTML and can be used in a ng-repeat if you like. Just be creative with what you name the opened

Stick this in your Controller:

$scope.calendar = {
    opened: {},
    dateFormat: 'MM/dd/yyyy',
    dateOptions: {},
    open: function($event, which) {
        $event.preventDefault();
        $event.stopPropagation();
        $scope.calendar.opened[which] = true;
    } 
};

HTML:

<div class="form-group row">
    <div class="col-lg-6">
        <label for="formDOB">Date of Birth</label>
        <p class="input-group">
          <input type="text" class="form-control" datepicker-popup="{{calendar.dateFormat}}" ng-model="record.birthDate" is-open="calendar.opened.dob" datepicker-options="calendar.dateOptions" close-text="Close" placeholder="Date of Birth" />
          <span class="input-group-btn">
            <button type="button" class="btn btn-default" ng-click="calendar.open($event, 'dob')"><i class="glyphicon glyphicon-calendar"></i></button>
          </span>
        </p>
    </div>
    <div class="col-lg-6">
        <label for="formWinDate">Win Date</label>
        <p class="input-group">
          <input type="text" class="form-control" datepicker-popup="{{calendar.dateFormat}}" ng-model="record.winDate" is-open="calendar.opened.win" datepicker-options="calendar.dateOptions" close-text="Close" placeholder="Win DAte" />
          <span class="input-group-btn">
            <button type="button" class="btn btn-default" ng-click="calendar.open($event, 'win')"><i class="glyphicon glyphicon-calendar"></i></button>
          </span>
        </p>
    </div>
</div>
geilt
  • 807
  • 8
  • 9
5

wayne's answer is great. I would only add, that you can make the function 'open()' better:

$scope.open = function ($event, datePicker) {
    $event.preventDefault();
    $event.stopPropagation();

    $scope.closeAll();
    datePicker.opened = true;
};

And then you have to use it like that:

ng-click="open($event, dateFrom)"

Where dateFrom is your ng-model (i.e. you use $scope.dateFrom).

EDIT: $scope.closeAll(); is a function that closes all the datePickers. It can be written like that:

$scope.closeAll = function() {
    $scope.dateFrom.opened = false;
    $scope.dateTo.opened = false;
};
Community
  • 1
  • 1
Mateusz Rasiński
  • 1,186
  • 1
  • 13
  • 15
2

I'd prefer not to mix ng-model with UI info.For this purpose, is necessary to define an array to store the opening flags, as well as checking if the datePicker is opened or not.

Moreover, I have changes the 'open' behavior to 'toggle' instead, in order to allow closinf the datePicker with the button.

Here is my controller:

$scope.toggleOpenDatePicker = function($event,datePicker) {
   $event.preventDefault();
   $event.stopPropagation();
   $scope[datePicker] = !$scope[datePicker];
};

Then it can be used as:

<input type="text" class="form-control" ng-model="model.date1" is-open="date1" />
   <span class="input-group-btn">
      <button type="button" class="btn btn-default" ng-click="toggleOpenDatePicker($event,'date1')"><i class="glyphicon glyphicon-calendar"></i>      
      </button>
   </span>

The $scope idea was borrowed from here:

Community
  • 1
  • 1
Toni Gamez
  • 6,819
  • 1
  • 23
  • 18
  • what's awesome about this is that you don't have to worry about defining the $scope vars `date1` and `date2` here. I think initially they will be undefined if you don't define them in the controller, but upon clicking the button they will be assigned a value. this was the cleanest solution for me – FireDragon Feb 10 '15 at 05:37
1

I solved my problem by use this plunker with minor changes.

HTML

<div class="row">
    <div class="col-md-6">
        <p class="input-group">
          <input type="text" class="form-control" datepicker-popup="{{format}}" ng-model="dt" is-open="openDatePickers[0]" min-date="minDate" max-date="'2015-06-22'" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" ng-required="true" close-text="Close" />
          <span class="input-group-btn">
            <button type="button" class="btn btn-default" ng-click="open($event, 0)"><i class="glyphicon glyphicon-calendar"></i></button>
          </span>
        </p>
    </div>
</div>
    <h4>Popup</h4>
<div class="row">
    <div class="col-md-6">
        <p class="input-group">
          <input type="text" class="form-control" datepicker-popup="{{format}}" ng-model="dt" is-open="openDatePickers[1]" min-date="minDate" max-date="'2015-06-22'" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" ng-required="true" close-text="Close" />
          <span class="input-group-btn">
            <button type="button" class="btn btn-default" ng-click="open($event, 1)"><i class="glyphicon glyphicon-calendar"></i></button>
          </span>
        </p>
    </div>
</div>

and in controller

$scope.openDatePickers = [];
$scope.open = function ($event, datePickerIndex) {
   $event.preventDefault();
   $event.stopPropagation();

   if ($scope.openDatePickers[datePickerIndex] === true) {
      $scope.openDatePickers.length = 0;
   } else {
      $scope.openDatePickers.length = 0;
      $scope.openDatePickers[datePickerIndex] = true;
   }
};

my changes

instead numbers (0 or 1) i use $index in angular ng-repeat.

like this:

is-open="openDatePickers[**$index**]"

ng-click="open($event, **$index**)"

<p class="input-group">
   <input type="text" class="form-control" datepicker-popup="{{format}}" ng-model="dt" is-open="openDatePickers[$index]" min-date="minDate" max-date="'2015-06-22'" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" ng-required="true" close-text="Close">
   <span class="input-group-btn">
      <button type="button" class="btn btn-default" ng-click="open($event, $index)"><i class="glyphicon glyphicon-calendar"></i></button>
   </span>
</p>
Majid Basirati
  • 2,665
  • 3
  • 24
  • 46
Morteza QorbanAlizade
  • 1,520
  • 2
  • 19
  • 35
0

i used it in a different way and it looks a bit easier to me. I was using one of the mentioned approaches, but i was to lazy to create tons of calendars since i was running i a loop without having any static identifier. So this solution is just valid for you, if you want to create lots of calendars inside of a ng-repeat. Hope it helps!

That is the controller

       $scope.datepickers = {
            data: {},
            isopen: {}
       }
       // setting the defaults once
       for (var i = 0; i < $scope.array.length; i++) {
            $scope.datepickers.isopen[i] = false;
            $scope.datepickers.data[i] = new Date();
        }

        // aso..

        $scope.valuationDatePickerOpen = function($event, index) {
          if ($event) {
            $event.preventDefault();
            $event.stopPropagation();
          }
          $scope.datepickers.isopen[index] = true;
        };

And this is the HTML snipped inside my loop

<!-- ng-repeat="entry in array track by $index" --> 

<input type="text" class="form-control" 
       datepicker-popup="dd-MMMM-yyyy" 
       is-open="datepickers.isopen[$index]"
       ng-click="valuationDatePickerOpen($event, $index)"
       ng-model="entry.date" />
djnose
  • 917
  • 7
  • 19