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.

1
  • 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. Commented Jul 31, 2014 at 14:26

6 Answers 6

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
});
Sign up to request clarification or add additional context in comments.

3 Comments

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.
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
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: github.com/angular-ui/bootstrap/issues/3157, hopefully Pawel and the gang agree, and we can get something properly encapsulated to work with
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>

5 Comments

Is there any way we could wrap this in a directive? I'm not a fan of having what is essentially UI code ($event.preventDefault()) in my controllers.
When I add initDate: '05/25/2014' or minDate: '05/25/2014' to my calendar scope, it doesn't work for me.
Try initDate: new Date('05/25/2014')
this is a much more concise answer than the others. Kudos, sir.
Note on my answer. Doing ti this way creates a ridiculous amount of watchers if you using 20+ datepickers like myself...searching for a solution...
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;
};

7 Comments

closeAll is not defined anywhere
@KuldeepDaftary You are right :) I've thought that it will be pretty straightforward ;) I've edited the content.
Beware, this answer tricked me. It worked perfectly the first time around so I copied it to ever datepicker in my app. Then I tested one where the model starts as null. Then I cried.
@BenCr Can you elaborate on this?
I don't have time to make a plunkr but basically, if you start with a null date, i.e. dateFrom is null then setting datepicker.opened = true in the $scope.open function doesn't work because null.opened isn't valid.
|
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:

1 Comment

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
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>

Comments

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" />

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.