Angular UI datepicker popup open without ng-click - angularjs

According to official Angular UI documentation for datepicker users in popup style I need to create additional button with ng-click event on it to change $scope property which was binded to is-open attribute like so:
<p class="input-group">
<input type="text" class="form-control" uib-datepicker-popup="{{format}}" ng-model="dt" is-open="popup1.opened" min-date="minDate" max-date="maxDate" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" ng-required="true" close-text="Close" alt-input-formats="altInputFormats" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open1()"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
In my application it is possible to have more than 10 such datepickers per view so I need to implement property for is-open attribute per each.
Is there any way to open datepicker popup without is-open attribute?

If you have +10 datepickers and repeat the same markup over and over, and need to create $scope functions without any real purpose - then it is almost screaming for a directive to do the trivial tasks! The markup you are repeating can be placed in a template :
<script type="text/ng-template" id="dateAutomater.html">
<input type="text" class="form-control"/>
<span class="input-group-btn">
<button type="button" class="btn btn-default">
<i class="glyphicon glyphicon-calendar"></i>
</button>
</span>
</script>
The directive (the very basics) could look like this :
.directive('dateAutomater', ['$compile', function($compile) {
return {
transclude: true,
templateUrl: 'dateAutomater.html',
restrict: 'AE',
link: function ($scope, element, attrs) {
$scope.dateInfo = $scope.dateInfo || {};
var dateInfo = $scope.dateInfo,
input = element.find('input'),
button = element.find('button'),
name = input.name || 'date'+Object.keys($scope.dateInfo).length,
info = {
open: false,
click: function() {
this.open = true
}
}
dateInfo[name] = info;
input.attr('ng-model', attrs.dateAutomater);
input.attr('uib-datepicker-popup', 'dd-MMMM-yyyy');
input.attr('is-open', 'dateInfo[\"'+name+'\"].open')
button.attr('ng-click', 'dateInfo[\"'+name+'\"].click()');
$compile(element.contents())($scope);
}
}
}])
It simply takes the model as argument, injects the markup from the template and bind the important variable is-open and ng-click function to a self maintained object, $scope.dateInfo. Usage
<p class="input-group" date-automater="dt"></p>
<p class="input-group" date-automater="date"></p>
<p class="input-group" date-automater="yesterDay"></p>
...
demo -> http://plnkr.co/edit/H6hgYdF420R4IKdjCBGM?p=preview
Now expand the directive / template to set other default properties you want on the datepicker, like min-date and so on.

Related

z-index not working in Angular UI Datepicker

In this plunk I have an Angular UI Datepicker inside a div (the div has orange background). The problem is that the Datepicker is truncated, even though I set z-index: 99999. If I remove from the div overflow-y:auto the Datepicker is shown, however I need the div to scroll when there's an overflow. How to fix this problem?
HTML:
<div style="height:200px;padding:30px;background-color:orange;overflow-y:auto">
<p class="input-group" style="z-index:99999">
<input type="text" class="form-control" ng-model="dt" is-open="config.opened"
uib-datepicker-popup popup-placement="top-left" datepicker-options="dateOptions"
ng-required="true" close-text="Close" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open1()">
<i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
</div>
Javascript:
var app = angular.module('app', ['ui.bootstrap']);
app.controller('ctl', function ($scope) {
$scope.opened = false;
$scope.dateOptions = {
showWeeks: false
};
$scope.open1 = function(){
$scope.opened = true;
};
});
app.directive('dir2', function () {
return {
restrict: 'EA',
scope: {
someVar: '='
},
templateUrl: 'dir2.html',
link: function (scope, element, attrs) {
scope.config = {};
scope.config.opened = true;
}
}
});
This is very common case with tooltip, datepicker or popover in such cases it is better choice to append the content to body so the current element styling doesn't matter. Basically in this case you could append datepicker to body by setting below datepikcer-append-to-body to true.
datepicker-append-to-body="true"
Don't go for setting z-index, it may work by tweaking few things, but it could unintentionally break other piece of software.
Plunker Demo

evaluate expression in scope

I am trying to create a directive for my date pickers here is the relevant code.
usage
<date-input open="openCalendar = true">
<input ng-model="ctrl.model.theDate"
uib-datepicker-popup="dd-MMMM-yyyy"
is-open="openCalendar">
</date-input>
directive
angular
.module('common.directives')
.directive('dateInput', function() {
return {
restrict: 'E',
transclude: true,
scope: {open:'&'},
template: 'date-input-template',
};
});
template
<script type="text/ng-template" id="date-input-template>
<div class="input-group">
<ng-transclude></ng-transclude>
<div class="input-group-btn">
<button class="btn btn-default" type="button" ng-click="open()">
<i class="glyphicon glyphicon-calendar"></i>
</button>
</div>
</div>
</script>
This works once. If I try to open the calendar a 2nd time, the date picker does not display. I'm sure I'm doing something wrong, just not sure what it is.
Any suggestions or guidance is appreciated.
You should call the function with the expression
ng-click="open()">
Your expression for ng-click needs to invoke open:
ng-click="open()"
Edit: You may want to use an object like data.openCalendar = true to avoid prototypal scope inheritance issues, or move the openCalendar = true expression into a function and invoke that function.

