angular-ui / bootstrap

PLEASE READ THE PROJECT STATUS BELOW. Native AngularJS (Angular) directives for Bootstrap. Smaller footprint (20kB gzipped), no 3rd party JS dependencies (jQuery, bootstrap JS) required. Please read the README.md file before submitting an issue!
http://angular-ui.github.io/bootstrap/
MIT License
14.28k stars 6.73k forks source link

Datepicker does not re-open after selecting a date. #2540

Closed zackarychapple closed 9 years ago

zackarychapple commented 10 years ago

I have multiple datepickers on my page and am having trouble getting them to work as expected. They all open fine and allow for selecting a date without issue. However after selecting the date the datepicker will no longer open for that given element. I have included both the HTML and JS I am using with the datepicker.

How can I make it so the datepicker can be opened more than once for any given element.

<input type="text" ng-model="myOjbect.dateOfBirthStr" datepicker-popup="MM/dd/yyyy"
                           is-open="dateOfBirthStrOpened"
                           name="dateOfBirthStr" id="dateOfBirthStr" class="form-control" close-text="Close">
<span class="input-group-addon" ng-click="openDatepicker($event, 'dateOfBirthStrOpened')">
    <i class="ss-icon ss-calendar"></i>
 </span>
  $scope.openDatepicker = function ($event, elementOpened) {
    $event.preventDefault();
    $event.stopPropagation();

    $scope[elementOpened] = !$scope[elementOpened];

  };
heyjorge commented 10 years ago

I experienced the same issue with almost your exact same code, although it didn't matter whether I was using 1 datepicker or multiple datepickers.

So I am not sure if you are using ui-router, but I am and I had to specify ng-controller in my html template in order for datepicker to be able to be re-opened, and I'm not sure why that worked. I normally specify the controller name in the route itself. Even though that worked, I do not like specifying my controller in the html template instead of the route because I want to be able to use resolves with my routes in ui-router and specifying ng-controller in the html instead of the route doesn't allow me to pass resolves into the controller.

So the workaround / alternative I decided to use without having to specify ng-controller in my actual template is to not use the span icon and instead have the datepicker be triggered off of the user clicking on the input field. This solution also makes it so you don't have to have a scope function in the controller that makes the is-open variable equal to true.

Here's a plunker I found showing what I ended up doing to get it to work off the input instead of the span button:

http://plnkr.co/edit/3fxVbuTxlTetNvQW4JeZ?p=preview

EDIT:

In fact, using your exact code in the plunker (I added another datepicker input as well even), it works fine. So I bet that you're like me and you use ui-router and specify your controller in the route itself. I have no idea why it doesn't work when you specify the controller in a ui-router route instead of directly in the HTML. I'm not really up for creating a plunker that sets all that up either, but clearly there is some behavior going on that either I don't understand or isn't quite right between ui-router specified controllers, and span elements/datepicker.

http://plnkr.co/edit/P5r3g9pGhNfoAR7iBJVd?p=preview

mmonti commented 10 years ago

Had the same problem here, but seems that the problem is the ui-bootstrap-tpls file. I just replaced the 0.11.0 version with the previous one (/angular-ui-bootstrap/0.10.0/ui-bootstrap-tpls.js) and is working correctly

I tried to make a diff but the files are totally different (at least the datepicker). Any ideas?

zackarychapple commented 10 years ago

@mmonti thats a good piece of information. I'll see if I can dig a little later.

JobaDiniz commented 10 years ago

+1

zourtney commented 10 years ago

+1 I have a very similar issue when embedding the datepicker popup inside of an ng-switch (in a custom directive's template). I dug around the source for quite a while but couldn't find an obvious cause. Pretty frustrating...

michelrtm commented 10 years ago

I just had this issue, try to pass the variables that contains the opened value to an object, example:

$scope.data = {};
$scope.data.isOpen1 = false;
$scope.data.isOpen2 = false;

$scope.open1 = function () {
//bla bla bla
}

And set the is-open= to data.isOpenX. Just solved it for me.

DavidePastore commented 9 years ago

This could help :dancer:

eroux commented 9 years ago

The solution of michelrtm worked for me... Could the example in the documentation be updated to use this? (the solution given in previous comment also uses this...

tdhsmith commented 9 years ago

I think the answer @zackarychapple links in #2584 gives the best explanation of the root cause: http://stackoverflow.com/a/25308180/3697161

(I'm still pretty new to angular and I learned and reinforced a lot through this issue, so I wanted to write up a summary:)


The datepickerPopup directive creates an isolate scope (set up on L438). This means the popup doesn't live in the same scope as the input/button that triggers it. The line isOpen: '=?' activates two-way binding between the isolate scope's isOpen variable and the isOpen attribute on the date field. However, due to the way scope inheritance works, setting a javascript primitive in a child scope will not write to the parent scope's variable. Angular's GitHub wiki has a well-diagrammed article describing some of this here.

So when you open the popup, $parentScope.opened is set to true, which the popup can see when it reads $popupScope.opened. But when you close it, $popupScope.opened = false sets its own value in the popup's scope. The parent scope has no idea this happened; $parentScope.opened is still true and it has no way to affect the divergent value in the popup's scope because it's being shadowed.

Thus, the most straightforward solution is to use an object instead, because when child scopes set object values, they actually check if there is an inherited parent value first.

This is what I'm using now:

<input type="date"
    class="form-control"
    ng-model="propietary_things.date" 
    datepicker-popup="yyyy-MM-dd"
    is-open="datepicker.opened" />
$scope.openDatepicker = function($event) {
    $event.preventDefault();
    $event.stopPropagation();
    $scope.datepicker = {'opened': true};
    // succinct way to ensure object has been created *and* set the property
}

Having an isolate scope seems to be a good thing because it prevents a lot of issues with mucking up the parent scope (for example, multiple pickers could end up all opening and closing at once or writing over the same data). Avoiding binding primitives is considered good practice in angular, so the docs should probably be updated regardless of whether there is a "fix" for the code. I can get around to that if no one else does.

juanpablopola commented 9 years ago

I had this trouble but I found my solution by this way, hope this can help you:

    <div class="col-md-4" ng-repeat="travel in travels">
        <table>
            <tr>
                <td colspan="2">
                    {{"TOTALPASSENGERS" | translate}}
                </td>
            </tr>
            <tr>
                <td>
                    {{"DATE" | translate}}
                </td>
                <td>
                    <p class="input-group">
                        <input type="text" name="$index" class="form-control" datepicker-popup="shortDate" ng-model="travel.Travel.date" is-open="openeds[$index]" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" ng-required="true" close-text='{{"CLOSE" | translate}}' />
                        <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>
                </td>
            </tr>

.....

In my controller I have the following code:

$scope.openeds = new Array();

for(var i = 0; i <$scope.travels.length; i++)
{
    $scope.openeds[i] = false;
}

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

    $scope.openeds[index] = true;
  };
wesleycho commented 9 years ago

This appears to be an old issue - feel free to open a new issue based on current master or 0.13.2 if this is still an issue with current UI Bootstrap.

Coffee-Tea commented 8 years ago

michelrtm and tdhsmith. Thank you guys, that simple solution and it WORKS!