Input value from a specialized controller isn't being sent back to the main controller

I'm building an application that's making use of Angular UI Bootstrap's datepicker in several spots. I've included some other enhancements to this, such as validation styling, and wanted to propagate it everywhere so I put it in a directive.
app.controller('DateController', function () {
var vm = this;
vm.status = {
opened: false
};
vm.open = open;
function open($event) {
vm.status.opened = true;
}
});
app.directive('customDatePicker', function () {
return {
restrict: 'E',
scope: {
name: '=',
bindingProperty: '=',
minDate: '=',
maxDate: '=',
maxMode: '=',
format: '=',
isRequired: '=',
form: '='
},
templateUrl: "/Scripts/SharedAngular/Templates/datePicker.html"
};
});
With the template URL code looking like:
<div ng-controller="DateController as datePicker">
<p class="input-group">
<input type="text" id="{{ name }}_datePicker" name="{{ name }}_datePicker" class="form-control" max-mode="maxMode" datepicker-popup="{{format}}" ng-model="bindingProperty" min-date="minDate" max-date="maxDate" ng-required="isRequired" close-text="Close" is-open="datePicker.status.opened" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="datePicker.open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
<span ng-show="form.{{ name }}_datePicker.$invalid && form.{{ name }}_datePicker.$dirty" class="glyphicon glyphicon-exclamation-sign form-control-feedback datepicker-error-icon"></span>
</p>
</div>
I'm including this directive in a couple places. It is placed on a page within the scope of a different controller like this:
<div ng-controller="myController as ctrl">
<custom-date-picker name="'somePrefix'" form="myForm" binding-property="ctrl.date" max-mode="'month'" max-date="ctrl.currentDate" format="'yyyy/MM/dd'" is-required="true"></custom-date-picker>
</div>
So, I want to take a property from myController and two-way bind it to the directive's date picker. However, what ends up actually happening is that the current value for ctrl.date property is passed to the datepicker, but any changes to that value that happen in the directive are not sent back. So, it seems that myController and DateController only pass the value one-way, and only when the directive is loaded onto the page. To be clear, the directive doesn't seem to be the source of my issue, since putting the HTML code from the template directly onto my page results in the same behavior. So, it appears that the issue is specifically due to the use of two different controllers.
Previously, I was using ng-model both in the template code and the directive on my primary page. Neither option seems to work. I'm open to any ideas on how to get the input from my directive to be sent back to the myController.
I figured out how to ditch the DateController entirely. I put my expressions directly into the template elements via ng-click and ng-init. The input creates a property called opened and uses ng-init initializes it to false. It then uses that property for the is-open attribute of the datepicker. Then the button just uses ng-click="opened=true". Definitely simplifies things and removes the nesting of controllers.
Directive:
app.directive('customDatePicker', function () {
return {
restrict: 'E',
scope: {
name: '=',
bindingProperty: '=ngModel',
minDate: '=',
maxDate: '=',
maxMode: '=',
format: '=',
isRequired: '=',
form: '='
},
require: 'ngModel',
templateUrl: "/Scripts/SharedAngular/Templates/datePicker.html"
};
});
Template:
<p class="input-group">
<input type="text" id="{{ name }}_datePicker" name="{{ name }}_datePicker" class="form-control" max-mode="maxMode" datepicker-popup="{{format}}" ng-model="bindingProperty" min-date="minDate" max-date="maxDate" ng-required="isRequired" close-text="Close" is-open="opened" ng-init="opened=false" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="opened=true"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
<span ng-show="form.{{ name }}_datePicker.$invalid && form.{{ name }}_datePicker.$dirty" class="glyphicon glyphicon-exclamation-sign form-control-feedback datepicker-error-icon"></span>
</p>
Using the directive:
<div ng-controller="MyController as ctrl">
<custom-date-picker name="'somePrefix'" form="myForm" ng-model="ctrl.Date" max-mode="'month'" max-date="ctrl.currentDate" format="'yyyy/MM/dd'" is-required="true"></custom-date-picker>
</div>

ng-model is not updated in ng-if

I'm failing to retrieve the actual value of my selected option (ng-model=filter) in my select html bloc.
Note that if I don't set $scope.filter then $scope.filter stay undefined even if I select an option in the dropdown list.
My html template, filters.html :
<div ng-if="!showFilterTemplatePlz">
<button class="btn btn-primary" ng-click="showFilterTemplate()" type="button">Add Filter</button>
</div>
<div ng-if="showFilterTemplatePlz" class="inline">
<button class="btn btn-primary" ng-click="closeFilters()" type="button">Close filters</button>
<label>filters</label>
<select ng-model="filter" ng-options="filter.name for filter in filterOptions" class="form-control">
</select>
<p>{{filter.name}}</p>
<button ng-click="addFilter()" id="addFilterButton" class="btn btn-primary btn-sm btn-default" type="button"><i class="fa fa-plus"></i> Add</button>
</div>
In my directive:
.directive('filters', ['retrieveStaticDatasService','usersService', function(datasService, usersService) {
return {
restrict: 'E',
scope: false,
controller: function ($scope) {
/* init */
$scope.filterOptions = [
{name: "languages"},
{name: "nationality"},
{name: "location"}
];
$scope.filter = $scope.filterOptions[0];
/* end init */
$scope.showFilterTemplate = function(){
$scope.showFilterTemplatePlz=true;
}
$scope.closeFilters = function(){
$scope.showFilterTemplatePlz=false;
}
$scope.addFilter = function(){
console.log("filter to add : " + $scope.filter.name);
}
},
templateUrl: '/partials/components/filters.html'
}
}])
Result :
I can see the actual value appears in my html but i will always show "languages" for$scope.filter.name in my console, whatever option I selected! I took a screenshot to show you : http://hpics.li/4a37ae3
Thanks for your help.
[Edit : I made some test and my model are setted and updated only if they are not inside the "<div ng-if="..."></div>"]. Is it not allowed to put a ng-if directly in the directive?
Answer : Angularjs ng-model doesn't work inside ng-if
I found the solution, the problem was that ng-if creates a child scope. So I changed filter to $parent.filter in
<select ng-model="$parent.filter" ng-options="option.name for option in filterOptions" class="form-control">
</select>
I think you are confusing it by having to objects named 'filter'. You might be overwriting the ng-options object rather than your controller one.
Try changing to
<select ng-model="filter" ng-options="option.name for option in filterOptions" class="form-control"></select>
<p>{{filter.name}}</p>
<button ng-click="addFilter()" id="addFilterButton" class="btn btn-primary btn-sm btn-default" type="button"><i class="fa fa-plus"></i> Add</button>

watch expression new value always equals to old value

i'm using two AngularJS Bootstrap-ui Datepicker directives in a single controller.
The code is very simple, the view :
<div class="row">
<div class="col-md-6">
<p class="input-group">
<input type="text" class="form-control" datepicker-popup="dd-MM-yyyy" ng-model="startDate" is-open="startDateOpened" min="minDate" max="'2015-06-22'" ng-required="true" close-text="Close" />
<span class="input-group-btn">
<button class="btn btn-default" ng-click="open($event, 'startDateOpened')"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
</div>
<div class="col-md-6">
<p class="input-group">
<input type="text" class="form-control" datepicker-popup="dd-MM-yyyy" ng-model="endDate" is-open="endDateOpened" min="minDate" max="'2015-06-22'" ng-required="true" close-text="Close" />
<span class="input-group-btn">
<button class="btn btn-default" ng-click="open($event, 'endDateOpened')"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
</div>
</div>
The controller (part of it).
var now = new Date();
var startDate = $scope.startDate = new Date(now.getFullYear(), now.getMonth(), 1); // beginning of month
var endDate = $scope.endDate = now;
$scope.$watchCollection('[startDate, endDate]', function (newVals, oldVals) {
if(!(newVals[0] === oldVals[0] && newVals[1] === oldVals[1])){
console.log('old values', oldVals);
console.log('new values', newVals);
print();
}
});
function print() {
console.log('startDate !', startDate);
console.log('endDate !', endDate);
}
$scope.open = function($event, name) {
$event.preventDefault();
$event.stopPropagation();
$scope[name] = true;
};
I am assigning two models - startDate and endDate. Everything looks like it is working fine, when I choose a date in either of the date pickers $scope.$watchCollection() callback is fired up but oldVals is always eqaul to newVals so the model doesn't really change (even though the change event listener is fired).
What am I doing wrong ? I'm guessing it's a lack of understanding something basic :)
Thanks
EDIT
#Maxim's answer of using deep watch did solve my problem of not going into the if statement. Now my other problem is that print function inside the callback always prints the old values of startDate and endDate
Try $watch with flag true aka deep watch:
$scope.$watch('[startDate, endDate]', function (newVals, oldVals) {
if(!(newVals[0] === oldVals[0] && newVals[1] === oldVals[1])){
console.log('old values', oldVals);
console.log('new values', newVals);
startDate = newVals[0];
endDate = newVals[1];
}
}, true);
Plunker

Resources