2014-08-10 11:37:05 +02:00
/ *
* angular - ui - bootstrap
* http : //angular-ui.github.io/bootstrap/
2019-03-29 22:00:08 +01:00
* Version : 2.5 . 0 - 2017 - 01 - 28
2014-08-10 11:37:05 +02:00
* License : MIT
2019-03-29 22:00:08 +01:00
* / a n g u l a r . m o d u l e ( " u i . b o o t s t r a p " , [ " u i . b o o t s t r a p . c o l l a p s e " , " u i . b o o t s t r a p . t a b i n d e x " , " u i . b o o t s t r a p . a c c o r d i o n " , " u i . b o o t s t r a p . a l e r t " , " u i . b o o t s t r a p . b u t t o n s " , " u i . b o o t s t r a p . c a r o u s e l " , " u i . b o o t s t r a p . d a t e p a r s e r " , " u i . b o o t s t r a p . i s C l a s s " , " u i . b o o t s t r a p . d a t e p i c k e r " , " u i . b o o t s t r a p . p o s i t i o n " , " u i . b o o t s t r a p . d a t e p i c k e r P o p u p " , " u i . b o o t s t r a p . d e b o u n c e " , " u i . b o o t s t r a p . m u l t i M a p " , " u i . b o o t s t r a p . d r o p d o w n " , " u i . b o o t s t r a p . s t a c k e d M a p " , " u i . b o o t s t r a p . m o d a l " , " u i . b o o t s t r a p . p a g i n g " , " u i . b o o t s t r a p . p a g e r " , " u i . b o o t s t r a p . p a g i n a t i o n " , " u i . b o o t s t r a p . t o o l t i p " , " u i . b o o t s t r a p . p o p o v e r " , " u i . b o o t s t r a p . p r o g r e s s b a r " , " u i . b o o t s t r a p . r a t i n g " , " u i . b o o t s t r a p . t a b s " , " u i . b o o t s t r a p . t i m e p i c k e r " , " u i . b o o t s t r a p . t y p e a h e a d " ] ) ;
2016-05-16 13:33:49 +02:00
angular . module ( 'ui.bootstrap.collapse' , [ ] )
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
. directive ( 'uibCollapse' , [ '$animate' , '$q' , '$parse' , '$injector' , function ( $animate , $q , $parse , $injector ) {
var $animateCss = $injector . has ( '$animateCss' ) ? $injector . get ( '$animateCss' ) : null ;
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
link : function ( scope , element , attrs ) {
var expandingExpr = $parse ( attrs . expanding ) ,
2019-03-29 22:00:08 +01:00
expandedExpr = $parse ( attrs . expanded ) ,
collapsingExpr = $parse ( attrs . collapsing ) ,
collapsedExpr = $parse ( attrs . collapsed ) ,
horizontal = false ,
css = { } ,
cssTo = { } ;
init ( ) ;
function init ( ) {
horizontal = ! ! ( 'horizontal' in attrs ) ;
if ( horizontal ) {
css = {
width : ''
} ;
cssTo = { width : '0' } ;
} else {
css = {
height : ''
} ;
cssTo = { height : '0' } ;
}
if ( ! scope . $eval ( attrs . uibCollapse ) ) {
element . addClass ( 'in' )
. addClass ( 'collapse' )
. attr ( 'aria-expanded' , true )
. attr ( 'aria-hidden' , false )
. css ( css ) ;
}
}
2016-05-16 13:33:49 +02:00
2019-03-29 22:00:08 +01:00
function getScrollFromElement ( element ) {
if ( horizontal ) {
return { width : element . scrollWidth + 'px' } ;
}
return { height : element . scrollHeight + 'px' } ;
2014-08-10 11:37:05 +02:00
}
function expand ( ) {
2016-05-16 13:33:49 +02:00
if ( element . hasClass ( 'collapse' ) && element . hasClass ( 'in' ) ) {
return ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
$q . resolve ( expandingExpr ( scope ) )
. then ( function ( ) {
element . removeClass ( 'collapse' )
. addClass ( 'collapsing' )
. attr ( 'aria-expanded' , true )
. attr ( 'aria-hidden' , false ) ;
if ( $animateCss ) {
$animateCss ( element , {
addClass : 'in' ,
easing : 'ease' ,
2019-03-29 22:00:08 +01:00
css : {
overflow : 'hidden'
} ,
to : getScrollFromElement ( element [ 0 ] )
2016-05-16 13:33:49 +02:00
} ) . start ( ) [ 'finally' ] ( expandDone ) ;
} else {
$animate . addClass ( element , 'in' , {
2019-03-29 22:00:08 +01:00
css : {
overflow : 'hidden'
} ,
to : getScrollFromElement ( element [ 0 ] )
2016-05-16 13:33:49 +02:00
} ) . then ( expandDone ) ;
}
2019-03-29 22:00:08 +01:00
} , angular . noop ) ;
2014-08-10 11:37:05 +02:00
}
function expandDone ( ) {
2016-05-16 13:33:49 +02:00
element . removeClass ( 'collapsing' )
. addClass ( 'collapse' )
2019-03-29 22:00:08 +01:00
. css ( css ) ;
2016-05-16 13:33:49 +02:00
expandedExpr ( scope ) ;
2014-08-10 11:37:05 +02:00
}
function collapse ( ) {
2016-05-16 13:33:49 +02:00
if ( ! element . hasClass ( 'collapse' ) && ! element . hasClass ( 'in' ) ) {
return collapseDone ( ) ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
$q . resolve ( collapsingExpr ( scope ) )
. then ( function ( ) {
element
2019-03-29 22:00:08 +01:00
// IMPORTANT: The width must be set before adding "collapsing" class.
// Otherwise, the browser attempts to animate from width 0 (in
// collapsing class) to the given width here.
. css ( getScrollFromElement ( element [ 0 ] ) )
2016-05-16 13:33:49 +02:00
// initially all panel collapse have the collapse class, this removal
// prevents the animation from jumping to collapsed state
. removeClass ( 'collapse' )
. addClass ( 'collapsing' )
. attr ( 'aria-expanded' , false )
. attr ( 'aria-hidden' , true ) ;
if ( $animateCss ) {
$animateCss ( element , {
removeClass : 'in' ,
2019-03-29 22:00:08 +01:00
to : cssTo
2016-05-16 13:33:49 +02:00
} ) . start ( ) [ 'finally' ] ( collapseDone ) ;
} else {
$animate . removeClass ( element , 'in' , {
2019-03-29 22:00:08 +01:00
to : cssTo
2016-05-16 13:33:49 +02:00
} ) . then ( collapseDone ) ;
}
2019-03-29 22:00:08 +01:00
} , angular . noop ) ;
2014-08-10 11:37:05 +02:00
}
function collapseDone ( ) {
2019-03-29 22:00:08 +01:00
element . css ( cssTo ) ; // Required so that collapse works when animation is disabled
2016-05-16 13:33:49 +02:00
element . removeClass ( 'collapsing' )
. addClass ( 'collapse' ) ;
collapsedExpr ( scope ) ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
scope . $watch ( attrs . uibCollapse , function ( shouldCollapse ) {
2014-08-10 11:37:05 +02:00
if ( shouldCollapse ) {
collapse ( ) ;
} else {
expand ( ) ;
}
} ) ;
}
} ;
} ] ) ;
2019-03-29 22:00:08 +01:00
angular . module ( 'ui.bootstrap.tabindex' , [ ] )
. directive ( 'uibTabindexToggle' , function ( ) {
return {
restrict : 'A' ,
link : function ( scope , elem , attrs ) {
attrs . $observe ( 'disabled' , function ( disabled ) {
attrs . $set ( 'tabindex' , disabled ? - 1 : null ) ;
} ) ;
}
} ;
} ) ;
angular . module ( 'ui.bootstrap.accordion' , [ 'ui.bootstrap.collapse' , 'ui.bootstrap.tabindex' ] )
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
. constant ( 'uibAccordionConfig' , {
2014-08-10 11:37:05 +02:00
closeOthers : true
} )
2016-05-16 13:33:49 +02:00
. controller ( 'UibAccordionController' , [ '$scope' , '$attrs' , 'uibAccordionConfig' , function ( $scope , $attrs , accordionConfig ) {
2014-08-10 11:37:05 +02:00
// This array keeps track of the accordion groups
this . groups = [ ] ;
// Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
this . closeOthers = function ( openGroup ) {
2016-05-16 13:33:49 +02:00
var closeOthers = angular . isDefined ( $attrs . closeOthers ) ?
$scope . $eval ( $attrs . closeOthers ) : accordionConfig . closeOthers ;
if ( closeOthers ) {
angular . forEach ( this . groups , function ( group ) {
if ( group !== openGroup ) {
2014-08-10 11:37:05 +02:00
group . isOpen = false ;
}
} ) ;
}
} ;
// This is called from the accordion-group directive to add itself to the accordion
this . addGroup = function ( groupScope ) {
var that = this ;
this . groups . push ( groupScope ) ;
2016-05-16 13:33:49 +02:00
groupScope . $on ( '$destroy' , function ( event ) {
2014-08-10 11:37:05 +02:00
that . removeGroup ( groupScope ) ;
} ) ;
} ;
// This is called from the accordion-group directive when to remove itself
this . removeGroup = function ( group ) {
var index = this . groups . indexOf ( group ) ;
2016-05-16 13:33:49 +02:00
if ( index !== - 1 ) {
2014-08-10 11:37:05 +02:00
this . groups . splice ( index , 1 ) ;
}
} ;
} ] )
// The accordion directive simply sets up the directive controller
// and adds an accordion CSS class to itself element.
2016-05-16 13:33:49 +02:00
. directive ( 'uibAccordion' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
controller : 'UibAccordionController' ,
controllerAs : 'accordion' ,
2014-08-10 11:37:05 +02:00
transclude : true ,
2016-05-16 13:33:49 +02:00
templateUrl : function ( element , attrs ) {
return attrs . templateUrl || 'uib/template/accordion/accordion.html' ;
}
2014-08-10 11:37:05 +02:00
} ;
} )
// The accordion-group directive indicates a block of html that will expand and collapse in an accordion
2016-05-16 13:33:49 +02:00
. directive ( 'uibAccordionGroup' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
require : '^uibAccordion' , // We need this directive to be inside an accordion
transclude : true , // It transcludes the contents of the directive into the template
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
2016-05-16 13:33:49 +02:00
templateUrl : function ( element , attrs ) {
return attrs . templateUrl || 'uib/template/accordion/accordion-group.html' ;
} ,
2014-08-10 11:37:05 +02:00
scope : {
heading : '@' , // Interpolate the heading attribute onto this scope
2016-05-16 13:33:49 +02:00
panelClass : '@?' , // Ditto with panelClass
2014-08-10 11:37:05 +02:00
isOpen : '=?' ,
isDisabled : '=?'
} ,
controller : function ( ) {
this . setHeading = function ( element ) {
this . heading = element ;
} ;
} ,
link : function ( scope , element , attrs , accordionCtrl ) {
2019-03-29 22:00:08 +01:00
element . addClass ( 'panel' ) ;
2014-08-10 11:37:05 +02:00
accordionCtrl . addGroup ( scope ) ;
2016-05-16 13:33:49 +02:00
scope . openClass = attrs . openClass || 'panel-open' ;
scope . panelClass = attrs . panelClass || 'panel-default' ;
2014-08-10 11:37:05 +02:00
scope . $watch ( 'isOpen' , function ( value ) {
2016-05-16 13:33:49 +02:00
element . toggleClass ( scope . openClass , ! ! value ) ;
if ( value ) {
2014-08-10 11:37:05 +02:00
accordionCtrl . closeOthers ( scope ) ;
}
} ) ;
2016-05-16 13:33:49 +02:00
scope . toggleOpen = function ( $event ) {
if ( ! scope . isDisabled ) {
if ( ! $event || $event . which === 32 ) {
scope . isOpen = ! scope . isOpen ;
}
2014-08-10 11:37:05 +02:00
}
} ;
2016-05-16 13:33:49 +02:00
var id = 'accordiongroup-' + scope . $id + '-' + Math . floor ( Math . random ( ) * 10000 ) ;
scope . headingId = id + '-tab' ;
scope . panelId = id + '-panel' ;
2014-08-10 11:37:05 +02:00
}
} ;
} )
// Use accordion-heading below an accordion-group to provide a heading containing HTML
2016-05-16 13:33:49 +02:00
. directive ( 'uibAccordionHeading' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
transclude : true , // Grab the contents to be used as the heading
template : '' , // In effect remove this element!
replace : true ,
2016-05-16 13:33:49 +02:00
require : '^uibAccordionGroup' ,
link : function ( scope , element , attrs , accordionGroupCtrl , transclude ) {
2014-08-10 11:37:05 +02:00
// Pass the heading to the accordion-group controller
// so that it can be transcluded into the right place in the template
// [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
2016-05-16 13:33:49 +02:00
accordionGroupCtrl . setHeading ( transclude ( scope , angular . noop ) ) ;
2014-08-10 11:37:05 +02:00
}
} ;
} )
// Use in the accordion-group template to indicate where you want the heading to be transcluded
// You must provide the property on the accordion-group controller that will hold the transcluded element
2016-05-16 13:33:49 +02:00
. directive ( 'uibAccordionTransclude' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
require : '^uibAccordionGroup' ,
link : function ( scope , element , attrs , controller ) {
scope . $watch ( function ( ) { return controller [ attrs . uibAccordionTransclude ] ; } , function ( heading ) {
if ( heading ) {
2016-06-11 17:57:30 +02:00
var elem = angular . element ( element [ 0 ] . querySelector ( getHeaderSelectors ( ) ) ) ;
2016-05-16 13:33:49 +02:00
elem . html ( '' ) ;
elem . append ( heading ) ;
2014-08-10 11:37:05 +02:00
}
} ) ;
}
} ;
2016-06-11 17:57:30 +02:00
function getHeaderSelectors ( ) {
return 'uib-accordion-header,' +
'data-uib-accordion-header,' +
'x-uib-accordion-header,' +
'uib\\:accordion-header,' +
'[uib-accordion-header],' +
'[data-uib-accordion-header],' +
'[x-uib-accordion-header]' ;
}
2014-08-10 11:37:05 +02:00
} ) ;
angular . module ( 'ui.bootstrap.alert' , [ ] )
2019-03-29 22:00:08 +01:00
. controller ( 'UibAlertController' , [ '$scope' , '$element' , '$attrs' , '$interpolate' , '$timeout' , function ( $scope , $element , $attrs , $interpolate , $timeout ) {
2016-05-16 13:33:49 +02:00
$scope . closeable = ! ! $attrs . close ;
2019-03-29 22:00:08 +01:00
$element . addClass ( 'alert' ) ;
$attrs . $set ( 'role' , 'alert' ) ;
if ( $scope . closeable ) {
$element . addClass ( 'alert-dismissible' ) ;
}
2016-05-16 13:33:49 +02:00
var dismissOnTimeout = angular . isDefined ( $attrs . dismissOnTimeout ) ?
$interpolate ( $attrs . dismissOnTimeout ) ( $scope . $parent ) : null ;
if ( dismissOnTimeout ) {
$timeout ( function ( ) {
$scope . close ( ) ;
} , parseInt ( dismissOnTimeout , 10 ) ) ;
}
2014-08-10 11:37:05 +02:00
} ] )
2016-05-16 13:33:49 +02:00
. directive ( 'uibAlert' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
controller : 'UibAlertController' ,
controllerAs : 'alert' ,
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
2016-05-16 13:33:49 +02:00
templateUrl : function ( element , attrs ) {
return attrs . templateUrl || 'uib/template/alert/alert.html' ;
} ,
transclude : true ,
2014-08-10 11:37:05 +02:00
scope : {
close : '&'
}
} ;
} ) ;
angular . module ( 'ui.bootstrap.buttons' , [ ] )
2016-05-16 13:33:49 +02:00
. constant ( 'uibButtonConfig' , {
2014-08-10 11:37:05 +02:00
activeClass : 'active' ,
toggleEvent : 'click'
} )
2016-05-16 13:33:49 +02:00
. controller ( 'UibButtonsController' , [ 'uibButtonConfig' , function ( buttonConfig ) {
2014-08-10 11:37:05 +02:00
this . activeClass = buttonConfig . activeClass || 'active' ;
this . toggleEvent = buttonConfig . toggleEvent || 'click' ;
} ] )
2016-05-16 13:33:49 +02:00
. directive ( 'uibBtnRadio' , [ '$parse' , function ( $parse ) {
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
require : [ 'uibBtnRadio' , 'ngModel' ] ,
controller : 'UibButtonsController' ,
controllerAs : 'buttons' ,
link : function ( scope , element , attrs , ctrls ) {
2014-08-10 11:37:05 +02:00
var buttonsCtrl = ctrls [ 0 ] , ngModelCtrl = ctrls [ 1 ] ;
2016-05-16 13:33:49 +02:00
var uncheckableExpr = $parse ( attrs . uibUncheckable ) ;
element . find ( 'input' ) . css ( { display : 'none' } ) ;
2014-08-10 11:37:05 +02:00
//model -> UI
2016-05-16 13:33:49 +02:00
ngModelCtrl . $render = function ( ) {
element . toggleClass ( buttonsCtrl . activeClass , angular . equals ( ngModelCtrl . $modelValue , scope . $eval ( attrs . uibBtnRadio ) ) ) ;
2014-08-10 11:37:05 +02:00
} ;
//ui->model
2016-05-16 13:33:49 +02:00
element . on ( buttonsCtrl . toggleEvent , function ( ) {
if ( attrs . disabled ) {
return ;
}
2014-08-10 11:37:05 +02:00
var isActive = element . hasClass ( buttonsCtrl . activeClass ) ;
if ( ! isActive || angular . isDefined ( attrs . uncheckable ) ) {
2016-05-16 13:33:49 +02:00
scope . $apply ( function ( ) {
ngModelCtrl . $setViewValue ( isActive ? null : scope . $eval ( attrs . uibBtnRadio ) ) ;
2014-08-10 11:37:05 +02:00
ngModelCtrl . $render ( ) ;
} ) ;
}
} ) ;
2016-05-16 13:33:49 +02:00
if ( attrs . uibUncheckable ) {
scope . $watch ( uncheckableExpr , function ( uncheckable ) {
attrs . $set ( 'uncheckable' , uncheckable ? '' : undefined ) ;
} ) ;
}
2014-08-10 11:37:05 +02:00
}
} ;
2016-05-16 13:33:49 +02:00
} ] )
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
. directive ( 'uibBtnCheckbox' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
require : [ 'uibBtnCheckbox' , 'ngModel' ] ,
controller : 'UibButtonsController' ,
controllerAs : 'button' ,
link : function ( scope , element , attrs , ctrls ) {
2014-08-10 11:37:05 +02:00
var buttonsCtrl = ctrls [ 0 ] , ngModelCtrl = ctrls [ 1 ] ;
2016-05-16 13:33:49 +02:00
element . find ( 'input' ) . css ( { display : 'none' } ) ;
2014-08-10 11:37:05 +02:00
function getTrueValue ( ) {
return getCheckboxValue ( attrs . btnCheckboxTrue , true ) ;
}
function getFalseValue ( ) {
return getCheckboxValue ( attrs . btnCheckboxFalse , false ) ;
}
2016-05-16 13:33:49 +02:00
function getCheckboxValue ( attribute , defaultValue ) {
return angular . isDefined ( attribute ) ? scope . $eval ( attribute ) : defaultValue ;
2014-08-10 11:37:05 +02:00
}
//model -> UI
2016-05-16 13:33:49 +02:00
ngModelCtrl . $render = function ( ) {
2014-08-10 11:37:05 +02:00
element . toggleClass ( buttonsCtrl . activeClass , angular . equals ( ngModelCtrl . $modelValue , getTrueValue ( ) ) ) ;
} ;
//ui->model
2016-05-16 13:33:49 +02:00
element . on ( buttonsCtrl . toggleEvent , function ( ) {
if ( attrs . disabled ) {
return ;
}
scope . $apply ( function ( ) {
2014-08-10 11:37:05 +02:00
ngModelCtrl . $setViewValue ( element . hasClass ( buttonsCtrl . activeClass ) ? getFalseValue ( ) : getTrueValue ( ) ) ;
ngModelCtrl . $render ( ) ;
} ) ;
} ) ;
}
} ;
} ) ;
2016-05-16 13:33:49 +02:00
angular . module ( 'ui.bootstrap.carousel' , [ ] )
. controller ( 'UibCarouselController' , [ '$scope' , '$element' , '$interval' , '$timeout' , '$animate' , function ( $scope , $element , $interval , $timeout , $animate ) {
2014-08-10 11:37:05 +02:00
var self = this ,
slides = self . slides = $scope . slides = [ ] ,
2016-05-16 13:33:49 +02:00
SLIDE _DIRECTION = 'uib-slideDirection' ,
currentIndex = $scope . active ,
2019-03-29 22:00:08 +01:00
currentInterval , isPlaying ;
2014-08-10 11:37:05 +02:00
var destroyed = false ;
2019-03-29 22:00:08 +01:00
$element . addClass ( 'carousel' ) ;
2016-05-16 13:33:49 +02:00
self . addSlide = function ( slide , element ) {
slides . push ( {
slide : slide ,
element : element
} ) ;
slides . sort ( function ( a , b ) {
return + a . slide . index - + b . slide . index ;
} ) ;
//if this is the first slide or the slide is set to active, select it
if ( slide . index === $scope . active || slides . length === 1 && ! angular . isNumber ( $scope . active ) ) {
2014-08-10 11:37:05 +02:00
if ( $scope . $currentTransition ) {
2016-05-16 13:33:49 +02:00
$scope . $currentTransition = null ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
currentIndex = slide . index ;
$scope . active = slide . index ;
setActive ( currentIndex ) ;
self . select ( slides [ findSlideIndex ( slide ) ] ) ;
if ( slides . length === 1 ) {
$scope . play ( ) ;
2014-08-10 11:37:05 +02:00
}
}
} ;
2016-05-16 13:33:49 +02:00
self . getCurrentIndex = function ( ) {
for ( var i = 0 ; i < slides . length ; i ++ ) {
if ( slides [ i ] . slide . index === currentIndex ) {
return i ;
}
}
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
self . next = $scope . next = function ( ) {
var newIndex = ( self . getCurrentIndex ( ) + 1 ) % slides . length ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( newIndex === 0 && $scope . noWrap ( ) ) {
$scope . pause ( ) ;
return ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
return self . select ( slides [ newIndex ] , 'next' ) ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
self . prev = $scope . prev = function ( ) {
var newIndex = self . getCurrentIndex ( ) - 1 < 0 ? slides . length - 1 : self . getCurrentIndex ( ) - 1 ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( $scope . noWrap ( ) && newIndex === slides . length - 1 ) {
$scope . pause ( ) ;
return ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
return self . select ( slides [ newIndex ] , 'prev' ) ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
self . removeSlide = function ( slide ) {
var index = findSlideIndex ( slide ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//get the index of the slide inside the carousel
slides . splice ( index , 1 ) ;
if ( slides . length > 0 && currentIndex === index ) {
if ( index >= slides . length ) {
currentIndex = slides . length - 1 ;
$scope . active = currentIndex ;
setActive ( currentIndex ) ;
self . select ( slides [ slides . length - 1 ] ) ;
} else {
currentIndex = index ;
$scope . active = currentIndex ;
setActive ( currentIndex ) ;
self . select ( slides [ index ] ) ;
}
} else if ( currentIndex > index ) {
currentIndex -- ;
$scope . active = currentIndex ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
//clean the active value when no more slide
if ( slides . length === 0 ) {
currentIndex = null ;
$scope . active = null ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
/* direction: "prev" or "next" */
self . select = $scope . select = function ( nextSlide , direction ) {
var nextIndex = findSlideIndex ( nextSlide . slide ) ;
//Decide direction if it's not given
if ( direction === undefined ) {
direction = nextIndex > self . getCurrentIndex ( ) ? 'next' : 'prev' ;
}
//Prevent this user-triggered transition from occurring if there is already one in progress
if ( nextSlide . slide . index !== currentIndex &&
! $scope . $currentTransition ) {
goNext ( nextSlide . slide , nextIndex , direction ) ;
2014-08-10 11:37:05 +02:00
}
} ;
2016-05-16 13:33:49 +02:00
/* Allow outside people to call indexOf on slides array */
$scope . indexOfSlide = function ( slide ) {
return + slide . slide . index ;
} ;
$scope . isActive = function ( slide ) {
return $scope . active === slide . slide . index ;
} ;
$scope . isPrevDisabled = function ( ) {
return $scope . active === 0 && $scope . noWrap ( ) ;
} ;
$scope . isNextDisabled = function ( ) {
return $scope . active === slides . length - 1 && $scope . noWrap ( ) ;
} ;
2014-08-10 11:37:05 +02:00
$scope . pause = function ( ) {
if ( ! $scope . noPause ) {
isPlaying = false ;
resetTimer ( ) ;
}
} ;
2016-05-16 13:33:49 +02:00
$scope . play = function ( ) {
if ( ! isPlaying ) {
isPlaying = true ;
restartTimer ( ) ;
2014-08-10 11:37:05 +02:00
}
} ;
2019-03-29 22:00:08 +01:00
$element . on ( 'mouseenter' , $scope . pause ) ;
$element . on ( 'mouseleave' , $scope . play ) ;
2016-05-16 13:33:49 +02:00
$scope . $on ( '$destroy' , function ( ) {
destroyed = true ;
resetTimer ( ) ;
} ) ;
$scope . $watch ( 'noTransition' , function ( noTransition ) {
$animate . enabled ( $element , ! noTransition ) ;
} ) ;
$scope . $watch ( 'interval' , restartTimer ) ;
$scope . $watchCollection ( 'slides' , resetTransition ) ;
$scope . $watch ( 'active' , function ( index ) {
if ( angular . isNumber ( index ) && currentIndex !== index ) {
for ( var i = 0 ; i < slides . length ; i ++ ) {
if ( slides [ i ] . slide . index === index ) {
index = i ;
break ;
}
}
var slide = slides [ index ] ;
if ( slide ) {
setActive ( index ) ;
2014-08-10 11:37:05 +02:00
self . select ( slides [ index ] ) ;
2016-05-16 13:33:49 +02:00
currentIndex = index ;
2014-08-10 11:37:05 +02:00
}
}
2016-05-16 13:33:49 +02:00
} ) ;
function getSlideByIndex ( index ) {
for ( var i = 0 , l = slides . length ; i < l ; ++ i ) {
if ( slides [ i ] . index === index ) {
return slides [ i ] ;
}
}
}
function setActive ( index ) {
for ( var i = 0 ; i < slides . length ; i ++ ) {
slides [ i ] . slide . active = i === index ;
}
}
function goNext ( slide , index , direction ) {
if ( destroyed ) {
return ;
}
angular . extend ( slide , { direction : direction } ) ;
angular . extend ( slides [ currentIndex ] . slide || { } , { direction : direction } ) ;
if ( $animate . enabled ( $element ) && ! $scope . $currentTransition &&
slides [ index ] . element && self . slides . length > 1 ) {
slides [ index ] . element . data ( SLIDE _DIRECTION , slide . direction ) ;
var currentIdx = self . getCurrentIndex ( ) ;
if ( angular . isNumber ( currentIdx ) && slides [ currentIdx ] . element ) {
slides [ currentIdx ] . element . data ( SLIDE _DIRECTION , slide . direction ) ;
}
$scope . $currentTransition = true ;
$animate . on ( 'addClass' , slides [ index ] . element , function ( element , phase ) {
if ( phase === 'close' ) {
$scope . $currentTransition = null ;
$animate . off ( 'addClass' , element ) ;
}
} ) ;
}
$scope . active = slide . index ;
currentIndex = slide . index ;
setActive ( index ) ;
//every time you change slides, reset the timer
restartTimer ( ) ;
}
function findSlideIndex ( slide ) {
for ( var i = 0 ; i < slides . length ; i ++ ) {
if ( slides [ i ] . slide === slide ) {
return i ;
}
}
}
function resetTimer ( ) {
if ( currentInterval ) {
$interval . cancel ( currentInterval ) ;
currentInterval = null ;
}
}
function resetTransition ( slides ) {
if ( ! slides . length ) {
$scope . $currentTransition = null ;
}
}
function restartTimer ( ) {
resetTimer ( ) ;
var interval = + $scope . interval ;
if ( ! isNaN ( interval ) && interval > 0 ) {
currentInterval = $interval ( timerFn , interval ) ;
}
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function timerFn ( ) {
var interval = + $scope . interval ;
if ( isPlaying && ! isNaN ( interval ) && interval > 0 && slides . length ) {
$scope . next ( ) ;
} else {
$scope . pause ( ) ;
}
}
2014-08-10 11:37:05 +02:00
} ] )
2016-05-16 13:33:49 +02:00
. directive ( 'uibCarousel' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
transclude : true ,
2016-05-16 13:33:49 +02:00
controller : 'UibCarouselController' ,
controllerAs : 'carousel' ,
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
2016-05-16 13:33:49 +02:00
templateUrl : function ( element , attrs ) {
return attrs . templateUrl || 'uib/template/carousel/carousel.html' ;
} ,
2014-08-10 11:37:05 +02:00
scope : {
2016-05-16 13:33:49 +02:00
active : '=' ,
2014-08-10 11:37:05 +02:00
interval : '=' ,
noTransition : '=' ,
2016-05-16 13:33:49 +02:00
noPause : '=' ,
noWrap : '&'
2014-08-10 11:37:05 +02:00
}
} ;
2016-05-16 13:33:49 +02:00
} )
2014-08-10 11:37:05 +02:00
2019-03-29 22:00:08 +01:00
. directive ( 'uibSlide' , [ '$animate' , function ( $animate ) {
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
require : '^uibCarousel' ,
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
2014-08-10 11:37:05 +02:00
transclude : true ,
2016-05-16 13:33:49 +02:00
templateUrl : function ( element , attrs ) {
return attrs . templateUrl || 'uib/template/carousel/slide.html' ;
} ,
2014-08-10 11:37:05 +02:00
scope : {
2016-05-16 13:33:49 +02:00
actual : '=?' ,
index : '=?'
2014-08-10 11:37:05 +02:00
} ,
link : function ( scope , element , attrs , carouselCtrl ) {
2019-03-29 22:00:08 +01:00
element . addClass ( 'item' ) ;
2014-08-10 11:37:05 +02:00
carouselCtrl . addSlide ( scope , element ) ;
//when the scope is destroyed then remove the slide from the current slides array
scope . $on ( '$destroy' , function ( ) {
carouselCtrl . removeSlide ( scope ) ;
} ) ;
2019-03-29 22:00:08 +01:00
scope . $watch ( 'active' , function ( active ) {
$animate [ active ? 'addClass' : 'removeClass' ] ( element , 'active' ) ;
} ) ;
2014-08-10 11:37:05 +02:00
}
} ;
2019-03-29 22:00:08 +01:00
} ] )
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
. animation ( '.item' , [ '$animateCss' ,
function ( $animateCss ) {
var SLIDE _DIRECTION = 'uib-slideDirection' ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function removeClass ( element , className , callback ) {
element . removeClass ( className ) ;
if ( callback ) {
callback ( ) ;
}
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
return {
beforeAddClass : function ( element , className , done ) {
if ( className === 'active' ) {
var stopped = false ;
var direction = element . data ( SLIDE _DIRECTION ) ;
var directionClass = direction === 'next' ? 'left' : 'right' ;
var removeClassFn = removeClass . bind ( this , element ,
directionClass + ' ' + direction , done ) ;
element . addClass ( direction ) ;
$animateCss ( element , { addClass : directionClass } )
. start ( )
. done ( removeClassFn ) ;
return function ( ) {
stopped = true ;
} ;
}
done ( ) ;
2014-08-10 11:37:05 +02:00
} ,
2016-05-16 13:33:49 +02:00
beforeRemoveClass : function ( element , className , done ) {
if ( className === 'active' ) {
var stopped = false ;
var direction = element . data ( SLIDE _DIRECTION ) ;
var directionClass = direction === 'next' ? 'left' : 'right' ;
var removeClassFn = removeClass . bind ( this , element , directionClass , done ) ;
$animateCss ( element , { addClass : directionClass } )
. start ( )
. done ( removeClassFn ) ;
return function ( ) {
stopped = true ;
} ;
}
done ( ) ;
2014-08-10 11:37:05 +02:00
}
} ;
2016-05-16 13:33:49 +02:00
} ] ) ;
angular . module ( 'ui.bootstrap.dateparser' , [ ] )
2019-03-29 22:00:08 +01:00
. service ( 'uibDateParser' , [ '$log' , '$locale' , 'dateFilter' , 'orderByFilter' , 'filterFilter' , function ( $log , $locale , dateFilter , orderByFilter , filterFilter ) {
2016-05-16 13:33:49 +02:00
// Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
var SPECIAL _CHARACTERS _REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g ;
var localeId ;
var formatCodeToRegex ;
this . init = function ( ) {
localeId = $locale . id ;
this . parsers = { } ;
this . formatters = { } ;
formatCodeToRegex = [
{
key : 'yyyy' ,
regex : '\\d{4}' ,
apply : function ( value ) { this . year = + value ; } ,
formatter : function ( date ) {
var _date = new Date ( ) ;
_date . setFullYear ( Math . abs ( date . getFullYear ( ) ) ) ;
return dateFilter ( _date , 'yyyy' ) ;
}
} ,
{
key : 'yy' ,
regex : '\\d{2}' ,
apply : function ( value ) { value = + value ; this . year = value < 69 ? value + 2000 : value + 1900 ; } ,
formatter : function ( date ) {
var _date = new Date ( ) ;
_date . setFullYear ( Math . abs ( date . getFullYear ( ) ) ) ;
return dateFilter ( _date , 'yy' ) ;
}
} ,
{
key : 'y' ,
regex : '\\d{1,4}' ,
apply : function ( value ) { this . year = + value ; } ,
formatter : function ( date ) {
var _date = new Date ( ) ;
_date . setFullYear ( Math . abs ( date . getFullYear ( ) ) ) ;
return dateFilter ( _date , 'y' ) ;
}
} ,
{
key : 'M!' ,
regex : '0?[1-9]|1[0-2]' ,
apply : function ( value ) { this . month = value - 1 ; } ,
formatter : function ( date ) {
var value = date . getMonth ( ) ;
if ( /^[0-9]$/ . test ( value ) ) {
return dateFilter ( date , 'MM' ) ;
}
return dateFilter ( date , 'M' ) ;
}
} ,
{
key : 'MMMM' ,
regex : $locale . DATETIME _FORMATS . MONTH . join ( '|' ) ,
apply : function ( value ) { this . month = $locale . DATETIME _FORMATS . MONTH . indexOf ( value ) ; } ,
formatter : function ( date ) { return dateFilter ( date , 'MMMM' ) ; }
} ,
{
key : 'MMM' ,
regex : $locale . DATETIME _FORMATS . SHORTMONTH . join ( '|' ) ,
apply : function ( value ) { this . month = $locale . DATETIME _FORMATS . SHORTMONTH . indexOf ( value ) ; } ,
formatter : function ( date ) { return dateFilter ( date , 'MMM' ) ; }
} ,
{
key : 'MM' ,
regex : '0[1-9]|1[0-2]' ,
apply : function ( value ) { this . month = value - 1 ; } ,
formatter : function ( date ) { return dateFilter ( date , 'MM' ) ; }
} ,
{
key : 'M' ,
regex : '[1-9]|1[0-2]' ,
apply : function ( value ) { this . month = value - 1 ; } ,
formatter : function ( date ) { return dateFilter ( date , 'M' ) ; }
} ,
{
key : 'd!' ,
regex : '[0-2]?[0-9]{1}|3[0-1]{1}' ,
apply : function ( value ) { this . date = + value ; } ,
formatter : function ( date ) {
var value = date . getDate ( ) ;
if ( /^[1-9]$/ . test ( value ) ) {
return dateFilter ( date , 'dd' ) ;
}
return dateFilter ( date , 'd' ) ;
}
} ,
{
key : 'dd' ,
regex : '[0-2][0-9]{1}|3[0-1]{1}' ,
apply : function ( value ) { this . date = + value ; } ,
formatter : function ( date ) { return dateFilter ( date , 'dd' ) ; }
} ,
{
key : 'd' ,
regex : '[1-2]?[0-9]{1}|3[0-1]{1}' ,
apply : function ( value ) { this . date = + value ; } ,
formatter : function ( date ) { return dateFilter ( date , 'd' ) ; }
} ,
{
key : 'EEEE' ,
regex : $locale . DATETIME _FORMATS . DAY . join ( '|' ) ,
formatter : function ( date ) { return dateFilter ( date , 'EEEE' ) ; }
} ,
{
key : 'EEE' ,
regex : $locale . DATETIME _FORMATS . SHORTDAY . join ( '|' ) ,
formatter : function ( date ) { return dateFilter ( date , 'EEE' ) ; }
} ,
{
key : 'HH' ,
regex : '(?:0|1)[0-9]|2[0-3]' ,
apply : function ( value ) { this . hours = + value ; } ,
formatter : function ( date ) { return dateFilter ( date , 'HH' ) ; }
} ,
{
key : 'hh' ,
regex : '0[0-9]|1[0-2]' ,
apply : function ( value ) { this . hours = + value ; } ,
formatter : function ( date ) { return dateFilter ( date , 'hh' ) ; }
} ,
{
key : 'H' ,
regex : '1?[0-9]|2[0-3]' ,
apply : function ( value ) { this . hours = + value ; } ,
formatter : function ( date ) { return dateFilter ( date , 'H' ) ; }
} ,
{
key : 'h' ,
regex : '[0-9]|1[0-2]' ,
apply : function ( value ) { this . hours = + value ; } ,
formatter : function ( date ) { return dateFilter ( date , 'h' ) ; }
} ,
{
key : 'mm' ,
regex : '[0-5][0-9]' ,
apply : function ( value ) { this . minutes = + value ; } ,
formatter : function ( date ) { return dateFilter ( date , 'mm' ) ; }
} ,
{
key : 'm' ,
regex : '[0-9]|[1-5][0-9]' ,
apply : function ( value ) { this . minutes = + value ; } ,
formatter : function ( date ) { return dateFilter ( date , 'm' ) ; }
} ,
{
key : 'sss' ,
regex : '[0-9][0-9][0-9]' ,
apply : function ( value ) { this . milliseconds = + value ; } ,
formatter : function ( date ) { return dateFilter ( date , 'sss' ) ; }
} ,
{
key : 'ss' ,
regex : '[0-5][0-9]' ,
apply : function ( value ) { this . seconds = + value ; } ,
formatter : function ( date ) { return dateFilter ( date , 'ss' ) ; }
} ,
{
key : 's' ,
regex : '[0-9]|[1-5][0-9]' ,
apply : function ( value ) { this . seconds = + value ; } ,
formatter : function ( date ) { return dateFilter ( date , 's' ) ; }
} ,
{
key : 'a' ,
regex : $locale . DATETIME _FORMATS . AMPMS . join ( '|' ) ,
apply : function ( value ) {
if ( this . hours === 12 ) {
this . hours = 0 ;
}
if ( value === 'PM' ) {
this . hours += 12 ;
}
} ,
formatter : function ( date ) { return dateFilter ( date , 'a' ) ; }
} ,
{
key : 'Z' ,
regex : '[+-]\\d{4}' ,
apply : function ( value ) {
var matches = value . match ( /([+-])(\d{2})(\d{2})/ ) ,
sign = matches [ 1 ] ,
hours = matches [ 2 ] ,
minutes = matches [ 3 ] ;
this . hours += toInt ( sign + hours ) ;
this . minutes += toInt ( sign + minutes ) ;
} ,
formatter : function ( date ) {
return dateFilter ( date , 'Z' ) ;
}
} ,
{
key : 'ww' ,
regex : '[0-4][0-9]|5[0-3]' ,
formatter : function ( date ) { return dateFilter ( date , 'ww' ) ; }
} ,
{
key : 'w' ,
regex : '[0-9]|[1-4][0-9]|5[0-3]' ,
formatter : function ( date ) { return dateFilter ( date , 'w' ) ; }
} ,
{
key : 'GGGG' ,
regex : $locale . DATETIME _FORMATS . ERANAMES . join ( '|' ) . replace ( /\s/g , '\\s' ) ,
formatter : function ( date ) { return dateFilter ( date , 'GGGG' ) ; }
} ,
{
key : 'GGG' ,
regex : $locale . DATETIME _FORMATS . ERAS . join ( '|' ) ,
formatter : function ( date ) { return dateFilter ( date , 'GGG' ) ; }
} ,
{
key : 'GG' ,
regex : $locale . DATETIME _FORMATS . ERAS . join ( '|' ) ,
formatter : function ( date ) { return dateFilter ( date , 'GG' ) ; }
} ,
{
key : 'G' ,
regex : $locale . DATETIME _FORMATS . ERAS . join ( '|' ) ,
formatter : function ( date ) { return dateFilter ( date , 'G' ) ; }
}
] ;
2019-03-29 22:00:08 +01:00
if ( angular . version . major >= 1 && angular . version . minor > 4 ) {
formatCodeToRegex . push ( {
key : 'LLLL' ,
regex : $locale . DATETIME _FORMATS . STANDALONEMONTH . join ( '|' ) ,
apply : function ( value ) { this . month = $locale . DATETIME _FORMATS . STANDALONEMONTH . indexOf ( value ) ; } ,
formatter : function ( date ) { return dateFilter ( date , 'LLLL' ) ; }
} ) ;
}
2016-05-16 13:33:49 +02:00
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
this . init ( ) ;
2019-03-29 22:00:08 +01:00
function getFormatCodeToRegex ( key ) {
return filterFilter ( formatCodeToRegex , { key : key } , true ) [ 0 ] ;
}
this . getParser = function ( key ) {
var f = getFormatCodeToRegex ( key ) ;
return f && f . apply || null ;
} ;
this . overrideParser = function ( key , parser ) {
var f = getFormatCodeToRegex ( key ) ;
if ( f && angular . isFunction ( parser ) ) {
this . parsers = { } ;
f . apply = parser ;
}
} . bind ( this ) ;
function createParser ( format ) {
2014-08-10 11:37:05 +02:00
var map = [ ] , regex = format . split ( '' ) ;
2016-05-16 13:33:49 +02:00
// check for literal values
var quoteIndex = format . indexOf ( '\'' ) ;
if ( quoteIndex > - 1 ) {
var inLiteral = false ;
format = format . split ( '' ) ;
for ( var i = quoteIndex ; i < format . length ; i ++ ) {
if ( inLiteral ) {
if ( format [ i ] === '\'' ) {
if ( i + 1 < format . length && format [ i + 1 ] === '\'' ) { // escaped single quote
format [ i + 1 ] = '$' ;
regex [ i + 1 ] = '' ;
} else { // end of literal
regex [ i ] = '' ;
inLiteral = false ;
}
}
format [ i ] = '$' ;
} else {
if ( format [ i ] === '\'' ) { // start of literal
format [ i ] = '$' ;
regex [ i ] = '' ;
inLiteral = true ;
}
}
}
format = format . join ( '' ) ;
}
angular . forEach ( formatCodeToRegex , function ( data ) {
var index = format . indexOf ( data . key ) ;
2014-08-10 11:37:05 +02:00
if ( index > - 1 ) {
format = format . split ( '' ) ;
regex [ index ] = '(' + data . regex + ')' ;
format [ index ] = '$' ; // Custom symbol to define consumed part of format
2016-05-16 13:33:49 +02:00
for ( var i = index + 1 , n = index + data . key . length ; i < n ; i ++ ) {
2014-08-10 11:37:05 +02:00
regex [ i ] = '' ;
format [ i ] = '$' ;
}
format = format . join ( '' ) ;
2016-05-16 13:33:49 +02:00
map . push ( {
index : index ,
key : data . key ,
2019-03-29 22:00:08 +01:00
apply : data . apply ,
2016-05-16 13:33:49 +02:00
matcher : data . regex
} ) ;
2014-08-10 11:37:05 +02:00
}
} ) ;
return {
regex : new RegExp ( '^' + regex . join ( '' ) + '$' ) ,
map : orderByFilter ( map , 'index' )
} ;
2016-05-16 13:33:49 +02:00
}
2019-03-29 22:00:08 +01:00
function createFormatter ( format ) {
var formatters = [ ] ;
var i = 0 ;
var formatter , literalIdx ;
while ( i < format . length ) {
if ( angular . isNumber ( literalIdx ) ) {
if ( format . charAt ( i ) === '\'' ) {
if ( i + 1 >= format . length || format . charAt ( i + 1 ) !== '\'' ) {
formatters . push ( constructLiteralFormatter ( format , literalIdx , i ) ) ;
literalIdx = null ;
}
} else if ( i === format . length ) {
while ( literalIdx < format . length ) {
formatter = constructFormatterFromIdx ( format , literalIdx ) ;
formatters . push ( formatter ) ;
literalIdx = formatter . endIdx ;
}
}
i ++ ;
continue ;
}
if ( format . charAt ( i ) === '\'' ) {
literalIdx = i ;
i ++ ;
continue ;
}
formatter = constructFormatterFromIdx ( format , i ) ;
formatters . push ( formatter . parser ) ;
i = formatter . endIdx ;
}
return formatters ;
}
function constructLiteralFormatter ( format , literalIdx , endIdx ) {
return function ( ) {
return format . substr ( literalIdx + 1 , endIdx - literalIdx - 1 ) ;
} ;
}
function constructFormatterFromIdx ( format , i ) {
var currentPosStr = format . substr ( i ) ;
for ( var j = 0 ; j < formatCodeToRegex . length ; j ++ ) {
if ( new RegExp ( '^' + formatCodeToRegex [ j ] . key ) . test ( currentPosStr ) ) {
var data = formatCodeToRegex [ j ] ;
return {
endIdx : i + data . key . length ,
parser : data . formatter
} ;
}
}
return {
endIdx : i + 1 ,
parser : function ( ) {
return currentPosStr . charAt ( 0 ) ;
}
} ;
}
2016-05-16 13:33:49 +02:00
this . filter = function ( date , format ) {
if ( ! angular . isDate ( date ) || isNaN ( date ) || ! format ) {
return '' ;
}
format = $locale . DATETIME _FORMATS [ format ] || format ;
if ( $locale . id !== localeId ) {
this . init ( ) ;
}
if ( ! this . formatters [ format ] ) {
2019-03-29 22:00:08 +01:00
this . formatters [ format ] = createFormatter ( format ) ;
2016-05-16 13:33:49 +02:00
}
2019-03-29 22:00:08 +01:00
var formatters = this . formatters [ format ] ;
2016-05-16 13:33:49 +02:00
2019-03-29 22:00:08 +01:00
return formatters . reduce ( function ( str , formatter ) {
return str + formatter ( date ) ;
2016-05-16 13:33:49 +02:00
} , '' ) ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
this . parse = function ( input , format , baseDate ) {
if ( ! angular . isString ( input ) || ! format ) {
2014-08-10 11:37:05 +02:00
return input ;
}
format = $locale . DATETIME _FORMATS [ format ] || format ;
2016-05-16 13:33:49 +02:00
format = format . replace ( SPECIAL _CHARACTERS _REGEXP , '\\$&' ) ;
if ( $locale . id !== localeId ) {
this . init ( ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( ! this . parsers [ format ] ) {
this . parsers [ format ] = createParser ( format , 'apply' ) ;
2014-08-10 11:37:05 +02:00
}
var parser = this . parsers [ format ] ,
regex = parser . regex ,
map = parser . map ,
2016-05-16 13:33:49 +02:00
results = input . match ( regex ) ,
tzOffset = false ;
if ( results && results . length ) {
var fields , dt ;
if ( angular . isDate ( baseDate ) && ! isNaN ( baseDate . getTime ( ) ) ) {
fields = {
year : baseDate . getFullYear ( ) ,
month : baseDate . getMonth ( ) ,
date : baseDate . getDate ( ) ,
hours : baseDate . getHours ( ) ,
minutes : baseDate . getMinutes ( ) ,
seconds : baseDate . getSeconds ( ) ,
milliseconds : baseDate . getMilliseconds ( )
} ;
} else {
if ( baseDate ) {
$log . warn ( 'dateparser:' , 'baseDate is not a valid date' ) ;
}
fields = { year : 1900 , month : 0 , date : 1 , hours : 0 , minutes : 0 , seconds : 0 , milliseconds : 0 } ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
for ( var i = 1 , n = results . length ; i < n ; i ++ ) {
var mapper = map [ i - 1 ] ;
if ( mapper . matcher === 'Z' ) {
tzOffset = true ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( mapper . apply ) {
2014-08-10 11:37:05 +02:00
mapper . apply . call ( fields , results [ i ] ) ;
}
}
2016-05-16 13:33:49 +02:00
var datesetter = tzOffset ? Date . prototype . setUTCFullYear :
Date . prototype . setFullYear ;
var timesetter = tzOffset ? Date . prototype . setUTCHours :
Date . prototype . setHours ;
if ( isValid ( fields . year , fields . month , fields . date ) ) {
if ( angular . isDate ( baseDate ) && ! isNaN ( baseDate . getTime ( ) ) && ! tzOffset ) {
dt = new Date ( baseDate ) ;
datesetter . call ( dt , fields . year , fields . month , fields . date ) ;
timesetter . call ( dt , fields . hours , fields . minutes ,
fields . seconds , fields . milliseconds ) ;
} else {
dt = new Date ( 0 ) ;
datesetter . call ( dt , fields . year , fields . month , fields . date ) ;
timesetter . call ( dt , fields . hours || 0 , fields . minutes || 0 ,
fields . seconds || 0 , fields . milliseconds || 0 ) ;
}
2014-08-10 11:37:05 +02:00
}
return dt ;
}
} ;
// Check if date is valid for specific month (and year for February).
// Month: 0 = Jan, 1 = Feb, etc
function isValid ( year , month , date ) {
2016-05-16 13:33:49 +02:00
if ( date < 1 ) {
return false ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
if ( month === 1 && date > 28 ) {
return date === 29 && ( year % 4 === 0 && year % 100 !== 0 || year % 400 === 0 ) ;
}
if ( month === 3 || month === 5 || month === 8 || month === 10 ) {
return date < 31 ;
2014-08-10 11:37:05 +02:00
}
return true ;
}
2016-05-16 13:33:49 +02:00
function toInt ( str ) {
return parseInt ( str , 10 ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
this . toTimezone = toTimezone ;
this . fromTimezone = fromTimezone ;
this . timezoneToOffset = timezoneToOffset ;
this . addDateMinutes = addDateMinutes ;
this . convertTimezoneToLocal = convertTimezoneToLocal ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function toTimezone ( date , timezone ) {
return date && timezone ? convertTimezoneToLocal ( date , timezone ) : date ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function fromTimezone ( date , timezone ) {
return date && timezone ? convertTimezoneToLocal ( date , timezone , true ) : date ;
}
2014-08-10 11:37:05 +02:00
2016-06-11 17:57:30 +02:00
//https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207
2016-05-16 13:33:49 +02:00
function timezoneToOffset ( timezone , fallback ) {
2016-06-11 17:57:30 +02:00
timezone = timezone . replace ( /:/g , '' ) ;
2016-05-16 13:33:49 +02:00
var requestedTimezoneOffset = Date . parse ( 'Jan 01, 1970 00:00:00 ' + timezone ) / 60000 ;
return isNaN ( requestedTimezoneOffset ) ? fallback : requestedTimezoneOffset ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function addDateMinutes ( date , minutes ) {
date = new Date ( date . getTime ( ) ) ;
date . setMinutes ( date . getMinutes ( ) + minutes ) ;
return date ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function convertTimezoneToLocal ( date , timezone , reverse ) {
reverse = reverse ? - 1 : 1 ;
2016-06-11 17:57:30 +02:00
var dateTimezoneOffset = date . getTimezoneOffset ( ) ;
var timezoneOffset = timezoneToOffset ( timezone , dateTimezoneOffset ) ;
return addDateMinutes ( date , reverse * ( timezoneOffset - dateTimezoneOffset ) ) ;
2016-05-16 13:33:49 +02:00
}
} ] ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to
// at most one element.
angular . module ( 'ui.bootstrap.isClass' , [ ] )
. directive ( 'uibIsClass' , [
'$animate' ,
function ( $animate ) {
// 11111111 22222222
var ON _REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/ ;
// 11111111 22222222
var IS _REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/ ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var dataPerTracked = { } ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
return {
restrict : 'A' ,
compile : function ( tElement , tAttrs ) {
var linkedScopes = [ ] ;
var instances = [ ] ;
var expToData = { } ;
var lastActivated = null ;
var onExpMatches = tAttrs . uibIsClass . match ( ON _REGEXP ) ;
var onExp = onExpMatches [ 2 ] ;
var expsStr = onExpMatches [ 1 ] ;
var exps = expsStr . split ( ',' ) ;
return linkFn ;
function linkFn ( scope , element , attrs ) {
linkedScopes . push ( scope ) ;
instances . push ( {
scope : scope ,
element : element
} ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
exps . forEach ( function ( exp , k ) {
addForExp ( exp , scope ) ;
} ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
scope . $on ( '$destroy' , removeScope ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function addForExp ( exp , scope ) {
var matches = exp . match ( IS _REGEXP ) ;
var clazz = scope . $eval ( matches [ 1 ] ) ;
var compareWithExp = matches [ 2 ] ;
var data = expToData [ exp ] ;
if ( ! data ) {
var watchFn = function ( compareWithVal ) {
var newActivated = null ;
instances . some ( function ( instance ) {
var thisVal = instance . scope . $eval ( onExp ) ;
if ( thisVal === compareWithVal ) {
newActivated = instance ;
return true ;
}
} ) ;
if ( data . lastActivated !== newActivated ) {
if ( data . lastActivated ) {
$animate . removeClass ( data . lastActivated . element , clazz ) ;
}
if ( newActivated ) {
$animate . addClass ( newActivated . element , clazz ) ;
}
data . lastActivated = newActivated ;
}
} ;
expToData [ exp ] = data = {
lastActivated : null ,
scope : scope ,
watchFn : watchFn ,
compareWithExp : compareWithExp ,
watcher : scope . $watch ( compareWithExp , watchFn )
} ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
data . watchFn ( scope . $eval ( compareWithExp ) ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function removeScope ( e ) {
var removedScope = e . targetScope ;
var index = linkedScopes . indexOf ( removedScope ) ;
linkedScopes . splice ( index , 1 ) ;
instances . splice ( index , 1 ) ;
if ( linkedScopes . length ) {
var newWatchScope = linkedScopes [ 0 ] ;
angular . forEach ( expToData , function ( data ) {
if ( data . scope === removedScope ) {
data . watcher = newWatchScope . $watch ( data . compareWithExp , data . watchFn ) ;
data . scope = newWatchScope ;
}
} ) ;
} else {
expToData = { } ;
}
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
}
} ;
} ] ) ;
angular . module ( 'ui.bootstrap.datepicker' , [ 'ui.bootstrap.dateparser' , 'ui.bootstrap.isClass' ] )
. value ( '$datepickerSuppressError' , false )
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
. value ( '$datepickerLiteralWarning' , true )
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
. constant ( 'uibDatepickerConfig' , {
datepickerMode : 'day' ,
2014-08-10 11:37:05 +02:00
formatDay : 'dd' ,
formatMonth : 'MMMM' ,
formatYear : 'yyyy' ,
formatDayHeader : 'EEE' ,
formatDayTitle : 'MMMM yyyy' ,
formatMonthTitle : 'yyyy' ,
2016-05-16 13:33:49 +02:00
maxDate : null ,
2014-08-10 11:37:05 +02:00
maxMode : 'year' ,
minDate : null ,
2016-05-16 13:33:49 +02:00
minMode : 'day' ,
2019-03-29 22:00:08 +01:00
monthColumns : 3 ,
2016-05-16 13:33:49 +02:00
ngModelOptions : { } ,
shortcutPropagation : false ,
showWeeks : true ,
yearColumns : 5 ,
yearRows : 4
2014-08-10 11:37:05 +02:00
} )
2019-03-29 22:00:08 +01:00
. controller ( 'UibDatepickerController' , [ '$scope' , '$element' , '$attrs' , '$parse' , '$interpolate' , '$locale' , '$log' , 'dateFilter' , 'uibDatepickerConfig' , '$datepickerLiteralWarning' , '$datepickerSuppressError' , 'uibDateParser' ,
function ( $scope , $element , $attrs , $parse , $interpolate , $locale , $log , dateFilter , datepickerConfig , $datepickerLiteralWarning , $datepickerSuppressError , dateParser ) {
2014-08-10 11:37:05 +02:00
var self = this ,
2016-05-16 13:33:49 +02:00
ngModelCtrl = { $setViewValue : angular . noop } , // nullModelCtrl;
ngModelOptions = { } ,
2019-03-29 22:00:08 +01:00
watchListeners = [ ] ;
$element . addClass ( 'uib-datepicker' ) ;
$attrs . $set ( 'role' , 'application' ) ;
2016-05-16 13:33:49 +02:00
if ( ! $scope . datepickerOptions ) {
$scope . datepickerOptions = { } ;
}
2014-08-10 11:37:05 +02:00
// Modes chain
this . modes = [ 'day' , 'month' , 'year' ] ;
2016-05-16 13:33:49 +02:00
[
'customClass' ,
'dateDisabled' ,
'datepickerMode' ,
'formatDay' ,
'formatDayHeader' ,
'formatDayTitle' ,
'formatMonth' ,
'formatMonthTitle' ,
'formatYear' ,
'maxDate' ,
'maxMode' ,
'minDate' ,
'minMode' ,
2019-03-29 22:00:08 +01:00
'monthColumns' ,
2016-05-16 13:33:49 +02:00
'showWeeks' ,
'shortcutPropagation' ,
'startingDay' ,
'yearColumns' ,
'yearRows'
] . forEach ( function ( key ) {
switch ( key ) {
case 'customClass' :
case 'dateDisabled' :
$scope [ key ] = $scope . datepickerOptions [ key ] || angular . noop ;
break ;
case 'datepickerMode' :
$scope . datepickerMode = angular . isDefined ( $scope . datepickerOptions . datepickerMode ) ?
$scope . datepickerOptions . datepickerMode : datepickerConfig . datepickerMode ;
break ;
case 'formatDay' :
case 'formatDayHeader' :
case 'formatDayTitle' :
case 'formatMonth' :
case 'formatMonthTitle' :
case 'formatYear' :
self [ key ] = angular . isDefined ( $scope . datepickerOptions [ key ] ) ?
$interpolate ( $scope . datepickerOptions [ key ] ) ( $scope . $parent ) :
datepickerConfig [ key ] ;
break ;
2019-03-29 22:00:08 +01:00
case 'monthColumns' :
2016-05-16 13:33:49 +02:00
case 'showWeeks' :
case 'shortcutPropagation' :
case 'yearColumns' :
case 'yearRows' :
self [ key ] = angular . isDefined ( $scope . datepickerOptions [ key ] ) ?
$scope . datepickerOptions [ key ] : datepickerConfig [ key ] ;
break ;
case 'startingDay' :
if ( angular . isDefined ( $scope . datepickerOptions . startingDay ) ) {
self . startingDay = $scope . datepickerOptions . startingDay ;
} else if ( angular . isNumber ( datepickerConfig . startingDay ) ) {
self . startingDay = datepickerConfig . startingDay ;
} else {
self . startingDay = ( $locale . DATETIME _FORMATS . FIRSTDAYOFWEEK + 8 ) % 7 ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
break ;
case 'maxDate' :
case 'minDate' :
$scope . $watch ( 'datepickerOptions.' + key , function ( value ) {
if ( value ) {
if ( angular . isDate ( value ) ) {
2019-03-29 22:00:08 +01:00
self [ key ] = dateParser . fromTimezone ( new Date ( value ) , ngModelOptions . getOption ( 'timezone' ) ) ;
2016-05-16 13:33:49 +02:00
} else {
if ( $datepickerLiteralWarning ) {
$log . warn ( 'Literal date support has been deprecated, please switch to date object usage' ) ;
}
self [ key ] = new Date ( dateFilter ( value , 'medium' ) ) ;
}
} else {
self [ key ] = datepickerConfig [ key ] ?
2019-03-29 22:00:08 +01:00
dateParser . fromTimezone ( new Date ( datepickerConfig [ key ] ) , ngModelOptions . getOption ( 'timezone' ) ) :
2016-05-16 13:33:49 +02:00
null ;
}
self . refreshView ( ) ;
} ) ;
break ;
case 'maxMode' :
case 'minMode' :
if ( $scope . datepickerOptions [ key ] ) {
$scope . $watch ( function ( ) { return $scope . datepickerOptions [ key ] ; } , function ( value ) {
2019-03-29 22:00:08 +01:00
self [ key ] = $scope [ key ] = angular . isDefined ( value ) ? value : $scope . datepickerOptions [ key ] ;
2016-05-16 13:33:49 +02:00
if ( key === 'minMode' && self . modes . indexOf ( $scope . datepickerOptions . datepickerMode ) < self . modes . indexOf ( self [ key ] ) ||
key === 'maxMode' && self . modes . indexOf ( $scope . datepickerOptions . datepickerMode ) > self . modes . indexOf ( self [ key ] ) ) {
$scope . datepickerMode = self [ key ] ;
$scope . datepickerOptions . datepickerMode = self [ key ] ;
}
} ) ;
} else {
self [ key ] = $scope [ key ] = datepickerConfig [ key ] || null ;
}
break ;
2014-08-10 11:37:05 +02:00
}
} ) ;
$scope . uniqueId = 'datepicker-' + $scope . $id + '-' + Math . floor ( Math . random ( ) * 10000 ) ;
2016-05-16 13:33:49 +02:00
$scope . disabled = angular . isDefined ( $attrs . disabled ) || false ;
if ( angular . isDefined ( $attrs . ngDisabled ) ) {
watchListeners . push ( $scope . $parent . $watch ( $attrs . ngDisabled , function ( disabled ) {
$scope . disabled = disabled ;
self . refreshView ( ) ;
} ) ) ;
}
2014-08-10 11:37:05 +02:00
$scope . isActive = function ( dateObject ) {
if ( self . compare ( dateObject . date , self . activeDate ) === 0 ) {
$scope . activeDateId = dateObject . uid ;
return true ;
}
return false ;
} ;
2016-05-16 13:33:49 +02:00
this . init = function ( ngModelCtrl _ ) {
2014-08-10 11:37:05 +02:00
ngModelCtrl = ngModelCtrl _ ;
2019-03-29 22:00:08 +01:00
ngModelOptions = extractOptions ( ngModelCtrl ) ;
2016-05-16 13:33:49 +02:00
if ( $scope . datepickerOptions . initDate ) {
2019-03-29 22:00:08 +01:00
self . activeDate = dateParser . fromTimezone ( $scope . datepickerOptions . initDate , ngModelOptions . getOption ( 'timezone' ) ) || new Date ( ) ;
2016-05-16 13:33:49 +02:00
$scope . $watch ( 'datepickerOptions.initDate' , function ( initDate ) {
if ( initDate && ( ngModelCtrl . $isEmpty ( ngModelCtrl . $modelValue ) || ngModelCtrl . $invalid ) ) {
2019-03-29 22:00:08 +01:00
self . activeDate = dateParser . fromTimezone ( initDate , ngModelOptions . getOption ( 'timezone' ) ) ;
2016-05-16 13:33:49 +02:00
self . refreshView ( ) ;
}
} ) ;
} else {
self . activeDate = new Date ( ) ;
}
2016-06-11 17:57:30 +02:00
var date = ngModelCtrl . $modelValue ? new Date ( ngModelCtrl . $modelValue ) : new Date ( ) ;
this . activeDate = ! isNaN ( date ) ?
2019-03-29 22:00:08 +01:00
dateParser . fromTimezone ( date , ngModelOptions . getOption ( 'timezone' ) ) :
dateParser . fromTimezone ( new Date ( ) , ngModelOptions . getOption ( 'timezone' ) ) ;
2014-08-10 11:37:05 +02:00
ngModelCtrl . $render = function ( ) {
self . render ( ) ;
} ;
} ;
this . render = function ( ) {
2016-05-16 13:33:49 +02:00
if ( ngModelCtrl . $viewValue ) {
var date = new Date ( ngModelCtrl . $viewValue ) ,
2014-08-10 11:37:05 +02:00
isValid = ! isNaN ( date ) ;
2016-05-16 13:33:49 +02:00
if ( isValid ) {
2019-03-29 22:00:08 +01:00
this . activeDate = dateParser . fromTimezone ( date , ngModelOptions . getOption ( 'timezone' ) ) ;
2016-05-16 13:33:49 +02:00
} else if ( ! $datepickerSuppressError ) {
$log . error ( 'Datepicker directive: "ng-model" value must be a Date object' ) ;
2014-08-10 11:37:05 +02:00
}
}
this . refreshView ( ) ;
} ;
this . refreshView = function ( ) {
2016-05-16 13:33:49 +02:00
if ( this . element ) {
$scope . selectedDt = null ;
2014-08-10 11:37:05 +02:00
this . _refreshView ( ) ;
2016-05-16 13:33:49 +02:00
if ( $scope . activeDt ) {
$scope . activeDateId = $scope . activeDt . uid ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var date = ngModelCtrl . $viewValue ? new Date ( ngModelCtrl . $viewValue ) : null ;
2019-03-29 22:00:08 +01:00
date = dateParser . fromTimezone ( date , ngModelOptions . getOption ( 'timezone' ) ) ;
2016-05-16 13:33:49 +02:00
ngModelCtrl . $setValidity ( 'dateDisabled' , ! date ||
this . element && ! this . isDisabled ( date ) ) ;
2014-08-10 11:37:05 +02:00
}
} ;
this . createDateObject = function ( date , format ) {
2016-05-16 13:33:49 +02:00
var model = ngModelCtrl . $viewValue ? new Date ( ngModelCtrl . $viewValue ) : null ;
2019-03-29 22:00:08 +01:00
model = dateParser . fromTimezone ( model , ngModelOptions . getOption ( 'timezone' ) ) ;
2016-05-16 13:33:49 +02:00
var today = new Date ( ) ;
2019-03-29 22:00:08 +01:00
today = dateParser . fromTimezone ( today , ngModelOptions . getOption ( 'timezone' ) ) ;
2016-05-16 13:33:49 +02:00
var time = this . compare ( date , today ) ;
var dt = {
2014-08-10 11:37:05 +02:00
date : date ,
2016-05-16 13:33:49 +02:00
label : dateParser . filter ( date , format ) ,
2014-08-10 11:37:05 +02:00
selected : model && this . compare ( date , model ) === 0 ,
disabled : this . isDisabled ( date ) ,
2016-05-16 13:33:49 +02:00
past : time < 0 ,
current : time === 0 ,
future : time > 0 ,
customClass : this . customClass ( date ) || null
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
if ( model && this . compare ( date , model ) === 0 ) {
$scope . selectedDt = dt ;
}
if ( self . activeDate && this . compare ( dt . date , self . activeDate ) === 0 ) {
$scope . activeDt = dt ;
}
return dt ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
this . isDisabled = function ( date ) {
return $scope . disabled ||
this . minDate && this . compare ( date , this . minDate ) < 0 ||
this . maxDate && this . compare ( date , this . maxDate ) > 0 ||
$scope . dateDisabled && $scope . dateDisabled ( { date : date , mode : $scope . datepickerMode } ) ;
} ;
this . customClass = function ( date ) {
return $scope . customClass ( { date : date , mode : $scope . datepickerMode } ) ;
2014-08-10 11:37:05 +02:00
} ;
// Split array into smaller arrays
this . split = function ( arr , size ) {
var arrays = [ ] ;
while ( arr . length > 0 ) {
arrays . push ( arr . splice ( 0 , size ) ) ;
}
return arrays ;
} ;
2016-05-16 13:33:49 +02:00
$scope . select = function ( date ) {
if ( $scope . datepickerMode === self . minMode ) {
2019-03-29 22:00:08 +01:00
var dt = ngModelCtrl . $viewValue ? dateParser . fromTimezone ( new Date ( ngModelCtrl . $viewValue ) , ngModelOptions . getOption ( 'timezone' ) ) : new Date ( 0 , 0 , 0 , 0 , 0 , 0 , 0 ) ;
2016-05-16 13:33:49 +02:00
dt . setFullYear ( date . getFullYear ( ) , date . getMonth ( ) , date . getDate ( ) ) ;
2019-03-29 22:00:08 +01:00
dt = dateParser . toTimezone ( dt , ngModelOptions . getOption ( 'timezone' ) ) ;
2016-05-16 13:33:49 +02:00
ngModelCtrl . $setViewValue ( dt ) ;
2014-08-10 11:37:05 +02:00
ngModelCtrl . $render ( ) ;
} else {
self . activeDate = date ;
2016-05-16 13:33:49 +02:00
setMode ( self . modes [ self . modes . indexOf ( $scope . datepickerMode ) - 1 ] ) ;
$scope . $emit ( 'uib:datepicker.mode' ) ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
$scope . $broadcast ( 'uib:datepicker.focus' ) ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
$scope . move = function ( direction ) {
2014-08-10 11:37:05 +02:00
var year = self . activeDate . getFullYear ( ) + direction * ( self . step . years || 0 ) ,
month = self . activeDate . getMonth ( ) + direction * ( self . step . months || 0 ) ;
self . activeDate . setFullYear ( year , month , 1 ) ;
self . refreshView ( ) ;
} ;
2016-05-16 13:33:49 +02:00
$scope . toggleMode = function ( direction ) {
2014-08-10 11:37:05 +02:00
direction = direction || 1 ;
2016-05-16 13:33:49 +02:00
if ( $scope . datepickerMode === self . maxMode && direction === 1 ||
$scope . datepickerMode === self . minMode && direction === - 1 ) {
2014-08-10 11:37:05 +02:00
return ;
}
2016-05-16 13:33:49 +02:00
setMode ( self . modes [ self . modes . indexOf ( $scope . datepickerMode ) + direction ] ) ;
$scope . $emit ( 'uib:datepicker.mode' ) ;
2014-08-10 11:37:05 +02:00
} ;
// Key event mapper
2016-05-16 13:33:49 +02:00
$scope . keys = { 13 : 'enter' , 32 : 'space' , 33 : 'pageup' , 34 : 'pagedown' , 35 : 'end' , 36 : 'home' , 37 : 'left' , 38 : 'up' , 39 : 'right' , 40 : 'down' } ;
2014-08-10 11:37:05 +02:00
var focusElement = function ( ) {
2016-05-16 13:33:49 +02:00
self . element [ 0 ] . focus ( ) ;
2014-08-10 11:37:05 +02:00
} ;
// Listen for focus requests from popup directive
2016-05-16 13:33:49 +02:00
$scope . $on ( 'uib:datepicker.focus' , focusElement ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$scope . keydown = function ( evt ) {
2014-08-10 11:37:05 +02:00
var key = $scope . keys [ evt . which ] ;
2016-05-16 13:33:49 +02:00
if ( ! key || evt . shiftKey || evt . altKey || $scope . disabled ) {
2014-08-10 11:37:05 +02:00
return ;
}
evt . preventDefault ( ) ;
2016-05-16 13:33:49 +02:00
if ( ! self . shortcutPropagation ) {
evt . stopPropagation ( ) ;
}
2014-08-10 11:37:05 +02:00
if ( key === 'enter' || key === 'space' ) {
2016-05-16 13:33:49 +02:00
if ( self . isDisabled ( self . activeDate ) ) {
2014-08-10 11:37:05 +02:00
return ; // do nothing
}
$scope . select ( self . activeDate ) ;
} else if ( evt . ctrlKey && ( key === 'up' || key === 'down' ) ) {
$scope . toggleMode ( key === 'up' ? 1 : - 1 ) ;
} else {
self . handleKeyDown ( key , evt ) ;
self . refreshView ( ) ;
}
} ;
2019-03-29 22:00:08 +01:00
$element . on ( 'keydown' , function ( evt ) {
$scope . $apply ( function ( ) {
$scope . keydown ( evt ) ;
} ) ;
} ) ;
2016-05-16 13:33:49 +02:00
$scope . $on ( '$destroy' , function ( ) {
//Clear all watch listeners on destroy
while ( watchListeners . length ) {
watchListeners . shift ( ) ( ) ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
} ) ;
function setMode ( mode ) {
$scope . datepickerMode = mode ;
$scope . datepickerOptions . datepickerMode = mode ;
}
2019-03-29 22:00:08 +01:00
function extractOptions ( ngModelCtrl ) {
var ngModelOptions ;
if ( angular . version . minor < 6 ) { // in angular < 1.6 $options could be missing
// guarantee a value
ngModelOptions = ngModelCtrl . $options ||
$scope . datepickerOptions . ngModelOptions ||
datepickerConfig . ngModelOptions ||
{ } ;
// mimic 1.6+ api
ngModelOptions . getOption = function ( key ) {
return ngModelOptions [ key ] ;
} ;
} else { // in angular >=1.6 $options is always present
// ng-model-options defaults timezone to null; don't let its precedence squash a non-null value
var timezone = ngModelCtrl . $options . getOption ( 'timezone' ) ||
( $scope . datepickerOptions . ngModelOptions ? $scope . datepickerOptions . ngModelOptions . timezone : null ) ||
( datepickerConfig . ngModelOptions ? datepickerConfig . ngModelOptions . timezone : null ) ;
// values passed to createChild override existing values
ngModelOptions = ngModelCtrl . $options // start with a ModelOptions instance
. createChild ( datepickerConfig . ngModelOptions ) // lowest precedence
. createChild ( $scope . datepickerOptions . ngModelOptions )
. createChild ( ngModelCtrl . $options ) // highest precedence
. createChild ( { timezone : timezone } ) ; // to keep from squashing a non-null value
}
return ngModelOptions ;
}
2016-05-16 13:33:49 +02:00
} ] )
. controller ( 'UibDaypickerController' , [ '$scope' , '$element' , 'dateFilter' , function ( scope , $element , dateFilter ) {
var DAYS _IN _MONTH = [ 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 ] ;
this . step = { months : 1 } ;
this . element = $element ;
function getDaysInMonth ( year , month ) {
return month === 1 && year % 4 === 0 &&
( year % 100 !== 0 || year % 400 === 0 ) ? 29 : DAYS _IN _MONTH [ month ] ;
}
this . init = function ( ctrl ) {
angular . extend ( ctrl , this ) ;
scope . showWeeks = ctrl . showWeeks ;
ctrl . refreshView ( ) ;
} ;
this . getDates = function ( startDate , n ) {
var dates = new Array ( n ) , current = new Date ( startDate ) , i = 0 , date ;
while ( i < n ) {
date = new Date ( current ) ;
dates [ i ++ ] = date ;
current . setDate ( current . getDate ( ) + 1 ) ;
}
return dates ;
} ;
this . _refreshView = function ( ) {
var year = this . activeDate . getFullYear ( ) ,
month = this . activeDate . getMonth ( ) ,
firstDayOfMonth = new Date ( this . activeDate ) ;
firstDayOfMonth . setFullYear ( year , month , 1 ) ;
var difference = this . startingDay - firstDayOfMonth . getDay ( ) ,
numDisplayedFromPreviousMonth = difference > 0 ?
7 - difference : - difference ,
firstDate = new Date ( firstDayOfMonth ) ;
if ( numDisplayedFromPreviousMonth > 0 ) {
firstDate . setDate ( - numDisplayedFromPreviousMonth + 1 ) ;
}
// 42 is the number of days on a six-week calendar
var days = this . getDates ( firstDate , 42 ) ;
for ( var i = 0 ; i < 42 ; i ++ ) {
days [ i ] = angular . extend ( this . createDateObject ( days [ i ] , this . formatDay ) , {
secondary : days [ i ] . getMonth ( ) !== month ,
uid : scope . uniqueId + '-' + i
} ) ;
}
scope . labels = new Array ( 7 ) ;
for ( var j = 0 ; j < 7 ; j ++ ) {
scope . labels [ j ] = {
abbr : dateFilter ( days [ j ] . date , this . formatDayHeader ) ,
full : dateFilter ( days [ j ] . date , 'EEEE' )
} ;
}
scope . title = dateFilter ( this . activeDate , this . formatDayTitle ) ;
scope . rows = this . split ( days , 7 ) ;
if ( scope . showWeeks ) {
scope . weekNumbers = [ ] ;
var thursdayIndex = ( 4 + 7 - this . startingDay ) % 7 ,
numWeeks = scope . rows . length ;
for ( var curWeek = 0 ; curWeek < numWeeks ; curWeek ++ ) {
scope . weekNumbers . push (
getISO8601WeekNumber ( scope . rows [ curWeek ] [ thursdayIndex ] . date ) ) ;
}
}
} ;
this . compare = function ( date1 , date2 ) {
var _date1 = new Date ( date1 . getFullYear ( ) , date1 . getMonth ( ) , date1 . getDate ( ) ) ;
var _date2 = new Date ( date2 . getFullYear ( ) , date2 . getMonth ( ) , date2 . getDate ( ) ) ;
_date1 . setFullYear ( date1 . getFullYear ( ) ) ;
_date2 . setFullYear ( date2 . getFullYear ( ) ) ;
return _date1 - _date2 ;
} ;
function getISO8601WeekNumber ( date ) {
var checkDate = new Date ( date ) ;
checkDate . setDate ( checkDate . getDate ( ) + 4 - ( checkDate . getDay ( ) || 7 ) ) ; // Thursday
var time = checkDate . getTime ( ) ;
checkDate . setMonth ( 0 ) ; // Compare with Jan 1
checkDate . setDate ( 1 ) ;
return Math . floor ( Math . round ( ( time - checkDate ) / 86400000 ) / 7 ) + 1 ;
}
this . handleKeyDown = function ( key , evt ) {
var date = this . activeDate . getDate ( ) ;
if ( key === 'left' ) {
date = date - 1 ;
} else if ( key === 'up' ) {
date = date - 7 ;
} else if ( key === 'right' ) {
date = date + 1 ;
} else if ( key === 'down' ) {
date = date + 7 ;
} else if ( key === 'pageup' || key === 'pagedown' ) {
var month = this . activeDate . getMonth ( ) + ( key === 'pageup' ? - 1 : 1 ) ;
this . activeDate . setMonth ( month , 1 ) ;
date = Math . min ( getDaysInMonth ( this . activeDate . getFullYear ( ) , this . activeDate . getMonth ( ) ) , date ) ;
} else if ( key === 'home' ) {
date = 1 ;
} else if ( key === 'end' ) {
date = getDaysInMonth ( this . activeDate . getFullYear ( ) , this . activeDate . getMonth ( ) ) ;
}
this . activeDate . setDate ( date ) ;
} ;
} ] )
. controller ( 'UibMonthpickerController' , [ '$scope' , '$element' , 'dateFilter' , function ( scope , $element , dateFilter ) {
this . step = { years : 1 } ;
this . element = $element ;
this . init = function ( ctrl ) {
angular . extend ( ctrl , this ) ;
ctrl . refreshView ( ) ;
} ;
this . _refreshView = function ( ) {
var months = new Array ( 12 ) ,
year = this . activeDate . getFullYear ( ) ,
date ;
for ( var i = 0 ; i < 12 ; i ++ ) {
date = new Date ( this . activeDate ) ;
date . setFullYear ( year , i , 1 ) ;
months [ i ] = angular . extend ( this . createDateObject ( date , this . formatMonth ) , {
uid : scope . uniqueId + '-' + i
} ) ;
}
scope . title = dateFilter ( this . activeDate , this . formatMonthTitle ) ;
2019-03-29 22:00:08 +01:00
scope . rows = this . split ( months , this . monthColumns ) ;
scope . yearHeaderColspan = this . monthColumns > 3 ? this . monthColumns - 2 : 1 ;
2016-05-16 13:33:49 +02:00
} ;
this . compare = function ( date1 , date2 ) {
var _date1 = new Date ( date1 . getFullYear ( ) , date1 . getMonth ( ) ) ;
var _date2 = new Date ( date2 . getFullYear ( ) , date2 . getMonth ( ) ) ;
_date1 . setFullYear ( date1 . getFullYear ( ) ) ;
_date2 . setFullYear ( date2 . getFullYear ( ) ) ;
return _date1 - _date2 ;
} ;
this . handleKeyDown = function ( key , evt ) {
var date = this . activeDate . getMonth ( ) ;
if ( key === 'left' ) {
date = date - 1 ;
} else if ( key === 'up' ) {
2019-03-29 22:00:08 +01:00
date = date - this . monthColumns ;
2016-05-16 13:33:49 +02:00
} else if ( key === 'right' ) {
date = date + 1 ;
} else if ( key === 'down' ) {
2019-03-29 22:00:08 +01:00
date = date + this . monthColumns ;
2016-05-16 13:33:49 +02:00
} else if ( key === 'pageup' || key === 'pagedown' ) {
var year = this . activeDate . getFullYear ( ) + ( key === 'pageup' ? - 1 : 1 ) ;
this . activeDate . setFullYear ( year ) ;
} else if ( key === 'home' ) {
date = 0 ;
} else if ( key === 'end' ) {
date = 11 ;
}
this . activeDate . setMonth ( date ) ;
} ;
} ] )
. controller ( 'UibYearpickerController' , [ '$scope' , '$element' , 'dateFilter' , function ( scope , $element , dateFilter ) {
var columns , range ;
this . element = $element ;
function getStartingYear ( year ) {
return parseInt ( ( year - 1 ) / range , 10 ) * range + 1 ;
}
this . yearpickerInit = function ( ) {
columns = this . yearColumns ;
range = this . yearRows * columns ;
this . step = { years : range } ;
} ;
this . _refreshView = function ( ) {
var years = new Array ( range ) , date ;
for ( var i = 0 , start = getStartingYear ( this . activeDate . getFullYear ( ) ) ; i < range ; i ++ ) {
date = new Date ( this . activeDate ) ;
date . setFullYear ( start + i , 0 , 1 ) ;
years [ i ] = angular . extend ( this . createDateObject ( date , this . formatYear ) , {
uid : scope . uniqueId + '-' + i
} ) ;
}
scope . title = [ years [ 0 ] . label , years [ range - 1 ] . label ] . join ( ' - ' ) ;
scope . rows = this . split ( years , columns ) ;
scope . columns = columns ;
} ;
this . compare = function ( date1 , date2 ) {
return date1 . getFullYear ( ) - date2 . getFullYear ( ) ;
} ;
this . handleKeyDown = function ( key , evt ) {
var date = this . activeDate . getFullYear ( ) ;
if ( key === 'left' ) {
date = date - 1 ;
} else if ( key === 'up' ) {
date = date - columns ;
} else if ( key === 'right' ) {
date = date + 1 ;
} else if ( key === 'down' ) {
date = date + columns ;
} else if ( key === 'pageup' || key === 'pagedown' ) {
date += ( key === 'pageup' ? - 1 : 1 ) * range ;
} else if ( key === 'home' ) {
date = getStartingYear ( this . activeDate . getFullYear ( ) ) ;
} else if ( key === 'end' ) {
date = getStartingYear ( this . activeDate . getFullYear ( ) ) + range - 1 ;
}
this . activeDate . setFullYear ( date ) ;
} ;
} ] )
. directive ( 'uibDatepicker' , function ( ) {
return {
templateUrl : function ( element , attrs ) {
return attrs . templateUrl || 'uib/template/datepicker/datepicker.html' ;
} ,
scope : {
datepickerOptions : '=?'
} ,
require : [ 'uibDatepicker' , '^ngModel' ] ,
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
2016-05-16 13:33:49 +02:00
controller : 'UibDatepickerController' ,
controllerAs : 'datepicker' ,
link : function ( scope , element , attrs , ctrls ) {
var datepickerCtrl = ctrls [ 0 ] , ngModelCtrl = ctrls [ 1 ] ;
datepickerCtrl . init ( ngModelCtrl ) ;
}
} ;
} )
. directive ( 'uibDaypicker' , function ( ) {
return {
templateUrl : function ( element , attrs ) {
return attrs . templateUrl || 'uib/template/datepicker/day.html' ;
} ,
require : [ '^uibDatepicker' , 'uibDaypicker' ] ,
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
2016-05-16 13:33:49 +02:00
controller : 'UibDaypickerController' ,
link : function ( scope , element , attrs , ctrls ) {
var datepickerCtrl = ctrls [ 0 ] ,
daypickerCtrl = ctrls [ 1 ] ;
daypickerCtrl . init ( datepickerCtrl ) ;
}
} ;
} )
. directive ( 'uibMonthpicker' , function ( ) {
return {
templateUrl : function ( element , attrs ) {
return attrs . templateUrl || 'uib/template/datepicker/month.html' ;
} ,
require : [ '^uibDatepicker' , 'uibMonthpicker' ] ,
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
2016-05-16 13:33:49 +02:00
controller : 'UibMonthpickerController' ,
link : function ( scope , element , attrs , ctrls ) {
var datepickerCtrl = ctrls [ 0 ] ,
monthpickerCtrl = ctrls [ 1 ] ;
monthpickerCtrl . init ( datepickerCtrl ) ;
}
} ;
2014-08-10 11:37:05 +02:00
} )
2016-05-16 13:33:49 +02:00
. directive ( 'uibYearpicker' , function ( ) {
return {
templateUrl : function ( element , attrs ) {
return attrs . templateUrl || 'uib/template/datepicker/year.html' ;
} ,
require : [ '^uibDatepicker' , 'uibYearpicker' ] ,
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
2016-05-16 13:33:49 +02:00
controller : 'UibYearpickerController' ,
link : function ( scope , element , attrs , ctrls ) {
var ctrl = ctrls [ 0 ] ;
angular . extend ( ctrl , ctrls [ 1 ] ) ;
ctrl . yearpickerInit ( ) ;
ctrl . refreshView ( ) ;
}
} ;
} ) ;
angular . module ( 'ui.bootstrap.position' , [ ] )
/ * *
* A set of utility methods for working with the DOM .
* It is meant to be used where we need to absolute - position elements in
* relation to another element ( this is the case for tooltips , popovers ,
* typeahead suggestions etc . ) .
* /
. factory ( '$uibPosition' , [ '$document' , '$window' , function ( $document , $window ) {
/ * *
* Used by scrollbarWidth ( ) function to cache scrollbar ' s width .
* Do not access this variable directly , use scrollbarWidth ( ) instead .
* /
var SCROLLBAR _WIDTH ;
/ * *
* scrollbar on body and html element in IE and Edge overlay
* content and should be considered 0 width .
* /
var BODY _SCROLLBAR _WIDTH ;
var OVERFLOW _REGEX = {
normal : /(auto|scroll)/ ,
hidden : /(auto|scroll|hidden)/
} ;
var PLACEMENT _REGEX = {
auto : /\s?auto?\s?/i ,
primary : /^(top|bottom|left|right)$/ ,
secondary : /^(top|bottom|left|right|center)$/ ,
vertical : /^(top|bottom)$/
} ;
var BODY _REGEX = /(HTML|BODY)/ ;
return {
/ * *
* Provides a raw DOM element from a jQuery / jQLite element .
*
* @ param { element } elem - The element to convert .
*
* @ returns { element } A HTML element .
* /
getRawNode : function ( elem ) {
return elem . nodeName ? elem : elem [ 0 ] || elem ;
} ,
/ * *
* Provides a parsed number for a style property . Strips
* units and casts invalid numbers to 0.
*
* @ param { string } value - The style value to parse .
*
* @ returns { number } A valid number .
* /
parseStyle : function ( value ) {
value = parseFloat ( value ) ;
return isFinite ( value ) ? value : 0 ;
} ,
/ * *
* Provides the closest positioned ancestor .
*
* @ param { element } element - The element to get the offest parent for .
*
* @ returns { element } The closest positioned ancestor .
* /
offsetParent : function ( elem ) {
elem = this . getRawNode ( elem ) ;
var offsetParent = elem . offsetParent || $document [ 0 ] . documentElement ;
function isStaticPositioned ( el ) {
return ( $window . getComputedStyle ( el ) . position || 'static' ) === 'static' ;
}
while ( offsetParent && offsetParent !== $document [ 0 ] . documentElement && isStaticPositioned ( offsetParent ) ) {
offsetParent = offsetParent . offsetParent ;
}
return offsetParent || $document [ 0 ] . documentElement ;
} ,
/ * *
* Provides the scrollbar width , concept from TWBS measureScrollbar ( )
* function in https : //github.com/twbs/bootstrap/blob/master/js/modal.js
* In IE and Edge , scollbar on body and html element overlay and should
* return a width of 0.
*
* @ returns { number } The width of the browser scollbar .
* /
scrollbarWidth : function ( isBody ) {
if ( isBody ) {
if ( angular . isUndefined ( BODY _SCROLLBAR _WIDTH ) ) {
var bodyElem = $document . find ( 'body' ) ;
bodyElem . addClass ( 'uib-position-body-scrollbar-measure' ) ;
BODY _SCROLLBAR _WIDTH = $window . innerWidth - bodyElem [ 0 ] . clientWidth ;
BODY _SCROLLBAR _WIDTH = isFinite ( BODY _SCROLLBAR _WIDTH ) ? BODY _SCROLLBAR _WIDTH : 0 ;
bodyElem . removeClass ( 'uib-position-body-scrollbar-measure' ) ;
}
return BODY _SCROLLBAR _WIDTH ;
}
if ( angular . isUndefined ( SCROLLBAR _WIDTH ) ) {
var scrollElem = angular . element ( '<div class="uib-position-scrollbar-measure"></div>' ) ;
$document . find ( 'body' ) . append ( scrollElem ) ;
SCROLLBAR _WIDTH = scrollElem [ 0 ] . offsetWidth - scrollElem [ 0 ] . clientWidth ;
SCROLLBAR _WIDTH = isFinite ( SCROLLBAR _WIDTH ) ? SCROLLBAR _WIDTH : 0 ;
scrollElem . remove ( ) ;
}
return SCROLLBAR _WIDTH ;
} ,
/ * *
* Provides the padding required on an element to replace the scrollbar .
*
* @ returns { object } An object with the following properties :
* < ul >
* < li > * * scrollbarWidth * * : the width of the scrollbar < / l i >
* < li > * * widthOverflow * * : whether the the width is overflowing < / l i >
* < li > * * right * * : the amount of right padding on the element needed to replace the scrollbar < / l i >
* < li > * * rightOriginal * * : the amount of right padding currently on the element < / l i >
* < li > * * heightOverflow * * : whether the the height is overflowing < / l i >
* < li > * * bottom * * : the amount of bottom padding on the element needed to replace the scrollbar < / l i >
* < li > * * bottomOriginal * * : the amount of bottom padding currently on the element < / l i >
* < / u l >
* /
scrollbarPadding : function ( elem ) {
elem = this . getRawNode ( elem ) ;
var elemStyle = $window . getComputedStyle ( elem ) ;
var paddingRight = this . parseStyle ( elemStyle . paddingRight ) ;
var paddingBottom = this . parseStyle ( elemStyle . paddingBottom ) ;
var scrollParent = this . scrollParent ( elem , false , true ) ;
2019-03-29 22:00:08 +01:00
var scrollbarWidth = this . scrollbarWidth ( BODY _REGEX . test ( scrollParent . tagName ) ) ;
2016-05-16 13:33:49 +02:00
return {
scrollbarWidth : scrollbarWidth ,
widthOverflow : scrollParent . scrollWidth > scrollParent . clientWidth ,
right : paddingRight + scrollbarWidth ,
originalRight : paddingRight ,
heightOverflow : scrollParent . scrollHeight > scrollParent . clientHeight ,
bottom : paddingBottom + scrollbarWidth ,
originalBottom : paddingBottom
} ;
} ,
/ * *
* Checks to see if the element is scrollable .
*
* @ param { element } elem - The element to check .
* @ param { boolean = } [ includeHidden = false ] - Should scroll style of 'hidden' be considered ,
* default is false .
*
* @ returns { boolean } Whether the element is scrollable .
* /
isScrollable : function ( elem , includeHidden ) {
elem = this . getRawNode ( elem ) ;
var overflowRegex = includeHidden ? OVERFLOW _REGEX . hidden : OVERFLOW _REGEX . normal ;
var elemStyle = $window . getComputedStyle ( elem ) ;
return overflowRegex . test ( elemStyle . overflow + elemStyle . overflowY + elemStyle . overflowX ) ;
} ,
/ * *
* Provides the closest scrollable ancestor .
* A port of the jQuery UI scrollParent method :
* https : //github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js
*
* @ param { element } elem - The element to find the scroll parent of .
* @ param { boolean = } [ includeHidden = false ] - Should scroll style of 'hidden' be considered ,
* default is false .
* @ param { boolean = } [ includeSelf = false ] - Should the element being passed be
* included in the scrollable llokup .
*
* @ returns { element } A HTML element .
* /
scrollParent : function ( elem , includeHidden , includeSelf ) {
elem = this . getRawNode ( elem ) ;
var overflowRegex = includeHidden ? OVERFLOW _REGEX . hidden : OVERFLOW _REGEX . normal ;
var documentEl = $document [ 0 ] . documentElement ;
var elemStyle = $window . getComputedStyle ( elem ) ;
if ( includeSelf && overflowRegex . test ( elemStyle . overflow + elemStyle . overflowY + elemStyle . overflowX ) ) {
return elem ;
}
var excludeStatic = elemStyle . position === 'absolute' ;
var scrollParent = elem . parentElement || documentEl ;
if ( scrollParent === documentEl || elemStyle . position === 'fixed' ) {
return documentEl ;
}
while ( scrollParent . parentElement && scrollParent !== documentEl ) {
var spStyle = $window . getComputedStyle ( scrollParent ) ;
if ( excludeStatic && spStyle . position !== 'static' ) {
excludeStatic = false ;
}
if ( ! excludeStatic && overflowRegex . test ( spStyle . overflow + spStyle . overflowY + spStyle . overflowX ) ) {
break ;
}
scrollParent = scrollParent . parentElement ;
}
return scrollParent ;
} ,
/ * *
* Provides read - only equivalent of jQuery ' s position function :
* http : //api.jquery.com/position/ - distance to closest positioned
* ancestor . Does not account for margins by default like jQuery position .
*
* @ param { element } elem - The element to caclulate the position on .
* @ param { boolean = } [ includeMargins = false ] - Should margins be accounted
* for , default is false .
*
* @ returns { object } An object with the following properties :
* < ul >
* < li > * * width * * : the width of the element < / l i >
* < li > * * height * * : the height of the element < / l i >
* < li > * * top * * : distance to top edge of offset parent < / l i >
* < li > * * left * * : distance to left edge of offset parent < / l i >
* < / u l >
* /
position : function ( elem , includeMagins ) {
elem = this . getRawNode ( elem ) ;
var elemOffset = this . offset ( elem ) ;
if ( includeMagins ) {
var elemStyle = $window . getComputedStyle ( elem ) ;
elemOffset . top -= this . parseStyle ( elemStyle . marginTop ) ;
elemOffset . left -= this . parseStyle ( elemStyle . marginLeft ) ;
}
var parent = this . offsetParent ( elem ) ;
var parentOffset = { top : 0 , left : 0 } ;
if ( parent !== $document [ 0 ] . documentElement ) {
parentOffset = this . offset ( parent ) ;
parentOffset . top += parent . clientTop - parent . scrollTop ;
parentOffset . left += parent . clientLeft - parent . scrollLeft ;
}
return {
width : Math . round ( angular . isNumber ( elemOffset . width ) ? elemOffset . width : elem . offsetWidth ) ,
height : Math . round ( angular . isNumber ( elemOffset . height ) ? elemOffset . height : elem . offsetHeight ) ,
top : Math . round ( elemOffset . top - parentOffset . top ) ,
left : Math . round ( elemOffset . left - parentOffset . left )
} ;
} ,
/ * *
* Provides read - only equivalent of jQuery ' s offset function :
* http : //api.jquery.com/offset/ - distance to viewport. Does
* not account for borders , margins , or padding on the body
* element .
*
* @ param { element } elem - The element to calculate the offset on .
*
* @ returns { object } An object with the following properties :
* < ul >
* < li > * * width * * : the width of the element < / l i >
* < li > * * height * * : the height of the element < / l i >
* < li > * * top * * : distance to top edge of viewport < / l i >
* < li > * * right * * : distance to bottom edge of viewport < / l i >
* < / u l >
* /
offset : function ( elem ) {
elem = this . getRawNode ( elem ) ;
var elemBCR = elem . getBoundingClientRect ( ) ;
return {
width : Math . round ( angular . isNumber ( elemBCR . width ) ? elemBCR . width : elem . offsetWidth ) ,
height : Math . round ( angular . isNumber ( elemBCR . height ) ? elemBCR . height : elem . offsetHeight ) ,
top : Math . round ( elemBCR . top + ( $window . pageYOffset || $document [ 0 ] . documentElement . scrollTop ) ) ,
left : Math . round ( elemBCR . left + ( $window . pageXOffset || $document [ 0 ] . documentElement . scrollLeft ) )
} ;
} ,
/ * *
* Provides offset distance to the closest scrollable ancestor
* or viewport . Accounts for border and scrollbar width .
*
* Right and bottom dimensions represent the distance to the
* respective edge of the viewport element . If the element
* edge extends beyond the viewport , a negative value will be
* reported .
*
* @ param { element } elem - The element to get the viewport offset for .
* @ param { boolean = } [ useDocument = false ] - Should the viewport be the document element instead
* of the first scrollable element , default is false .
* @ param { boolean = } [ includePadding = true ] - Should the padding on the offset parent element
* be accounted for , default is true .
*
* @ returns { object } An object with the following properties :
* < ul >
* < li > * * top * * : distance to the top content edge of viewport element < / l i >
* < li > * * bottom * * : distance to the bottom content edge of viewport element < / l i >
* < li > * * left * * : distance to the left content edge of viewport element < / l i >
* < li > * * right * * : distance to the right content edge of viewport element < / l i >
* < / u l >
* /
viewportOffset : function ( elem , useDocument , includePadding ) {
elem = this . getRawNode ( elem ) ;
includePadding = includePadding !== false ? true : false ;
var elemBCR = elem . getBoundingClientRect ( ) ;
var offsetBCR = { top : 0 , left : 0 , bottom : 0 , right : 0 } ;
var offsetParent = useDocument ? $document [ 0 ] . documentElement : this . scrollParent ( elem ) ;
var offsetParentBCR = offsetParent . getBoundingClientRect ( ) ;
offsetBCR . top = offsetParentBCR . top + offsetParent . clientTop ;
offsetBCR . left = offsetParentBCR . left + offsetParent . clientLeft ;
if ( offsetParent === $document [ 0 ] . documentElement ) {
offsetBCR . top += $window . pageYOffset ;
offsetBCR . left += $window . pageXOffset ;
}
offsetBCR . bottom = offsetBCR . top + offsetParent . clientHeight ;
offsetBCR . right = offsetBCR . left + offsetParent . clientWidth ;
if ( includePadding ) {
var offsetParentStyle = $window . getComputedStyle ( offsetParent ) ;
offsetBCR . top += this . parseStyle ( offsetParentStyle . paddingTop ) ;
offsetBCR . bottom -= this . parseStyle ( offsetParentStyle . paddingBottom ) ;
offsetBCR . left += this . parseStyle ( offsetParentStyle . paddingLeft ) ;
offsetBCR . right -= this . parseStyle ( offsetParentStyle . paddingRight ) ;
}
return {
top : Math . round ( elemBCR . top - offsetBCR . top ) ,
bottom : Math . round ( offsetBCR . bottom - elemBCR . bottom ) ,
left : Math . round ( elemBCR . left - offsetBCR . left ) ,
right : Math . round ( offsetBCR . right - elemBCR . right )
} ;
} ,
/ * *
* Provides an array of placement values parsed from a placement string .
* Along with the 'auto' indicator , supported placement strings are :
* < ul >
* < li > top : element on top , horizontally centered on host element . < / l i >
* < li > top - left : element on top , left edge aligned with host element left edge . < / l i >
* < li > top - right : element on top , lerightft edge aligned with host element right edge . < / l i >
* < li > bottom : element on bottom , horizontally centered on host element . < / l i >
* < li > bottom - left : element on bottom , left edge aligned with host element left edge . < / l i >
* < li > bottom - right : element on bottom , right edge aligned with host element right edge . < / l i >
* < li > left : element on left , vertically centered on host element . < / l i >
* < li > left - top : element on left , top edge aligned with host element top edge . < / l i >
* < li > left - bottom : element on left , bottom edge aligned with host element bottom edge . < / l i >
* < li > right : element on right , vertically centered on host element . < / l i >
* < li > right - top : element on right , top edge aligned with host element top edge . < / l i >
* < li > right - bottom : element on right , bottom edge aligned with host element bottom edge . < / l i >
* < / u l >
* A placement string with an 'auto' indicator is expected to be
* space separated from the placement , i . e : 'auto bottom-left' If
* the primary and secondary placement values do not match ' top ,
* bottom , left , right ' then ' top ' will be the primary placement and
* 'center' will be the secondary placement . If 'auto' is passed , true
* will be returned as the 3 rd value of the array .
*
* @ param { string } placement - The placement string to parse .
*
* @ returns { array } An array with the following values
* < ul >
* < li > * * [ 0 ] * * : The primary placement . < / l i >
* < li > * * [ 1 ] * * : The secondary placement . < / l i >
* < li > * * [ 2 ] * * : If auto is passed : true , else undefined . < / l i >
* < / u l >
* /
parsePlacement : function ( placement ) {
var autoPlace = PLACEMENT _REGEX . auto . test ( placement ) ;
if ( autoPlace ) {
placement = placement . replace ( PLACEMENT _REGEX . auto , '' ) ;
}
placement = placement . split ( '-' ) ;
placement [ 0 ] = placement [ 0 ] || 'top' ;
if ( ! PLACEMENT _REGEX . primary . test ( placement [ 0 ] ) ) {
placement [ 0 ] = 'top' ;
}
placement [ 1 ] = placement [ 1 ] || 'center' ;
if ( ! PLACEMENT _REGEX . secondary . test ( placement [ 1 ] ) ) {
placement [ 1 ] = 'center' ;
}
if ( autoPlace ) {
placement [ 2 ] = true ;
} else {
placement [ 2 ] = false ;
}
return placement ;
} ,
/ * *
* Provides coordinates for an element to be positioned relative to
* another element . Passing 'auto' as part of the placement parameter
* will enable smart placement - where the element fits . i . e :
* 'auto left-top' will check to see if there is enough space to the left
* of the hostElem to fit the targetElem , if not place right ( same for secondary
* top placement ) . Available space is calculated using the viewportOffset
* function .
*
* @ param { element } hostElem - The element to position against .
* @ param { element } targetElem - The element to position .
* @ param { string = } [ placement = top ] - The placement for the targetElem ,
* default is 'top' . 'center' is assumed as secondary placement for
* 'top' , 'left' , 'right' , and 'bottom' placements . Available placements are :
* < ul >
* < li > top < / l i >
* < li > top - right < / l i >
* < li > top - left < / l i >
* < li > bottom < / l i >
* < li > bottom - left < / l i >
* < li > bottom - right < / l i >
* < li > left < / l i >
* < li > left - top < / l i >
* < li > left - bottom < / l i >
* < li > right < / l i >
* < li > right - top < / l i >
* < li > right - bottom < / l i >
* < / u l >
* @ param { boolean = } [ appendToBody = false ] - Should the top and left values returned
* be calculated from the body element , default is false .
*
* @ returns { object } An object with the following properties :
* < ul >
* < li > * * top * * : Value for targetElem top . < / l i >
* < li > * * left * * : Value for targetElem left . < / l i >
* < li > * * placement * * : The resolved placement . < / l i >
* < / u l >
* /
positionElements : function ( hostElem , targetElem , placement , appendToBody ) {
hostElem = this . getRawNode ( hostElem ) ;
targetElem = this . getRawNode ( targetElem ) ;
// need to read from prop to support tests.
var targetWidth = angular . isDefined ( targetElem . offsetWidth ) ? targetElem . offsetWidth : targetElem . prop ( 'offsetWidth' ) ;
var targetHeight = angular . isDefined ( targetElem . offsetHeight ) ? targetElem . offsetHeight : targetElem . prop ( 'offsetHeight' ) ;
placement = this . parsePlacement ( placement ) ;
var hostElemPos = appendToBody ? this . offset ( hostElem ) : this . position ( hostElem ) ;
var targetElemPos = { top : 0 , left : 0 , placement : '' } ;
if ( placement [ 2 ] ) {
var viewportOffset = this . viewportOffset ( hostElem , appendToBody ) ;
var targetElemStyle = $window . getComputedStyle ( targetElem ) ;
var adjustedSize = {
width : targetWidth + Math . round ( Math . abs ( this . parseStyle ( targetElemStyle . marginLeft ) + this . parseStyle ( targetElemStyle . marginRight ) ) ) ,
height : targetHeight + Math . round ( Math . abs ( this . parseStyle ( targetElemStyle . marginTop ) + this . parseStyle ( targetElemStyle . marginBottom ) ) )
} ;
placement [ 0 ] = placement [ 0 ] === 'top' && adjustedSize . height > viewportOffset . top && adjustedSize . height <= viewportOffset . bottom ? 'bottom' :
placement [ 0 ] === 'bottom' && adjustedSize . height > viewportOffset . bottom && adjustedSize . height <= viewportOffset . top ? 'top' :
placement [ 0 ] === 'left' && adjustedSize . width > viewportOffset . left && adjustedSize . width <= viewportOffset . right ? 'right' :
placement [ 0 ] === 'right' && adjustedSize . width > viewportOffset . right && adjustedSize . width <= viewportOffset . left ? 'left' :
placement [ 0 ] ;
placement [ 1 ] = placement [ 1 ] === 'top' && adjustedSize . height - hostElemPos . height > viewportOffset . bottom && adjustedSize . height - hostElemPos . height <= viewportOffset . top ? 'bottom' :
placement [ 1 ] === 'bottom' && adjustedSize . height - hostElemPos . height > viewportOffset . top && adjustedSize . height - hostElemPos . height <= viewportOffset . bottom ? 'top' :
placement [ 1 ] === 'left' && adjustedSize . width - hostElemPos . width > viewportOffset . right && adjustedSize . width - hostElemPos . width <= viewportOffset . left ? 'right' :
placement [ 1 ] === 'right' && adjustedSize . width - hostElemPos . width > viewportOffset . left && adjustedSize . width - hostElemPos . width <= viewportOffset . right ? 'left' :
placement [ 1 ] ;
if ( placement [ 1 ] === 'center' ) {
if ( PLACEMENT _REGEX . vertical . test ( placement [ 0 ] ) ) {
var xOverflow = hostElemPos . width / 2 - targetWidth / 2 ;
if ( viewportOffset . left + xOverflow < 0 && adjustedSize . width - hostElemPos . width <= viewportOffset . right ) {
placement [ 1 ] = 'left' ;
} else if ( viewportOffset . right + xOverflow < 0 && adjustedSize . width - hostElemPos . width <= viewportOffset . left ) {
placement [ 1 ] = 'right' ;
}
} else {
var yOverflow = hostElemPos . height / 2 - adjustedSize . height / 2 ;
if ( viewportOffset . top + yOverflow < 0 && adjustedSize . height - hostElemPos . height <= viewportOffset . bottom ) {
placement [ 1 ] = 'top' ;
} else if ( viewportOffset . bottom + yOverflow < 0 && adjustedSize . height - hostElemPos . height <= viewportOffset . top ) {
placement [ 1 ] = 'bottom' ;
}
}
}
}
switch ( placement [ 0 ] ) {
case 'top' :
targetElemPos . top = hostElemPos . top - targetHeight ;
break ;
case 'bottom' :
targetElemPos . top = hostElemPos . top + hostElemPos . height ;
break ;
case 'left' :
targetElemPos . left = hostElemPos . left - targetWidth ;
break ;
case 'right' :
targetElemPos . left = hostElemPos . left + hostElemPos . width ;
break ;
}
switch ( placement [ 1 ] ) {
case 'top' :
targetElemPos . top = hostElemPos . top ;
break ;
case 'bottom' :
targetElemPos . top = hostElemPos . top + hostElemPos . height - targetHeight ;
break ;
case 'left' :
targetElemPos . left = hostElemPos . left ;
break ;
case 'right' :
targetElemPos . left = hostElemPos . left + hostElemPos . width - targetWidth ;
break ;
case 'center' :
if ( PLACEMENT _REGEX . vertical . test ( placement [ 0 ] ) ) {
targetElemPos . left = hostElemPos . left + hostElemPos . width / 2 - targetWidth / 2 ;
} else {
targetElemPos . top = hostElemPos . top + hostElemPos . height / 2 - targetHeight / 2 ;
}
break ;
}
targetElemPos . top = Math . round ( targetElemPos . top ) ;
targetElemPos . left = Math . round ( targetElemPos . left ) ;
targetElemPos . placement = placement [ 1 ] === 'center' ? placement [ 0 ] : placement [ 0 ] + '-' + placement [ 1 ] ;
return targetElemPos ;
} ,
/ * *
2019-03-29 22:00:08 +01:00
* Provides a way to adjust the top positioning after first
* render to correctly align element to top after content
* rendering causes resized element height
*
* @ param { array } placementClasses - The array of strings of classes
* element should have .
* @ param { object } containerPosition - The object with container
* position information
* @ param { number } initialHeight - The initial height for the elem .
* @ param { number } currentHeight - The current height for the elem .
* /
adjustTop : function ( placementClasses , containerPosition , initialHeight , currentHeight ) {
if ( placementClasses . indexOf ( 'top' ) !== - 1 && initialHeight !== currentHeight ) {
return {
top : containerPosition . top - currentHeight + 'px'
} ;
}
} ,
/ * *
* Provides a way for positioning tooltip & dropdown
* arrows when using placement options beyond the standard
* left , right , top , or bottom .
*
* @ param { element } elem - The tooltip / dropdown element .
* @ param { string } placement - The placement for the elem .
* /
2016-05-16 13:33:49 +02:00
positionArrow : function ( elem , placement ) {
elem = this . getRawNode ( elem ) ;
var innerElem = elem . querySelector ( '.tooltip-inner, .popover-inner' ) ;
if ( ! innerElem ) {
return ;
}
var isTooltip = angular . element ( innerElem ) . hasClass ( 'tooltip-inner' ) ;
var arrowElem = isTooltip ? elem . querySelector ( '.tooltip-arrow' ) : elem . querySelector ( '.arrow' ) ;
if ( ! arrowElem ) {
return ;
}
var arrowCss = {
top : '' ,
bottom : '' ,
left : '' ,
right : ''
} ;
placement = this . parsePlacement ( placement ) ;
if ( placement [ 1 ] === 'center' ) {
// no adjustment necessary - just reset styles
angular . element ( arrowElem ) . css ( arrowCss ) ;
return ;
}
var borderProp = 'border-' + placement [ 0 ] + '-width' ;
var borderWidth = $window . getComputedStyle ( arrowElem ) [ borderProp ] ;
var borderRadiusProp = 'border-' ;
if ( PLACEMENT _REGEX . vertical . test ( placement [ 0 ] ) ) {
borderRadiusProp += placement [ 0 ] + '-' + placement [ 1 ] ;
} else {
borderRadiusProp += placement [ 1 ] + '-' + placement [ 0 ] ;
}
borderRadiusProp += '-radius' ;
var borderRadius = $window . getComputedStyle ( isTooltip ? innerElem : elem ) [ borderRadiusProp ] ;
switch ( placement [ 0 ] ) {
case 'top' :
arrowCss . bottom = isTooltip ? '0' : '-' + borderWidth ;
break ;
case 'bottom' :
arrowCss . top = isTooltip ? '0' : '-' + borderWidth ;
break ;
case 'left' :
arrowCss . right = isTooltip ? '0' : '-' + borderWidth ;
break ;
case 'right' :
arrowCss . left = isTooltip ? '0' : '-' + borderWidth ;
break ;
}
arrowCss [ placement [ 1 ] ] = borderRadius ;
angular . element ( arrowElem ) . css ( arrowCss ) ;
}
} ;
} ] ) ;
angular . module ( 'ui.bootstrap.datepickerPopup' , [ 'ui.bootstrap.datepicker' , 'ui.bootstrap.position' ] )
. value ( '$datepickerPopupLiteralWarning' , true )
. constant ( 'uibDatepickerPopupConfig' , {
altInputFormats : [ ] ,
appendToBody : false ,
clearText : 'Clear' ,
closeOnDateSelection : true ,
closeText : 'Done' ,
currentText : 'Today' ,
datepickerPopup : 'yyyy-MM-dd' ,
datepickerPopupTemplateUrl : 'uib/template/datepickerPopup/popup.html' ,
datepickerTemplateUrl : 'uib/template/datepicker/datepicker.html' ,
html5Types : {
date : 'yyyy-MM-dd' ,
'datetime-local' : 'yyyy-MM-ddTHH:mm:ss.sss' ,
'month' : 'yyyy-MM'
} ,
onOpenFocus : true ,
showButtonBar : true ,
placement : 'auto bottom-left'
} )
. controller ( 'UibDatepickerPopupController' , [ '$scope' , '$element' , '$attrs' , '$compile' , '$log' , '$parse' , '$window' , '$document' , '$rootScope' , '$uibPosition' , 'dateFilter' , 'uibDateParser' , 'uibDatepickerPopupConfig' , '$timeout' , 'uibDatepickerConfig' , '$datepickerPopupLiteralWarning' ,
function ( $scope , $element , $attrs , $compile , $log , $parse , $window , $document , $rootScope , $position , dateFilter , dateParser , datepickerPopupConfig , $timeout , datepickerConfig , $datepickerPopupLiteralWarning ) {
var cache = { } ,
isHtml5DateInput = false ;
var dateFormat , closeOnDateSelection , appendToBody , onOpenFocus ,
datepickerPopupTemplateUrl , datepickerTemplateUrl , popupEl , datepickerEl , scrollParentEl ,
2019-03-29 22:00:08 +01:00
ngModel , ngModelOptions , $popup , altInputFormats , watchListeners = [ ] ;
2016-05-16 13:33:49 +02:00
this . init = function ( _ngModel _ ) {
ngModel = _ngModel _ ;
2019-03-29 22:00:08 +01:00
ngModelOptions = extractOptions ( ngModel ) ;
2016-05-16 13:33:49 +02:00
closeOnDateSelection = angular . isDefined ( $attrs . closeOnDateSelection ) ?
$scope . $parent . $eval ( $attrs . closeOnDateSelection ) :
datepickerPopupConfig . closeOnDateSelection ;
appendToBody = angular . isDefined ( $attrs . datepickerAppendToBody ) ?
$scope . $parent . $eval ( $attrs . datepickerAppendToBody ) :
datepickerPopupConfig . appendToBody ;
onOpenFocus = angular . isDefined ( $attrs . onOpenFocus ) ?
$scope . $parent . $eval ( $attrs . onOpenFocus ) : datepickerPopupConfig . onOpenFocus ;
datepickerPopupTemplateUrl = angular . isDefined ( $attrs . datepickerPopupTemplateUrl ) ?
$attrs . datepickerPopupTemplateUrl :
datepickerPopupConfig . datepickerPopupTemplateUrl ;
datepickerTemplateUrl = angular . isDefined ( $attrs . datepickerTemplateUrl ) ?
$attrs . datepickerTemplateUrl : datepickerPopupConfig . datepickerTemplateUrl ;
altInputFormats = angular . isDefined ( $attrs . altInputFormats ) ?
$scope . $parent . $eval ( $attrs . altInputFormats ) :
datepickerPopupConfig . altInputFormats ;
$scope . showButtonBar = angular . isDefined ( $attrs . showButtonBar ) ?
$scope . $parent . $eval ( $attrs . showButtonBar ) :
datepickerPopupConfig . showButtonBar ;
if ( datepickerPopupConfig . html5Types [ $attrs . type ] ) {
dateFormat = datepickerPopupConfig . html5Types [ $attrs . type ] ;
isHtml5DateInput = true ;
} else {
dateFormat = $attrs . uibDatepickerPopup || datepickerPopupConfig . datepickerPopup ;
$attrs . $observe ( 'uibDatepickerPopup' , function ( value , oldValue ) {
var newDateFormat = value || datepickerPopupConfig . datepickerPopup ;
// Invalidate the $modelValue to ensure that formatters re-run
// FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
if ( newDateFormat !== dateFormat ) {
dateFormat = newDateFormat ;
ngModel . $modelValue = null ;
if ( ! dateFormat ) {
throw new Error ( 'uibDatepickerPopup must have a date format specified.' ) ;
}
}
} ) ;
}
if ( ! dateFormat ) {
throw new Error ( 'uibDatepickerPopup must have a date format specified.' ) ;
}
if ( isHtml5DateInput && $attrs . uibDatepickerPopup ) {
throw new Error ( 'HTML5 date input types do not support custom formats.' ) ;
}
// popup element used to display calendar
popupEl = angular . element ( '<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>' ) ;
popupEl . attr ( {
'ng-model' : 'date' ,
'ng-change' : 'dateSelection(date)' ,
'template-url' : datepickerPopupTemplateUrl
} ) ;
// datepicker element
datepickerEl = angular . element ( popupEl . children ( ) [ 0 ] ) ;
datepickerEl . attr ( 'template-url' , datepickerTemplateUrl ) ;
if ( ! $scope . datepickerOptions ) {
$scope . datepickerOptions = { } ;
}
if ( isHtml5DateInput ) {
if ( $attrs . type === 'month' ) {
$scope . datepickerOptions . datepickerMode = 'month' ;
$scope . datepickerOptions . minMode = 'month' ;
}
}
datepickerEl . attr ( 'datepicker-options' , 'datepickerOptions' ) ;
if ( ! isHtml5DateInput ) {
// Internal API to maintain the correct ng-invalid-[key] class
ngModel . $$parserName = 'date' ;
ngModel . $validators . date = validator ;
ngModel . $parsers . unshift ( parseDate ) ;
ngModel . $formatters . push ( function ( value ) {
if ( ngModel . $isEmpty ( value ) ) {
$scope . date = value ;
return value ;
}
2016-06-11 17:57:30 +02:00
if ( angular . isNumber ( value ) ) {
value = new Date ( value ) ;
2016-05-16 13:33:49 +02:00
}
2019-03-29 22:00:08 +01:00
$scope . date = dateParser . fromTimezone ( value , ngModelOptions . getOption ( 'timezone' ) ) ;
2016-06-11 17:57:30 +02:00
2016-05-16 13:33:49 +02:00
return dateParser . filter ( $scope . date , dateFormat ) ;
} ) ;
} else {
ngModel . $formatters . push ( function ( value ) {
2019-03-29 22:00:08 +01:00
$scope . date = dateParser . fromTimezone ( value , ngModelOptions . getOption ( 'timezone' ) ) ;
2016-05-16 13:33:49 +02:00
return value ;
} ) ;
}
// Detect changes in the view from the text box
ngModel . $viewChangeListeners . push ( function ( ) {
$scope . date = parseDateString ( ngModel . $viewValue ) ;
} ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$element . on ( 'keydown' , inputKeydownBind ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$popup = $compile ( popupEl ) ( $scope ) ;
// Prevent jQuery cache memory leak (template is now redundant after linking)
popupEl . remove ( ) ;
if ( appendToBody ) {
$document . find ( 'body' ) . append ( $popup ) ;
} else {
$element . after ( $popup ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$scope . $on ( '$destroy' , function ( ) {
if ( $scope . isOpen === true ) {
if ( ! $rootScope . $$phase ) {
$scope . $apply ( function ( ) {
$scope . isOpen = false ;
} ) ;
2014-08-10 11:37:05 +02:00
}
}
2016-05-16 13:33:49 +02:00
$popup . remove ( ) ;
$element . off ( 'keydown' , inputKeydownBind ) ;
$document . off ( 'click' , documentClickBind ) ;
if ( scrollParentEl ) {
scrollParentEl . off ( 'scroll' , positionPopup ) ;
}
angular . element ( $window ) . off ( 'resize' , positionPopup ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//Clear all watch listeners on destroy
while ( watchListeners . length ) {
watchListeners . shift ( ) ( ) ;
}
} ) ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$scope . getText = function ( key ) {
return $scope [ key + 'Text' ] || datepickerPopupConfig [ key + 'Text' ] ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$scope . isDisabled = function ( date ) {
if ( date === 'today' ) {
2019-03-29 22:00:08 +01:00
date = dateParser . fromTimezone ( new Date ( ) , ngModelOptions . getOption ( 'timezone' ) ) ;
2016-05-16 13:33:49 +02:00
}
var dates = { } ;
angular . forEach ( [ 'minDate' , 'maxDate' ] , function ( key ) {
if ( ! $scope . datepickerOptions [ key ] ) {
dates [ key ] = null ;
} else if ( angular . isDate ( $scope . datepickerOptions [ key ] ) ) {
2019-03-29 22:00:08 +01:00
dates [ key ] = new Date ( $scope . datepickerOptions [ key ] ) ;
2016-05-16 13:33:49 +02:00
} else {
if ( $datepickerPopupLiteralWarning ) {
$log . warn ( 'Literal date support has been deprecated, please switch to date object usage' ) ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
dates [ key ] = new Date ( dateFilter ( $scope . datepickerOptions [ key ] , 'medium' ) ) ;
}
} ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
return $scope . datepickerOptions &&
dates . minDate && $scope . compare ( date , dates . minDate ) < 0 ||
dates . maxDate && $scope . compare ( date , dates . maxDate ) > 0 ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$scope . compare = function ( date1 , date2 ) {
return new Date ( date1 . getFullYear ( ) , date1 . getMonth ( ) , date1 . getDate ( ) ) - new Date ( date2 . getFullYear ( ) , date2 . getMonth ( ) , date2 . getDate ( ) ) ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// Inner change
$scope . dateSelection = function ( dt ) {
2019-03-29 22:00:08 +01:00
$scope . date = dt ;
2016-05-16 13:33:49 +02:00
var date = $scope . date ? dateParser . filter ( $scope . date , dateFormat ) : null ; // Setting to NULL is necessary for form validators to function
$element . val ( date ) ;
ngModel . $setViewValue ( date ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( closeOnDateSelection ) {
$scope . isOpen = false ;
$element [ 0 ] . focus ( ) ;
2014-08-10 11:37:05 +02:00
}
} ;
2016-05-16 13:33:49 +02:00
$scope . keydown = function ( evt ) {
if ( evt . which === 27 ) {
evt . stopPropagation ( ) ;
$scope . isOpen = false ;
$element [ 0 ] . focus ( ) ;
}
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$scope . select = function ( date , evt ) {
evt . stopPropagation ( ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( date === 'today' ) {
var today = new Date ( ) ;
if ( angular . isDate ( $scope . date ) ) {
date = new Date ( $scope . date ) ;
date . setFullYear ( today . getFullYear ( ) , today . getMonth ( ) , today . getDate ( ) ) ;
} else {
2019-03-29 22:00:08 +01:00
date = dateParser . fromTimezone ( today , ngModelOptions . getOption ( 'timezone' ) ) ;
date . setHours ( 0 , 0 , 0 , 0 ) ;
2016-05-16 13:33:49 +02:00
}
}
$scope . dateSelection ( date ) ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$scope . close = function ( evt ) {
evt . stopPropagation ( ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$scope . isOpen = false ;
$element [ 0 ] . focus ( ) ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
$scope . disabled = angular . isDefined ( $attrs . disabled ) || false ;
if ( $attrs . ngDisabled ) {
watchListeners . push ( $scope . $parent . $watch ( $parse ( $attrs . ngDisabled ) , function ( disabled ) {
$scope . disabled = disabled ;
} ) ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$scope . $watch ( 'isOpen' , function ( value ) {
if ( value ) {
if ( ! $scope . disabled ) {
$timeout ( function ( ) {
positionPopup ( ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( onOpenFocus ) {
$scope . $broadcast ( 'uib:datepicker.focus' ) ;
}
$document . on ( 'click' , documentClickBind ) ;
var placement = $attrs . popupPlacement ? $attrs . popupPlacement : datepickerPopupConfig . placement ;
if ( appendToBody || $position . parsePlacement ( placement ) [ 2 ] ) {
scrollParentEl = scrollParentEl || angular . element ( $position . scrollParent ( $element ) ) ;
if ( scrollParentEl ) {
scrollParentEl . on ( 'scroll' , positionPopup ) ;
}
} else {
scrollParentEl = null ;
}
angular . element ( $window ) . on ( 'resize' , positionPopup ) ;
} , 0 , false ) ;
} else {
$scope . isOpen = false ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
} else {
$document . off ( 'click' , documentClickBind ) ;
if ( scrollParentEl ) {
scrollParentEl . off ( 'scroll' , positionPopup ) ;
}
angular . element ( $window ) . off ( 'resize' , positionPopup ) ;
}
} ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function cameltoDash ( string ) {
return string . replace ( /([A-Z])/g , function ( $1 ) { return '-' + $1 . toLowerCase ( ) ; } ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function parseDateString ( viewValue ) {
var date = dateParser . parse ( viewValue , dateFormat , $scope . date ) ;
if ( isNaN ( date ) ) {
for ( var i = 0 ; i < altInputFormats . length ; i ++ ) {
date = dateParser . parse ( viewValue , altInputFormats [ i ] , $scope . date ) ;
if ( ! isNaN ( date ) ) {
return date ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
}
}
return date ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function parseDate ( viewValue ) {
if ( angular . isNumber ( viewValue ) ) {
// presumably timestamp to date object
viewValue = new Date ( viewValue ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( ! viewValue ) {
return null ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( angular . isDate ( viewValue ) && ! isNaN ( viewValue ) ) {
return viewValue ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( angular . isString ( viewValue ) ) {
var date = parseDateString ( viewValue ) ;
if ( ! isNaN ( date ) ) {
2019-03-29 22:00:08 +01:00
return dateParser . toTimezone ( date , ngModelOptions . getOption ( 'timezone' ) ) ;
2016-05-16 13:33:49 +02:00
}
2014-08-10 11:37:05 +02:00
}
2019-03-29 22:00:08 +01:00
return ngModelOptions . getOption ( 'allowInvalid' ) ? viewValue : undefined ;
2016-05-16 13:33:49 +02:00
}
function validator ( modelValue , viewValue ) {
var value = modelValue || viewValue ;
if ( ! $attrs . ngRequired && ! value ) {
return true ;
}
if ( angular . isNumber ( value ) ) {
value = new Date ( value ) ;
}
if ( ! value ) {
return true ;
}
if ( angular . isDate ( value ) && ! isNaN ( value ) ) {
return true ;
}
if ( angular . isString ( value ) ) {
2019-03-29 22:00:08 +01:00
return ! isNaN ( parseDateString ( value ) ) ;
2016-05-16 13:33:49 +02:00
}
return false ;
}
function documentClickBind ( event ) {
if ( ! $scope . isOpen && $scope . disabled ) {
return ;
}
var popup = $popup [ 0 ] ;
var dpContainsTarget = $element [ 0 ] . contains ( event . target ) ;
// The popup node may not be an element node
// In some browsers (IE) only element nodes have the 'contains' function
var popupContainsTarget = popup . contains !== undefined && popup . contains ( event . target ) ;
if ( $scope . isOpen && ! ( dpContainsTarget || popupContainsTarget ) ) {
$scope . $apply ( function ( ) {
$scope . isOpen = false ;
} ) ;
}
}
function inputKeydownBind ( evt ) {
if ( evt . which === 27 && $scope . isOpen ) {
evt . preventDefault ( ) ;
evt . stopPropagation ( ) ;
$scope . $apply ( function ( ) {
$scope . isOpen = false ;
} ) ;
$element [ 0 ] . focus ( ) ;
} else if ( evt . which === 40 && ! $scope . isOpen ) {
evt . preventDefault ( ) ;
evt . stopPropagation ( ) ;
$scope . $apply ( function ( ) {
$scope . isOpen = true ;
} ) ;
}
}
function positionPopup ( ) {
if ( $scope . isOpen ) {
var dpElement = angular . element ( $popup [ 0 ] . querySelector ( '.uib-datepicker-popup' ) ) ;
var placement = $attrs . popupPlacement ? $attrs . popupPlacement : datepickerPopupConfig . placement ;
var position = $position . positionElements ( $element , dpElement , placement , appendToBody ) ;
dpElement . css ( { top : position . top + 'px' , left : position . left + 'px' } ) ;
if ( dpElement . hasClass ( 'uib-position-measure' ) ) {
dpElement . removeClass ( 'uib-position-measure' ) ;
}
}
}
2014-08-10 11:37:05 +02:00
2019-03-29 22:00:08 +01:00
function extractOptions ( ngModelCtrl ) {
var ngModelOptions ;
if ( angular . version . minor < 6 ) { // in angular < 1.6 $options could be missing
// guarantee a value
ngModelOptions = angular . isObject ( ngModelCtrl . $options ) ?
ngModelCtrl . $options :
{
timezone : null
} ;
// mimic 1.6+ api
ngModelOptions . getOption = function ( key ) {
return ngModelOptions [ key ] ;
} ;
} else { // in angular >=1.6 $options is always present
ngModelOptions = ngModelCtrl . $options ;
}
return ngModelOptions ;
}
2016-05-16 13:33:49 +02:00
$scope . $on ( 'uib:datepicker.mode' , function ( ) {
$timeout ( positionPopup , 0 , false ) ;
} ) ;
} ] )
. directive ( 'uibDatepickerPopup' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
require : [ 'ngModel' , 'uibDatepickerPopup' ] ,
controller : 'UibDatepickerPopupController' ,
2014-08-10 11:37:05 +02:00
scope : {
2016-05-16 13:33:49 +02:00
datepickerOptions : '=?' ,
2014-08-10 11:37:05 +02:00
isOpen : '=?' ,
currentText : '@' ,
clearText : '@' ,
2016-05-16 13:33:49 +02:00
closeText : '@'
2014-08-10 11:37:05 +02:00
} ,
2016-05-16 13:33:49 +02:00
link : function ( scope , element , attrs , ctrls ) {
var ngModel = ctrls [ 0 ] ,
ctrl = ctrls [ 1 ] ;
ctrl . init ( ngModel ) ;
}
} ;
} )
. directive ( 'uibDatepickerPopupWrap' , function ( ) {
return {
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
2016-05-16 13:33:49 +02:00
transclude : true ,
templateUrl : function ( element , attrs ) {
return attrs . templateUrl || 'uib/template/datepickerPopup/popup.html' ;
}
} ;
} ) ;
angular . module ( 'ui.bootstrap.debounce' , [ ] )
/ * *
* A helper , internal service that debounces a function
* /
. factory ( '$$debounce' , [ '$timeout' , function ( $timeout ) {
return function ( callback , debounceTime ) {
var timeoutPromise ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
return function ( ) {
var self = this ;
var args = Array . prototype . slice . call ( arguments ) ;
if ( timeoutPromise ) {
$timeout . cancel ( timeoutPromise ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
timeoutPromise = $timeout ( function ( ) {
callback . apply ( self , args ) ;
} , debounceTime ) ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
} ;
} ] ) ;
2014-08-10 11:37:05 +02:00
2019-03-29 22:00:08 +01:00
angular . module ( 'ui.bootstrap.multiMap' , [ ] )
/ * *
* A helper , internal data structure that stores all references attached to key
* /
. factory ( '$$multiMap' , function ( ) {
return {
createNew : function ( ) {
var map = { } ;
return {
entries : function ( ) {
return Object . keys ( map ) . map ( function ( key ) {
return {
key : key ,
value : map [ key ]
} ;
} ) ;
} ,
get : function ( key ) {
return map [ key ] ;
} ,
hasKey : function ( key ) {
return ! ! map [ key ] ;
} ,
keys : function ( ) {
return Object . keys ( map ) ;
} ,
put : function ( key , value ) {
if ( ! map [ key ] ) {
map [ key ] = [ ] ;
}
map [ key ] . push ( value ) ;
} ,
remove : function ( key , value ) {
var values = map [ key ] ;
if ( ! values ) {
return ;
}
var idx = values . indexOf ( value ) ;
if ( idx !== - 1 ) {
values . splice ( idx , 1 ) ;
}
if ( ! values . length ) {
delete map [ key ] ;
}
}
} ;
}
} ;
} ) ;
angular . module ( 'ui.bootstrap.dropdown' , [ 'ui.bootstrap.multiMap' , 'ui.bootstrap.position' ] )
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
. constant ( 'uibDropdownConfig' , {
appendToOpenClass : 'uib-dropdown-open' ,
openClass : 'open'
} )
2014-08-10 11:37:05 +02:00
2019-03-29 22:00:08 +01:00
. service ( 'uibDropdownService' , [ '$document' , '$rootScope' , '$$multiMap' , function ( $document , $rootScope , $$multiMap ) {
2016-05-16 13:33:49 +02:00
var openScope = null ;
2019-03-29 22:00:08 +01:00
var openedContainers = $$multiMap . createNew ( ) ;
this . isOnlyOpen = function ( dropdownScope , appendTo ) {
var openedDropdowns = openedContainers . get ( appendTo ) ;
if ( openedDropdowns ) {
var openDropdown = openedDropdowns . reduce ( function ( toClose , dropdown ) {
if ( dropdown . scope === dropdownScope ) {
return dropdown ;
}
2014-08-10 11:37:05 +02:00
2019-03-29 22:00:08 +01:00
return toClose ;
} , { } ) ;
if ( openDropdown ) {
return openedDropdowns . length === 1 ;
}
}
return false ;
} ;
this . open = function ( dropdownScope , element , appendTo ) {
2016-05-16 13:33:49 +02:00
if ( ! openScope ) {
$document . on ( 'click' , closeDropdown ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( openScope && openScope !== dropdownScope ) {
openScope . isOpen = false ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
openScope = dropdownScope ;
2019-03-29 22:00:08 +01:00
if ( ! appendTo ) {
return ;
}
var openedDropdowns = openedContainers . get ( appendTo ) ;
if ( openedDropdowns ) {
var openedScopes = openedDropdowns . map ( function ( dropdown ) {
return dropdown . scope ;
} ) ;
if ( openedScopes . indexOf ( dropdownScope ) === - 1 ) {
openedContainers . put ( appendTo , {
scope : dropdownScope
} ) ;
}
} else {
openedContainers . put ( appendTo , {
scope : dropdownScope
} ) ;
}
2016-05-16 13:33:49 +02:00
} ;
2014-08-10 11:37:05 +02:00
2019-03-29 22:00:08 +01:00
this . close = function ( dropdownScope , element , appendTo ) {
2016-05-16 13:33:49 +02:00
if ( openScope === dropdownScope ) {
$document . off ( 'click' , closeDropdown ) ;
2019-03-29 22:00:08 +01:00
$document . off ( 'keydown' , this . keybindFilter ) ;
openScope = null ;
}
if ( ! appendTo ) {
return ;
}
var openedDropdowns = openedContainers . get ( appendTo ) ;
if ( openedDropdowns ) {
var dropdownToClose = openedDropdowns . reduce ( function ( toClose , dropdown ) {
if ( dropdown . scope === dropdownScope ) {
return dropdown ;
}
return toClose ;
} , { } ) ;
if ( dropdownToClose ) {
openedContainers . remove ( appendTo , dropdownToClose ) ;
}
2016-05-16 13:33:49 +02:00
}
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var closeDropdown = function ( evt ) {
// This method may still be called during the same mouse event that
// unbound this event handler. So check openScope before proceeding.
2019-03-29 22:00:08 +01:00
if ( ! openScope || ! openScope . isOpen ) { return ; }
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( evt && openScope . getAutoClose ( ) === 'disabled' ) { return ; }
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( evt && evt . which === 3 ) { return ; }
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var toggleElement = openScope . getToggleElement ( ) ;
if ( evt && toggleElement && toggleElement [ 0 ] . contains ( evt . target ) ) {
return ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var dropdownElement = openScope . getDropdownElement ( ) ;
if ( evt && openScope . getAutoClose ( ) === 'outsideClick' &&
dropdownElement && dropdownElement [ 0 ] . contains ( evt . target ) ) {
return ;
}
2014-08-10 11:37:05 +02:00
2019-03-29 22:00:08 +01:00
openScope . focusToggleElement ( ) ;
2016-05-16 13:33:49 +02:00
openScope . isOpen = false ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( ! $rootScope . $$phase ) {
openScope . $apply ( ) ;
}
} ;
2014-08-10 11:37:05 +02:00
2019-03-29 22:00:08 +01:00
this . keybindFilter = function ( evt ) {
if ( ! openScope ) {
// see this.close as ESC could have been pressed which kills the scope so we can not proceed
return ;
}
var dropdownElement = openScope . getDropdownElement ( ) ;
var toggleElement = openScope . getToggleElement ( ) ;
var dropdownElementTargeted = dropdownElement && dropdownElement [ 0 ] . contains ( evt . target ) ;
var toggleElementTargeted = toggleElement && toggleElement [ 0 ] . contains ( evt . target ) ;
2016-05-16 13:33:49 +02:00
if ( evt . which === 27 ) {
evt . stopPropagation ( ) ;
openScope . focusToggleElement ( ) ;
closeDropdown ( ) ;
2019-03-29 22:00:08 +01:00
} else if ( openScope . isKeynavEnabled ( ) && [ 38 , 40 ] . indexOf ( evt . which ) !== - 1 && openScope . isOpen && ( dropdownElementTargeted || toggleElementTargeted ) ) {
2016-05-16 13:33:49 +02:00
evt . preventDefault ( ) ;
evt . stopPropagation ( ) ;
openScope . focusDropdownEntry ( evt . which ) ;
}
} ;
} ] )
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
. controller ( 'UibDropdownController' , [ '$scope' , '$element' , '$attrs' , '$parse' , 'uibDropdownConfig' , 'uibDropdownService' , '$animate' , '$uibPosition' , '$document' , '$compile' , '$templateRequest' , function ( $scope , $element , $attrs , $parse , dropdownConfig , uibDropdownService , $animate , $position , $document , $compile , $templateRequest ) {
var self = this ,
scope = $scope . $new ( ) , // create a child scope so we are not polluting original one
templateScope ,
appendToOpenClass = dropdownConfig . appendToOpenClass ,
openClass = dropdownConfig . openClass ,
getIsOpen ,
setIsOpen = angular . noop ,
toggleInvoker = $attrs . onToggle ? $parse ( $attrs . onToggle ) : angular . noop ,
keynavEnabled = false ,
selectedOption = null ,
body = $document . find ( 'body' ) ;
$element . addClass ( 'dropdown' ) ;
this . init = function ( ) {
if ( $attrs . isOpen ) {
getIsOpen = $parse ( $attrs . isOpen ) ;
setIsOpen = getIsOpen . assign ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$scope . $watch ( getIsOpen , function ( value ) {
scope . isOpen = ! ! value ;
} ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
keynavEnabled = angular . isDefined ( $attrs . keyboardNav ) ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
this . toggle = function ( open ) {
scope . isOpen = arguments . length ? ! ! open : ! scope . isOpen ;
if ( angular . isFunction ( setIsOpen ) ) {
setIsOpen ( scope , scope . isOpen ) ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
return scope . isOpen ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
// Allow other directives to watch status
this . isOpen = function ( ) {
return scope . isOpen ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
scope . getToggleElement = function ( ) {
return self . toggleElement ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
scope . getAutoClose = function ( ) {
return $attrs . autoClose || 'always' ; //or 'outsideClick' or 'disabled'
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
scope . getElement = function ( ) {
return $element ;
} ;
scope . isKeynavEnabled = function ( ) {
return keynavEnabled ;
} ;
scope . focusDropdownEntry = function ( keyCode ) {
var elems = self . dropdownMenu ? //If append to body is used.
angular . element ( self . dropdownMenu ) . find ( 'a' ) :
$element . find ( 'ul' ) . eq ( 0 ) . find ( 'a' ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
switch ( keyCode ) {
case 40 : {
if ( ! angular . isNumber ( self . selectedOption ) ) {
self . selectedOption = 0 ;
} else {
self . selectedOption = self . selectedOption === elems . length - 1 ?
self . selectedOption :
self . selectedOption + 1 ;
}
break ;
}
case 38 : {
if ( ! angular . isNumber ( self . selectedOption ) ) {
self . selectedOption = elems . length - 1 ;
} else {
self . selectedOption = self . selectedOption === 0 ?
0 : self . selectedOption - 1 ;
}
break ;
}
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
elems [ self . selectedOption ] . focus ( ) ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
scope . getDropdownElement = function ( ) {
return self . dropdownMenu ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
scope . focusToggleElement = function ( ) {
if ( self . toggleElement ) {
self . toggleElement [ 0 ] . focus ( ) ;
2014-08-10 11:37:05 +02:00
}
} ;
2019-03-29 22:00:08 +01:00
function removeDropdownMenu ( ) {
$element . append ( self . dropdownMenu ) ;
}
2016-05-16 13:33:49 +02:00
scope . $watch ( 'isOpen' , function ( isOpen , wasOpen ) {
2019-03-29 22:00:08 +01:00
var appendTo = null ,
appendToBody = false ;
if ( angular . isDefined ( $attrs . dropdownAppendTo ) ) {
var appendToEl = $parse ( $attrs . dropdownAppendTo ) ( scope ) ;
if ( appendToEl ) {
appendTo = angular . element ( appendToEl ) ;
}
}
if ( angular . isDefined ( $attrs . dropdownAppendToBody ) ) {
var appendToBodyValue = $parse ( $attrs . dropdownAppendToBody ) ( scope ) ;
if ( appendToBodyValue !== false ) {
appendToBody = true ;
}
}
if ( appendToBody && ! appendTo ) {
appendTo = body ;
}
if ( appendTo && self . dropdownMenu ) {
if ( isOpen ) {
appendTo . append ( self . dropdownMenu ) ;
$element . on ( '$destroy' , removeDropdownMenu ) ;
} else {
$element . off ( '$destroy' , removeDropdownMenu ) ;
removeDropdownMenu ( ) ;
}
}
2016-05-16 13:33:49 +02:00
if ( appendTo && self . dropdownMenu ) {
var pos = $position . positionElements ( $element , self . dropdownMenu , 'bottom-left' , true ) ,
css ,
2016-06-11 17:57:30 +02:00
rightalign ,
2019-03-29 22:00:08 +01:00
scrollbarPadding ,
scrollbarWidth = 0 ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
css = {
top : pos . top + 'px' ,
display : isOpen ? 'block' : 'none'
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
rightalign = self . dropdownMenu . hasClass ( 'dropdown-menu-right' ) ;
if ( ! rightalign ) {
css . left = pos . left + 'px' ;
css . right = 'auto' ;
} else {
css . left = 'auto' ;
2019-03-29 22:00:08 +01:00
scrollbarPadding = $position . scrollbarPadding ( appendTo ) ;
if ( scrollbarPadding . heightOverflow && scrollbarPadding . scrollbarWidth ) {
scrollbarWidth = scrollbarPadding . scrollbarWidth ;
}
2016-06-11 17:57:30 +02:00
css . right = window . innerWidth - scrollbarWidth -
2016-05-16 13:33:49 +02:00
( pos . left + $element . prop ( 'offsetWidth' ) ) + 'px' ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// Need to adjust our positioning to be relative to the appendTo container
// if it's not the body element
if ( ! appendToBody ) {
var appendOffset = $position . offset ( appendTo ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
css . top = pos . top - appendOffset . top + 'px' ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( ! rightalign ) {
css . left = pos . left - appendOffset . left + 'px' ;
} else {
css . right = window . innerWidth -
( pos . left - appendOffset . left + $element . prop ( 'offsetWidth' ) ) + 'px' ;
}
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
self . dropdownMenu . css ( css ) ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
var openContainer = appendTo ? appendTo : $element ;
2019-03-29 22:00:08 +01:00
var dropdownOpenClass = appendTo ? appendToOpenClass : openClass ;
var hasOpenClass = openContainer . hasClass ( dropdownOpenClass ) ;
var isOnlyOpen = uibDropdownService . isOnlyOpen ( $scope , appendTo ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( hasOpenClass === ! isOpen ) {
2019-03-29 22:00:08 +01:00
var toggleClass ;
if ( appendTo ) {
toggleClass = ! isOnlyOpen ? 'addClass' : 'removeClass' ;
} else {
toggleClass = isOpen ? 'addClass' : 'removeClass' ;
}
$animate [ toggleClass ] ( openContainer , dropdownOpenClass ) . then ( function ( ) {
2016-05-16 13:33:49 +02:00
if ( angular . isDefined ( isOpen ) && isOpen !== wasOpen ) {
toggleInvoker ( $scope , { open : ! ! isOpen } ) ;
}
} ) ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
if ( isOpen ) {
if ( self . dropdownMenuTemplateUrl ) {
$templateRequest ( self . dropdownMenuTemplateUrl ) . then ( function ( tplContent ) {
templateScope = scope . $new ( ) ;
$compile ( tplContent . trim ( ) ) ( templateScope , function ( dropdownElement ) {
var newEl = dropdownElement ;
self . dropdownMenu . replaceWith ( newEl ) ;
self . dropdownMenu = newEl ;
2019-03-29 22:00:08 +01:00
$document . on ( 'keydown' , uibDropdownService . keybindFilter ) ;
2016-05-16 13:33:49 +02:00
} ) ;
} ) ;
2019-03-29 22:00:08 +01:00
} else {
$document . on ( 'keydown' , uibDropdownService . keybindFilter ) ;
2016-05-16 13:33:49 +02:00
}
2014-08-10 11:37:05 +02:00
scope . focusToggleElement ( ) ;
2019-03-29 22:00:08 +01:00
uibDropdownService . open ( scope , $element , appendTo ) ;
2014-08-10 11:37:05 +02:00
} else {
2019-03-29 22:00:08 +01:00
uibDropdownService . close ( scope , $element , appendTo ) ;
2016-05-16 13:33:49 +02:00
if ( self . dropdownMenuTemplateUrl ) {
if ( templateScope ) {
templateScope . $destroy ( ) ;
}
var newEl = angular . element ( '<ul class="dropdown-menu"></ul>' ) ;
self . dropdownMenu . replaceWith ( newEl ) ;
self . dropdownMenu = newEl ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
self . selectedOption = null ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
if ( angular . isFunction ( setIsOpen ) ) {
setIsOpen ( $scope , isOpen ) ;
}
2014-08-10 11:37:05 +02:00
} ) ;
} ] )
2016-05-16 13:33:49 +02:00
. directive ( 'uibDropdown' , function ( ) {
return {
controller : 'UibDropdownController' ,
link : function ( scope , element , attrs , dropdownCtrl ) {
dropdownCtrl . init ( ) ;
}
} ;
} )
. directive ( 'uibDropdownMenu' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
restrict : 'A' ,
require : '?^uibDropdown' ,
2014-08-10 11:37:05 +02:00
link : function ( scope , element , attrs , dropdownCtrl ) {
2016-05-16 13:33:49 +02:00
if ( ! dropdownCtrl || angular . isDefined ( attrs . dropdownNested ) ) {
return ;
}
element . addClass ( 'dropdown-menu' ) ;
var tplUrl = attrs . templateUrl ;
if ( tplUrl ) {
dropdownCtrl . dropdownMenuTemplateUrl = tplUrl ;
}
if ( ! dropdownCtrl . dropdownMenu ) {
dropdownCtrl . dropdownMenu = element ;
}
2014-08-10 11:37:05 +02:00
}
} ;
} )
2016-05-16 13:33:49 +02:00
. directive ( 'uibDropdownToggle' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
require : '?^uibDropdown' ,
2014-08-10 11:37:05 +02:00
link : function ( scope , element , attrs , dropdownCtrl ) {
2016-05-16 13:33:49 +02:00
if ( ! dropdownCtrl ) {
2014-08-10 11:37:05 +02:00
return ;
}
2016-05-16 13:33:49 +02:00
element . addClass ( 'dropdown-toggle' ) ;
2014-08-10 11:37:05 +02:00
dropdownCtrl . toggleElement = element ;
var toggleDropdown = function ( event ) {
event . preventDefault ( ) ;
2016-05-16 13:33:49 +02:00
if ( ! element . hasClass ( 'disabled' ) && ! attrs . disabled ) {
2014-08-10 11:37:05 +02:00
scope . $apply ( function ( ) {
dropdownCtrl . toggle ( ) ;
} ) ;
}
} ;
2019-03-29 22:00:08 +01:00
element . on ( 'click' , toggleDropdown ) ;
2014-08-10 11:37:05 +02:00
// WAI-ARIA
element . attr ( { 'aria-haspopup' : true , 'aria-expanded' : false } ) ;
2016-05-16 13:33:49 +02:00
scope . $watch ( dropdownCtrl . isOpen , function ( isOpen ) {
2014-08-10 11:37:05 +02:00
element . attr ( 'aria-expanded' , ! ! isOpen ) ;
} ) ;
scope . $on ( '$destroy' , function ( ) {
2019-03-29 22:00:08 +01:00
element . off ( 'click' , toggleDropdown ) ;
2014-08-10 11:37:05 +02:00
} ) ;
}
} ;
} ) ;
2016-05-16 13:33:49 +02:00
angular . module ( 'ui.bootstrap.stackedMap' , [ ] )
2014-08-10 11:37:05 +02:00
/ * *
* A helper , internal data structure that acts as a map but also allows getting / removing
* elements in the LIFO order
* /
2016-05-16 13:33:49 +02:00
. factory ( '$$stackedMap' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
createNew : function ( ) {
2014-08-10 11:37:05 +02:00
var stack = [ ] ;
return {
2016-05-16 13:33:49 +02:00
add : function ( key , value ) {
2014-08-10 11:37:05 +02:00
stack . push ( {
key : key ,
value : value
} ) ;
} ,
2016-05-16 13:33:49 +02:00
get : function ( key ) {
2014-08-10 11:37:05 +02:00
for ( var i = 0 ; i < stack . length ; i ++ ) {
2016-05-16 13:33:49 +02:00
if ( key === stack [ i ] . key ) {
2014-08-10 11:37:05 +02:00
return stack [ i ] ;
}
}
} ,
keys : function ( ) {
var keys = [ ] ;
for ( var i = 0 ; i < stack . length ; i ++ ) {
keys . push ( stack [ i ] . key ) ;
}
return keys ;
} ,
2016-05-16 13:33:49 +02:00
top : function ( ) {
2014-08-10 11:37:05 +02:00
return stack [ stack . length - 1 ] ;
} ,
2016-05-16 13:33:49 +02:00
remove : function ( key ) {
2014-08-10 11:37:05 +02:00
var idx = - 1 ;
for ( var i = 0 ; i < stack . length ; i ++ ) {
2016-05-16 13:33:49 +02:00
if ( key === stack [ i ] . key ) {
2014-08-10 11:37:05 +02:00
idx = i ;
break ;
}
}
return stack . splice ( idx , 1 ) [ 0 ] ;
} ,
2016-05-16 13:33:49 +02:00
removeTop : function ( ) {
2019-03-29 22:00:08 +01:00
return stack . pop ( ) ;
2014-08-10 11:37:05 +02:00
} ,
2016-05-16 13:33:49 +02:00
length : function ( ) {
2014-08-10 11:37:05 +02:00
return stack . length ;
}
} ;
}
} ;
2016-05-16 13:33:49 +02:00
} ) ;
2019-03-29 22:00:08 +01:00
angular . module ( 'ui.bootstrap.modal' , [ 'ui.bootstrap.multiMap' , 'ui.bootstrap.stackedMap' , 'ui.bootstrap.position' ] )
2016-05-16 13:33:49 +02:00
/ * *
* Pluggable resolve mechanism for the modal resolve resolution
* Supports UI Router ' s $resolve service
* /
. provider ( '$uibResolve' , function ( ) {
var resolve = this ;
this . resolver = null ;
this . setResolver = function ( resolver ) {
this . resolver = resolver ;
} ;
this . $get = [ '$injector' , '$q' , function ( $injector , $q ) {
var resolver = resolve . resolver ? $injector . get ( resolve . resolver ) : null ;
return {
resolve : function ( invocables , locals , parent , self ) {
if ( resolver ) {
return resolver . resolve ( invocables , locals , parent , self ) ;
}
var promises = [ ] ;
angular . forEach ( invocables , function ( value ) {
if ( angular . isFunction ( value ) || angular . isArray ( value ) ) {
promises . push ( $q . resolve ( $injector . invoke ( value ) ) ) ;
} else if ( angular . isString ( value ) ) {
promises . push ( $q . resolve ( $injector . get ( value ) ) ) ;
} else {
promises . push ( $q . resolve ( value ) ) ;
}
} ) ;
return $q . all ( promises ) . then ( function ( resolves ) {
var resolveObj = { } ;
var resolveIter = 0 ;
angular . forEach ( invocables , function ( value , key ) {
resolveObj [ key ] = resolves [ resolveIter ++ ] ;
} ) ;
return resolveObj ;
} ) ;
}
} ;
} ] ;
2014-08-10 11:37:05 +02:00
} )
/ * *
* A helper directive for the $modal service . It creates a backdrop element .
* /
2016-05-16 13:33:49 +02:00
. directive ( 'uibModalBackdrop' , [ '$animate' , '$injector' , '$uibModalStack' ,
function ( $animate , $injector , $modalStack ) {
2014-08-10 11:37:05 +02:00
return {
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
2016-05-16 13:33:49 +02:00
compile : function ( tElement , tAttrs ) {
tElement . addClass ( tAttrs . backdropClass ) ;
return linkFn ;
}
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function linkFn ( scope , element , attrs ) {
if ( attrs . modalInClass ) {
$animate . addClass ( element , attrs . modalInClass ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
scope . $on ( $modalStack . NOW _CLOSING _EVENT , function ( e , setIsAsync ) {
var done = setIsAsync ( ) ;
if ( scope . modalOptions . animation ) {
$animate . removeClass ( element , attrs . modalInClass ) . then ( done ) ;
} else {
done ( ) ;
}
2014-08-10 11:37:05 +02:00
} ) ;
}
2016-05-16 13:33:49 +02:00
}
2014-08-10 11:37:05 +02:00
} ] )
2016-05-16 13:33:49 +02:00
. directive ( 'uibModalWindow' , [ '$uibModalStack' , '$q' , '$animateCss' , '$document' ,
function ( $modalStack , $q , $animateCss , $document ) {
2014-08-10 11:37:05 +02:00
return {
scope : {
2016-05-16 13:33:49 +02:00
index : '@'
2014-08-10 11:37:05 +02:00
} ,
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
2014-08-10 11:37:05 +02:00
transclude : true ,
templateUrl : function ( tElement , tAttrs ) {
2016-05-16 13:33:49 +02:00
return tAttrs . templateUrl || 'uib/template/modal/window.html' ;
2014-08-10 11:37:05 +02:00
} ,
2016-05-16 13:33:49 +02:00
link : function ( scope , element , attrs ) {
element . addClass ( attrs . windowTopClass || '' ) ;
2014-08-10 11:37:05 +02:00
scope . size = attrs . size ;
2016-05-16 13:33:49 +02:00
scope . close = function ( evt ) {
2014-08-10 11:37:05 +02:00
var modal = $modalStack . getTop ( ) ;
2016-05-16 13:33:49 +02:00
if ( modal && modal . value . backdrop &&
modal . value . backdrop !== 'static' &&
evt . target === evt . currentTarget ) {
2014-08-10 11:37:05 +02:00
evt . preventDefault ( ) ;
evt . stopPropagation ( ) ;
$modalStack . dismiss ( modal . key , 'backdrop click' ) ;
}
} ;
2016-05-16 13:33:49 +02:00
// moved from template to fix issue #2280
element . on ( 'click' , scope . close ) ;
// This property is only added to the scope for the purpose of detecting when this directive is rendered.
// We can detect that by using this property in the template associated with this directive and then use
// {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
scope . $isRendered = true ;
2019-03-29 22:00:08 +01:00
// Deferred object that will be resolved when this modal is rendered.
2016-05-16 13:33:49 +02:00
var modalRenderDeferObj = $q . defer ( ) ;
2019-03-29 22:00:08 +01:00
// Resolve render promise post-digest
scope . $$postDigest ( function ( ) {
modalRenderDeferObj . resolve ( ) ;
2016-05-16 13:33:49 +02:00
} ) ;
modalRenderDeferObj . promise . then ( function ( ) {
var animationPromise = null ;
if ( attrs . modalInClass ) {
animationPromise = $animateCss ( element , {
addClass : attrs . modalInClass
} ) . start ( ) ;
scope . $on ( $modalStack . NOW _CLOSING _EVENT , function ( e , setIsAsync ) {
var done = setIsAsync ( ) ;
$animateCss ( element , {
removeClass : attrs . modalInClass
} ) . start ( ) . then ( done ) ;
} ) ;
}
$q . when ( animationPromise ) . then ( function ( ) {
// Notify {@link $modalStack} that modal is rendered.
var modal = $modalStack . getTop ( ) ;
if ( modal ) {
$modalStack . modalRendered ( modal . key ) ;
}
/ * *
* If something within the freshly - opened modal already has focus ( perhaps via a
2019-03-29 22:00:08 +01:00
* directive that causes focus ) then there ' s no need to try to focus anything .
2016-05-16 13:33:49 +02:00
* /
if ( ! ( $document [ 0 ] . activeElement && element [ 0 ] . contains ( $document [ 0 ] . activeElement ) ) ) {
var inputWithAutofocus = element [ 0 ] . querySelector ( '[autofocus]' ) ;
/ * *
* Auto - focusing of a freshly - opened modal element causes any child elements
* with the autofocus attribute to lose focus . This is an issue on touch
* based devices which will show and then hide the onscreen keyboard .
* Attempts to refocus the autofocus element via JavaScript will not reopen
* the onscreen keyboard . Fixed by updated the focusing logic to only autofocus
* the modal element if the modal does not contain an autofocus element .
* /
if ( inputWithAutofocus ) {
inputWithAutofocus . focus ( ) ;
} else {
element [ 0 ] . focus ( ) ;
}
}
} ) ;
} ) ;
2014-08-10 11:37:05 +02:00
}
} ;
} ] )
2016-05-16 13:33:49 +02:00
. directive ( 'uibModalAnimationClass' , function ( ) {
return {
compile : function ( tElement , tAttrs ) {
if ( tAttrs . modalAnimation ) {
tElement . addClass ( tAttrs . uibModalAnimationClass ) ;
}
}
} ;
} )
2019-03-29 22:00:08 +01:00
. directive ( 'uibModalTransclude' , [ '$animate' , function ( $animate ) {
2016-05-16 13:33:49 +02:00
return {
link : function ( scope , element , attrs , controller , transclude ) {
transclude ( scope . $parent , function ( clone ) {
element . empty ( ) ;
2019-03-29 22:00:08 +01:00
$animate . enter ( clone , element ) ;
2016-05-16 13:33:49 +02:00
} ) ;
}
} ;
2019-03-29 22:00:08 +01:00
} ] )
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
. factory ( '$uibModalStack' , [ '$animate' , '$animateCss' , '$document' ,
'$compile' , '$rootScope' , '$q' , '$$multiMap' , '$$stackedMap' , '$uibPosition' ,
function ( $animate , $animateCss , $document , $compile , $rootScope , $q , $$multiMap , $$stackedMap , $uibPosition ) {
2014-08-10 11:37:05 +02:00
var OPENED _MODAL _CLASS = 'modal-open' ;
var backdropDomEl , backdropScope ;
var openedWindows = $$stackedMap . createNew ( ) ;
2016-05-16 13:33:49 +02:00
var openedClasses = $$multiMap . createNew ( ) ;
var $modalStack = {
NOW _CLOSING _EVENT : 'modal.stack.now-closing'
} ;
var topModalIndex = 0 ;
var previousTopOpenedModal = null ;
2019-03-29 22:00:08 +01:00
var ARIA _HIDDEN _ATTRIBUTE _NAME = 'data-bootstrap-modal-aria-hidden-count' ;
2016-05-16 13:33:49 +02:00
//Modal focus behavior
2019-03-29 22:00:08 +01:00
var tabbableSelector = 'a[href], area[href], input:not([disabled]):not([tabindex=\'-1\']), ' +
'button:not([disabled]):not([tabindex=\'-1\']),select:not([disabled]):not([tabindex=\'-1\']), textarea:not([disabled]):not([tabindex=\'-1\']), ' +
'iframe, object, embed, *[tabindex]:not([tabindex=\'-1\']), *[contenteditable=true]' ;
2016-05-16 13:33:49 +02:00
var scrollbarPadding ;
2019-03-29 22:00:08 +01:00
var SNAKE _CASE _REGEXP = /[A-Z]/g ;
// TODO: extract into common dependency with tooltip
function snake _case ( name ) {
var separator = '-' ;
return name . replace ( SNAKE _CASE _REGEXP , function ( letter , pos ) {
return ( pos ? separator : '' ) + letter . toLowerCase ( ) ;
} ) ;
}
2016-05-16 13:33:49 +02:00
function isVisible ( element ) {
return ! ! ( element . offsetWidth ||
element . offsetHeight ||
element . getClientRects ( ) . length ) ;
}
2014-08-10 11:37:05 +02:00
function backdropIndex ( ) {
var topBackdropIndex = - 1 ;
var opened = openedWindows . keys ( ) ;
for ( var i = 0 ; i < opened . length ; i ++ ) {
if ( openedWindows . get ( opened [ i ] ) . value . backdrop ) {
topBackdropIndex = i ;
}
}
2016-05-16 13:33:49 +02:00
// If any backdrop exist, ensure that it's index is always
// right below the top modal
if ( topBackdropIndex > - 1 && topBackdropIndex < topModalIndex ) {
topBackdropIndex = topModalIndex ;
}
2014-08-10 11:37:05 +02:00
return topBackdropIndex ;
}
2016-05-16 13:33:49 +02:00
$rootScope . $watch ( backdropIndex , function ( newBackdropIndex ) {
2014-08-10 11:37:05 +02:00
if ( backdropScope ) {
backdropScope . index = newBackdropIndex ;
}
} ) ;
2016-05-16 13:33:49 +02:00
function removeModalWindow ( modalInstance , elementToReceiveFocus ) {
2014-08-10 11:37:05 +02:00
var modalWindow = openedWindows . get ( modalInstance ) . value ;
2016-05-16 13:33:49 +02:00
var appendToElement = modalWindow . appendTo ;
2014-08-10 11:37:05 +02:00
//clean up the stack
openedWindows . remove ( modalInstance ) ;
2016-05-16 13:33:49 +02:00
previousTopOpenedModal = openedWindows . top ( ) ;
if ( previousTopOpenedModal ) {
topModalIndex = parseInt ( previousTopOpenedModal . value . modalDomEl . attr ( 'index' ) , 10 ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
removeAfterAnimate ( modalWindow . modalDomEl , modalWindow . modalScope , function ( ) {
var modalBodyClass = modalWindow . openedClass || OPENED _MODAL _CLASS ;
openedClasses . remove ( modalBodyClass , modalInstance ) ;
var areAnyOpen = openedClasses . hasKey ( modalBodyClass ) ;
appendToElement . toggleClass ( modalBodyClass , areAnyOpen ) ;
if ( ! areAnyOpen && scrollbarPadding && scrollbarPadding . heightOverflow && scrollbarPadding . scrollbarWidth ) {
if ( scrollbarPadding . originalRight ) {
appendToElement . css ( { paddingRight : scrollbarPadding . originalRight + 'px' } ) ;
} else {
appendToElement . css ( { paddingRight : '' } ) ;
}
scrollbarPadding = null ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
toggleTopWindowClass ( true ) ;
} , modalWindow . closedDeferred ) ;
checkRemoveBackdrop ( ) ;
//move focus to specified element if available, or else to body
if ( elementToReceiveFocus && elementToReceiveFocus . focus ) {
elementToReceiveFocus . focus ( ) ;
} else if ( appendToElement . focus ) {
appendToElement . focus ( ) ;
}
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
// Add or remove "windowTopClass" from the top window in the stack
function toggleTopWindowClass ( toggleSwitch ) {
var modalWindow ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( openedWindows . length ( ) > 0 ) {
modalWindow = openedWindows . top ( ) . value ;
modalWindow . modalDomEl . toggleClass ( modalWindow . windowTopClass || '' , toggleSwitch ) ;
}
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function checkRemoveBackdrop ( ) {
//remove backdrop if no longer needed
if ( backdropDomEl && backdropIndex ( ) === - 1 ) {
var backdropScopeRef = backdropScope ;
removeAfterAnimate ( backdropDomEl , backdropScope , function ( ) {
backdropScopeRef = null ;
2014-08-10 11:37:05 +02:00
} ) ;
2016-05-16 13:33:49 +02:00
backdropDomEl = undefined ;
backdropScope = undefined ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
}
function removeAfterAnimate ( domEl , scope , done , closedDeferred ) {
var asyncDeferred ;
var asyncPromise = null ;
var setIsAsync = function ( ) {
if ( ! asyncDeferred ) {
asyncDeferred = $q . defer ( ) ;
asyncPromise = asyncDeferred . promise ;
}
return function asyncDone ( ) {
asyncDeferred . resolve ( ) ;
} ;
} ;
scope . $broadcast ( $modalStack . NOW _CLOSING _EVENT , setIsAsync ) ;
// Note that it's intentional that asyncPromise might be null.
// That's when setIsAsync has not been called during the
// NOW_CLOSING_EVENT broadcast.
return $q . when ( asyncPromise ) . then ( afterAnimating ) ;
2014-08-10 11:37:05 +02:00
function afterAnimating ( ) {
if ( afterAnimating . done ) {
return ;
}
afterAnimating . done = true ;
2016-05-16 13:33:49 +02:00
$animate . leave ( domEl ) . then ( function ( ) {
2019-03-29 22:00:08 +01:00
if ( done ) {
done ( ) ;
}
2016-05-16 13:33:49 +02:00
domEl . remove ( ) ;
if ( closedDeferred ) {
closedDeferred . resolve ( ) ;
}
} ) ;
scope . $destroy ( ) ;
2014-08-10 11:37:05 +02:00
}
}
2016-05-16 13:33:49 +02:00
$document . on ( 'keydown' , keydownListener ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$rootScope . $on ( '$destroy' , function ( ) {
$document . off ( 'keydown' , keydownListener ) ;
} ) ;
function keydownListener ( evt ) {
if ( evt . isDefaultPrevented ( ) ) {
return evt ;
}
var modal = openedWindows . top ( ) ;
if ( modal ) {
switch ( evt . which ) {
case 27 : {
if ( modal . value . keyboard ) {
evt . preventDefault ( ) ;
$rootScope . $apply ( function ( ) {
$modalStack . dismiss ( modal . key , 'escape key press' ) ;
} ) ;
}
break ;
}
case 9 : {
var list = $modalStack . loadFocusElementList ( modal ) ;
var focusChanged = false ;
if ( evt . shiftKey ) {
if ( $modalStack . isFocusInFirstItem ( evt , list ) || $modalStack . isModalFocused ( evt , modal ) ) {
focusChanged = $modalStack . focusLastFocusableElement ( list ) ;
}
} else {
if ( $modalStack . isFocusInLastItem ( evt , list ) ) {
focusChanged = $modalStack . focusFirstFocusableElement ( list ) ;
}
}
if ( focusChanged ) {
evt . preventDefault ( ) ;
evt . stopPropagation ( ) ;
}
break ;
}
2014-08-10 11:37:05 +02:00
}
}
2016-05-16 13:33:49 +02:00
}
$modalStack . open = function ( modalInstance , modal ) {
var modalOpener = $document [ 0 ] . activeElement ,
modalBodyClass = modal . openedClass || OPENED _MODAL _CLASS ;
toggleTopWindowClass ( false ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// Store the current top first, to determine what index we ought to use
// for the current top modal
previousTopOpenedModal = openedWindows . top ( ) ;
2014-08-10 11:37:05 +02:00
openedWindows . add ( modalInstance , {
deferred : modal . deferred ,
2016-05-16 13:33:49 +02:00
renderDeferred : modal . renderDeferred ,
closedDeferred : modal . closedDeferred ,
2014-08-10 11:37:05 +02:00
modalScope : modal . scope ,
backdrop : modal . backdrop ,
2016-05-16 13:33:49 +02:00
keyboard : modal . keyboard ,
openedClass : modal . openedClass ,
windowTopClass : modal . windowTopClass ,
animation : modal . animation ,
appendTo : modal . appendTo
2014-08-10 11:37:05 +02:00
} ) ;
2016-05-16 13:33:49 +02:00
openedClasses . put ( modalBodyClass , modalInstance ) ;
var appendToElement = modal . appendTo ,
2014-08-10 11:37:05 +02:00
currBackdropIndex = backdropIndex ( ) ;
if ( currBackdropIndex >= 0 && ! backdropDomEl ) {
backdropScope = $rootScope . $new ( true ) ;
2016-05-16 13:33:49 +02:00
backdropScope . modalOptions = modal ;
2014-08-10 11:37:05 +02:00
backdropScope . index = currBackdropIndex ;
2016-05-16 13:33:49 +02:00
backdropDomEl = angular . element ( '<div uib-modal-backdrop="modal-backdrop"></div>' ) ;
2019-03-29 22:00:08 +01:00
backdropDomEl . attr ( {
'class' : 'modal-backdrop' ,
'ng-style' : '{\'z-index\': 1040 + (index && 1 || 0) + index*10}' ,
'uib-modal-animation-class' : 'fade' ,
'modal-in-class' : 'in'
} ) ;
if ( modal . backdropClass ) {
backdropDomEl . addClass ( modal . backdropClass ) ;
}
2016-05-16 13:33:49 +02:00
if ( modal . animation ) {
backdropDomEl . attr ( 'modal-animation' , 'true' ) ;
}
$compile ( backdropDomEl ) ( backdropScope ) ;
$animate . enter ( backdropDomEl , appendToElement ) ;
2019-03-29 22:00:08 +01:00
if ( $uibPosition . isScrollable ( appendToElement ) ) {
scrollbarPadding = $uibPosition . scrollbarPadding ( appendToElement ) ;
if ( scrollbarPadding . heightOverflow && scrollbarPadding . scrollbarWidth ) {
appendToElement . css ( { paddingRight : scrollbarPadding . right + 'px' } ) ;
}
2016-05-16 13:33:49 +02:00
}
2014-08-10 11:37:05 +02:00
}
2019-03-29 22:00:08 +01:00
var content ;
if ( modal . component ) {
content = document . createElement ( snake _case ( modal . component . name ) ) ;
content = angular . element ( content ) ;
content . attr ( {
resolve : '$resolve' ,
'modal-instance' : '$uibModalInstance' ,
close : '$close($value)' ,
dismiss : '$dismiss($value)'
} ) ;
} else {
content = modal . content ;
}
2016-05-16 13:33:49 +02:00
// Set the top modal index based on the index of the previous top modal
topModalIndex = previousTopOpenedModal ? parseInt ( previousTopOpenedModal . value . modalDomEl . attr ( 'index' ) , 10 ) + 1 : 0 ;
var angularDomEl = angular . element ( '<div uib-modal-window="modal-window"></div>' ) ;
2014-08-10 11:37:05 +02:00
angularDomEl . attr ( {
2019-03-29 22:00:08 +01:00
'class' : 'modal' ,
2014-08-10 11:37:05 +02:00
'template-url' : modal . windowTemplateUrl ,
2016-05-16 13:33:49 +02:00
'window-top-class' : modal . windowTopClass ,
2019-03-29 22:00:08 +01:00
'role' : 'dialog' ,
'aria-labelledby' : modal . ariaLabelledBy ,
'aria-describedby' : modal . ariaDescribedBy ,
2014-08-10 11:37:05 +02:00
'size' : modal . size ,
2016-05-16 13:33:49 +02:00
'index' : topModalIndex ,
2019-03-29 22:00:08 +01:00
'animate' : 'animate' ,
'ng-style' : '{\'z-index\': 1050 + $$topModalIndex*10, display: \'block\'}' ,
'tabindex' : - 1 ,
'uib-modal-animation-class' : 'fade' ,
'modal-in-class' : 'in'
} ) . append ( content ) ;
if ( modal . windowClass ) {
angularDomEl . addClass ( modal . windowClass ) ;
}
2016-05-16 13:33:49 +02:00
if ( modal . animation ) {
angularDomEl . attr ( 'modal-animation' , 'true' ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
appendToElement . addClass ( modalBodyClass ) ;
2019-03-29 22:00:08 +01:00
if ( modal . scope ) {
// we need to explicitly add the modal index to the modal scope
// because it is needed by ngStyle to compute the zIndex property.
modal . scope . $$topModalIndex = topModalIndex ;
}
2016-05-16 13:33:49 +02:00
$animate . enter ( $compile ( angularDomEl ) ( modal . scope ) , appendToElement ) ;
openedWindows . top ( ) . value . modalDomEl = angularDomEl ;
openedWindows . top ( ) . value . modalOpener = modalOpener ;
2019-03-29 22:00:08 +01:00
applyAriaHidden ( angularDomEl ) ;
function applyAriaHidden ( el ) {
if ( ! el || el [ 0 ] . tagName === 'BODY' ) {
return ;
}
getSiblings ( el ) . forEach ( function ( sibling ) {
var elemIsAlreadyHidden = sibling . getAttribute ( 'aria-hidden' ) === 'true' ,
ariaHiddenCount = parseInt ( sibling . getAttribute ( ARIA _HIDDEN _ATTRIBUTE _NAME ) , 10 ) ;
if ( ! ariaHiddenCount ) {
ariaHiddenCount = elemIsAlreadyHidden ? 1 : 0 ;
}
sibling . setAttribute ( ARIA _HIDDEN _ATTRIBUTE _NAME , ariaHiddenCount + 1 ) ;
sibling . setAttribute ( 'aria-hidden' , 'true' ) ;
} ) ;
return applyAriaHidden ( el . parent ( ) ) ;
function getSiblings ( el ) {
var children = el . parent ( ) ? el . parent ( ) . children ( ) : [ ] ;
return Array . prototype . filter . call ( children , function ( child ) {
return child !== el [ 0 ] ;
} ) ;
}
}
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
function broadcastClosing ( modalWindow , resultOrReason , closing ) {
return ! modalWindow . value . modalScope . $broadcast ( 'modal.closing' , resultOrReason , closing ) . defaultPrevented ;
}
2019-03-29 22:00:08 +01:00
function unhideBackgroundElements ( ) {
Array . prototype . forEach . call (
document . querySelectorAll ( '[' + ARIA _HIDDEN _ATTRIBUTE _NAME + ']' ) ,
function ( hiddenEl ) {
var ariaHiddenCount = parseInt ( hiddenEl . getAttribute ( ARIA _HIDDEN _ATTRIBUTE _NAME ) , 10 ) ,
newHiddenCount = ariaHiddenCount - 1 ;
hiddenEl . setAttribute ( ARIA _HIDDEN _ATTRIBUTE _NAME , newHiddenCount ) ;
if ( ! newHiddenCount ) {
hiddenEl . removeAttribute ( ARIA _HIDDEN _ATTRIBUTE _NAME ) ;
hiddenEl . removeAttribute ( 'aria-hidden' ) ;
}
}
) ;
}
2016-05-16 13:33:49 +02:00
$modalStack . close = function ( modalInstance , result ) {
var modalWindow = openedWindows . get ( modalInstance ) ;
2019-03-29 22:00:08 +01:00
unhideBackgroundElements ( ) ;
2016-05-16 13:33:49 +02:00
if ( modalWindow && broadcastClosing ( modalWindow , result , true ) ) {
modalWindow . value . modalScope . $$uibDestructionScheduled = true ;
modalWindow . value . deferred . resolve ( result ) ;
removeModalWindow ( modalInstance , modalWindow . value . modalOpener ) ;
return true ;
2014-08-10 11:37:05 +02:00
}
2019-03-29 22:00:08 +01:00
2016-05-16 13:33:49 +02:00
return ! modalWindow ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
$modalStack . dismiss = function ( modalInstance , reason ) {
var modalWindow = openedWindows . get ( modalInstance ) ;
2019-03-29 22:00:08 +01:00
unhideBackgroundElements ( ) ;
2016-05-16 13:33:49 +02:00
if ( modalWindow && broadcastClosing ( modalWindow , reason , false ) ) {
modalWindow . value . modalScope . $$uibDestructionScheduled = true ;
modalWindow . value . deferred . reject ( reason ) ;
removeModalWindow ( modalInstance , modalWindow . value . modalOpener ) ;
return true ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
return ! modalWindow ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
$modalStack . dismissAll = function ( reason ) {
2014-08-10 11:37:05 +02:00
var topModal = this . getTop ( ) ;
2016-05-16 13:33:49 +02:00
while ( topModal && this . dismiss ( topModal . key , reason ) ) {
2014-08-10 11:37:05 +02:00
topModal = this . getTop ( ) ;
}
} ;
2016-05-16 13:33:49 +02:00
$modalStack . getTop = function ( ) {
2014-08-10 11:37:05 +02:00
return openedWindows . top ( ) ;
} ;
2016-05-16 13:33:49 +02:00
$modalStack . modalRendered = function ( modalInstance ) {
var modalWindow = openedWindows . get ( modalInstance ) ;
if ( modalWindow ) {
modalWindow . value . renderDeferred . resolve ( ) ;
}
} ;
$modalStack . focusFirstFocusableElement = function ( list ) {
if ( list . length > 0 ) {
list [ 0 ] . focus ( ) ;
return true ;
}
return false ;
} ;
$modalStack . focusLastFocusableElement = function ( list ) {
if ( list . length > 0 ) {
list [ list . length - 1 ] . focus ( ) ;
return true ;
}
return false ;
} ;
$modalStack . isModalFocused = function ( evt , modalWindow ) {
if ( evt && modalWindow ) {
var modalDomEl = modalWindow . value . modalDomEl ;
if ( modalDomEl && modalDomEl . length ) {
return ( evt . target || evt . srcElement ) === modalDomEl [ 0 ] ;
}
}
return false ;
} ;
$modalStack . isFocusInFirstItem = function ( evt , list ) {
if ( list . length > 0 ) {
return ( evt . target || evt . srcElement ) === list [ 0 ] ;
}
return false ;
} ;
$modalStack . isFocusInLastItem = function ( evt , list ) {
if ( list . length > 0 ) {
return ( evt . target || evt . srcElement ) === list [ list . length - 1 ] ;
}
return false ;
} ;
$modalStack . loadFocusElementList = function ( modalWindow ) {
if ( modalWindow ) {
var modalDomE1 = modalWindow . value . modalDomEl ;
if ( modalDomE1 && modalDomE1 . length ) {
2019-03-29 22:00:08 +01:00
var elements = modalDomE1 [ 0 ] . querySelectorAll ( tabbableSelector ) ;
2016-05-16 13:33:49 +02:00
return elements ?
Array . prototype . filter . call ( elements , function ( element ) {
return isVisible ( element ) ;
} ) : elements ;
}
}
} ;
2014-08-10 11:37:05 +02:00
return $modalStack ;
} ] )
2016-05-16 13:33:49 +02:00
. provider ( '$uibModal' , function ( ) {
2014-08-10 11:37:05 +02:00
var $modalProvider = {
options : {
2016-05-16 13:33:49 +02:00
animation : true ,
backdrop : true , //can also be false or 'static'
2014-08-10 11:37:05 +02:00
keyboard : true
} ,
2016-05-16 13:33:49 +02:00
$get : [ '$rootScope' , '$q' , '$document' , '$templateRequest' , '$controller' , '$uibResolve' , '$uibModalStack' ,
function ( $rootScope , $q , $document , $templateRequest , $controller , $uibResolve , $modalStack ) {
2014-08-10 11:37:05 +02:00
var $modal = { } ;
function getTemplatePromise ( options ) {
return options . template ? $q . when ( options . template ) :
2016-05-16 13:33:49 +02:00
$templateRequest ( angular . isFunction ( options . templateUrl ) ?
options . templateUrl ( ) : options . templateUrl ) ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
var promiseChain = null ;
$modal . getPromiseChain = function ( ) {
return promiseChain ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$modal . open = function ( modalOptions ) {
2014-08-10 11:37:05 +02:00
var modalResultDeferred = $q . defer ( ) ;
var modalOpenedDeferred = $q . defer ( ) ;
2016-05-16 13:33:49 +02:00
var modalClosedDeferred = $q . defer ( ) ;
var modalRenderDeferred = $q . defer ( ) ;
2014-08-10 11:37:05 +02:00
//prepare an instance of a modal to be injected into controllers and returned to a caller
var modalInstance = {
result : modalResultDeferred . promise ,
opened : modalOpenedDeferred . promise ,
2016-05-16 13:33:49 +02:00
closed : modalClosedDeferred . promise ,
rendered : modalRenderDeferred . promise ,
2014-08-10 11:37:05 +02:00
close : function ( result ) {
2016-05-16 13:33:49 +02:00
return $modalStack . close ( modalInstance , result ) ;
2014-08-10 11:37:05 +02:00
} ,
dismiss : function ( reason ) {
2016-05-16 13:33:49 +02:00
return $modalStack . dismiss ( modalInstance , reason ) ;
2014-08-10 11:37:05 +02:00
}
} ;
//merge and clean up options
modalOptions = angular . extend ( { } , $modalProvider . options , modalOptions ) ;
modalOptions . resolve = modalOptions . resolve || { } ;
2016-05-16 13:33:49 +02:00
modalOptions . appendTo = modalOptions . appendTo || $document . find ( 'body' ) . eq ( 0 ) ;
2014-08-10 11:37:05 +02:00
2019-03-29 22:00:08 +01:00
if ( ! modalOptions . appendTo . length ) {
throw new Error ( 'appendTo element not found. Make sure that the element passed is in DOM.' ) ;
}
2014-08-10 11:37:05 +02:00
//verify options
2019-03-29 22:00:08 +01:00
if ( ! modalOptions . component && ! modalOptions . template && ! modalOptions . templateUrl ) {
throw new Error ( 'One of component or template or templateUrl options is required.' ) ;
2014-08-10 11:37:05 +02:00
}
2019-03-29 22:00:08 +01:00
var templateAndResolvePromise ;
if ( modalOptions . component ) {
templateAndResolvePromise = $q . when ( $uibResolve . resolve ( modalOptions . resolve , { } , null , null ) ) ;
} else {
templateAndResolvePromise =
$q . all ( [ getTemplatePromise ( modalOptions ) , $uibResolve . resolve ( modalOptions . resolve , { } , null , null ) ] ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function resolveWithTemplate ( ) {
return templateAndResolvePromise ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// Wait for the resolution of the existing promise chain.
// Then switch to our own combined promise dependency (regardless of how the previous modal fared).
// Then add to $modalStack and resolve opened.
// Finally clean up the chain variable if no subsequent modal has overwritten it.
var samePromise ;
samePromise = promiseChain = $q . all ( [ promiseChain ] )
. then ( resolveWithTemplate , resolveWithTemplate )
. then ( function resolveSuccess ( tplAndVars ) {
var providedScope = modalOptions . scope || $rootScope ;
var modalScope = providedScope . $new ( ) ;
modalScope . $close = modalInstance . close ;
modalScope . $dismiss = modalInstance . dismiss ;
modalScope . $on ( '$destroy' , function ( ) {
if ( ! modalScope . $$uibDestructionScheduled ) {
modalScope . $dismiss ( '$uibUnscheduledDestruction' ) ;
}
2014-08-10 11:37:05 +02:00
} ) ;
2019-03-29 22:00:08 +01:00
var modal = {
scope : modalScope ,
deferred : modalResultDeferred ,
renderDeferred : modalRenderDeferred ,
closedDeferred : modalClosedDeferred ,
animation : modalOptions . animation ,
backdrop : modalOptions . backdrop ,
keyboard : modalOptions . keyboard ,
backdropClass : modalOptions . backdropClass ,
windowTopClass : modalOptions . windowTopClass ,
windowClass : modalOptions . windowClass ,
windowTemplateUrl : modalOptions . windowTemplateUrl ,
ariaLabelledBy : modalOptions . ariaLabelledBy ,
ariaDescribedBy : modalOptions . ariaDescribedBy ,
size : modalOptions . size ,
openedClass : modalOptions . openedClass ,
appendTo : modalOptions . appendTo
} ;
var component = { } ;
2016-05-16 13:33:49 +02:00
var ctrlInstance , ctrlInstantiate , ctrlLocals = { } ;
2019-03-29 22:00:08 +01:00
if ( modalOptions . component ) {
constructLocals ( component , false , true , false ) ;
component . name = modalOptions . component ;
modal . component = component ;
} else if ( modalOptions . controller ) {
constructLocals ( ctrlLocals , true , false , true ) ;
2016-05-16 13:33:49 +02:00
// the third param will make the controller instantiate later,private api
// @see https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L126
2016-06-11 17:57:30 +02:00
ctrlInstantiate = $controller ( modalOptions . controller , ctrlLocals , true , modalOptions . controllerAs ) ;
if ( modalOptions . controllerAs && modalOptions . bindToController ) {
2016-05-16 13:33:49 +02:00
ctrlInstance = ctrlInstantiate . instance ;
2016-06-11 17:57:30 +02:00
ctrlInstance . $close = modalScope . $close ;
ctrlInstance . $dismiss = modalScope . $dismiss ;
angular . extend ( ctrlInstance , {
$resolve : ctrlLocals . $scope . $resolve
} , providedScope ) ;
2016-05-16 13:33:49 +02:00
}
2016-06-11 17:57:30 +02:00
ctrlInstance = ctrlInstantiate ( ) ;
2016-05-16 13:33:49 +02:00
if ( angular . isFunction ( ctrlInstance . $onInit ) ) {
ctrlInstance . $onInit ( ) ;
}
}
2014-08-10 11:37:05 +02:00
2019-03-29 22:00:08 +01:00
if ( ! modalOptions . component ) {
modal . content = tplAndVars [ 0 ] ;
}
$modalStack . open ( modalInstance , modal ) ;
2016-05-16 13:33:49 +02:00
modalOpenedDeferred . resolve ( true ) ;
2014-08-10 11:37:05 +02:00
2019-03-29 22:00:08 +01:00
function constructLocals ( obj , template , instanceOnScope , injectable ) {
obj . $scope = modalScope ;
obj . $scope . $resolve = { } ;
if ( instanceOnScope ) {
obj . $scope . $uibModalInstance = modalInstance ;
} else {
obj . $uibModalInstance = modalInstance ;
}
var resolves = template ? tplAndVars [ 1 ] : tplAndVars ;
angular . forEach ( resolves , function ( value , key ) {
if ( injectable ) {
obj [ key ] = value ;
}
obj . $scope . $resolve [ key ] = value ;
} ) ;
}
2014-08-10 11:37:05 +02:00
} , function resolveError ( reason ) {
2016-05-16 13:33:49 +02:00
modalOpenedDeferred . reject ( reason ) ;
2014-08-10 11:37:05 +02:00
modalResultDeferred . reject ( reason ) ;
2016-05-16 13:33:49 +02:00
} ) [ 'finally' ] ( function ( ) {
if ( promiseChain === samePromise ) {
promiseChain = null ;
}
2014-08-10 11:37:05 +02:00
} ) ;
return modalInstance ;
} ;
return $modal ;
2016-05-16 13:33:49 +02:00
}
]
2014-08-10 11:37:05 +02:00
} ;
return $modalProvider ;
} ) ;
2016-05-16 13:33:49 +02:00
angular . module ( 'ui.bootstrap.paging' , [ ] )
/ * *
* Helper internal service for generating common controller code between the
* pager and pagination components
* /
. factory ( 'uibPaging' , [ '$parse' , function ( $parse ) {
return {
create : function ( ctrl , $scope , $attrs ) {
ctrl . setNumPages = $attrs . numPages ? $parse ( $attrs . numPages ) . assign : angular . noop ;
ctrl . ngModelCtrl = { $setViewValue : angular . noop } ; // nullModelCtrl
ctrl . _watchers = [ ] ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
ctrl . init = function ( ngModelCtrl , config ) {
ctrl . ngModelCtrl = ngModelCtrl ;
ctrl . config = config ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
ngModelCtrl . $render = function ( ) {
ctrl . render ( ) ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( $attrs . itemsPerPage ) {
ctrl . _watchers . push ( $scope . $parent . $watch ( $attrs . itemsPerPage , function ( value ) {
ctrl . itemsPerPage = parseInt ( value , 10 ) ;
$scope . totalPages = ctrl . calculateTotalPages ( ) ;
ctrl . updatePage ( ) ;
} ) ) ;
} else {
ctrl . itemsPerPage = config . itemsPerPage ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$scope . $watch ( 'totalItems' , function ( newTotal , oldTotal ) {
if ( angular . isDefined ( newTotal ) || newTotal !== oldTotal ) {
$scope . totalPages = ctrl . calculateTotalPages ( ) ;
ctrl . updatePage ( ) ;
}
} ) ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
ctrl . calculateTotalPages = function ( ) {
var totalPages = ctrl . itemsPerPage < 1 ? 1 : Math . ceil ( $scope . totalItems / ctrl . itemsPerPage ) ;
return Math . max ( totalPages || 0 , 1 ) ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
ctrl . render = function ( ) {
$scope . page = parseInt ( ctrl . ngModelCtrl . $viewValue , 10 ) || 1 ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$scope . selectPage = function ( page , evt ) {
if ( evt ) {
evt . preventDefault ( ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var clickAllowed = ! $scope . ngDisabled || ! evt ;
if ( clickAllowed && $scope . page !== page && page > 0 && page <= $scope . totalPages ) {
if ( evt && evt . target ) {
evt . target . blur ( ) ;
}
ctrl . ngModelCtrl . $setViewValue ( page ) ;
ctrl . ngModelCtrl . $render ( ) ;
}
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$scope . getText = function ( key ) {
return $scope [ key + 'Text' ] || ctrl . config [ key + 'Text' ] ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$scope . noPrevious = function ( ) {
return $scope . page === 1 ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$scope . noNext = function ( ) {
return $scope . page === $scope . totalPages ;
} ;
ctrl . updatePage = function ( ) {
ctrl . setNumPages ( $scope . $parent , $scope . totalPages ) ; // Readonly variable
if ( $scope . page > $scope . totalPages ) {
$scope . selectPage ( $scope . totalPages ) ;
} else {
ctrl . ngModelCtrl . $render ( ) ;
}
} ;
$scope . $on ( '$destroy' , function ( ) {
while ( ctrl . _watchers . length ) {
ctrl . _watchers . shift ( ) ( ) ;
}
} ) ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
} ;
} ] ) ;
2019-03-29 22:00:08 +01:00
angular . module ( 'ui.bootstrap.pager' , [ 'ui.bootstrap.paging' , 'ui.bootstrap.tabindex' ] )
2016-05-16 13:33:49 +02:00
. controller ( 'UibPagerController' , [ '$scope' , '$attrs' , 'uibPaging' , 'uibPagerConfig' , function ( $scope , $attrs , uibPaging , uibPagerConfig ) {
$scope . align = angular . isDefined ( $attrs . align ) ? $scope . $parent . $eval ( $attrs . align ) : uibPagerConfig . align ;
uibPaging . create ( this , $scope , $attrs ) ;
2014-08-10 11:37:05 +02:00
} ] )
2016-05-16 13:33:49 +02:00
. constant ( 'uibPagerConfig' , {
2014-08-10 11:37:05 +02:00
itemsPerPage : 10 ,
2016-05-16 13:33:49 +02:00
previousText : '« Previous' ,
nextText : 'Next »' ,
align : true
2014-08-10 11:37:05 +02:00
} )
2016-05-16 13:33:49 +02:00
. directive ( 'uibPager' , [ 'uibPagerConfig' , function ( uibPagerConfig ) {
2014-08-10 11:37:05 +02:00
return {
scope : {
totalItems : '=' ,
previousText : '@' ,
nextText : '@' ,
2016-05-16 13:33:49 +02:00
ngDisabled : '='
} ,
require : [ 'uibPager' , '?ngModel' ] ,
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
2016-05-16 13:33:49 +02:00
controller : 'UibPagerController' ,
controllerAs : 'pager' ,
templateUrl : function ( element , attrs ) {
return attrs . templateUrl || 'uib/template/pager/pager.html' ;
2014-08-10 11:37:05 +02:00
} ,
link : function ( scope , element , attrs , ctrls ) {
2019-03-29 22:00:08 +01:00
element . addClass ( 'pager' ) ;
2014-08-10 11:37:05 +02:00
var paginationCtrl = ctrls [ 0 ] , ngModelCtrl = ctrls [ 1 ] ;
if ( ! ngModelCtrl ) {
2016-05-16 13:33:49 +02:00
return ; // do nothing if no ng-model
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
paginationCtrl . init ( ngModelCtrl , uibPagerConfig ) ;
}
} ;
} ] ) ;
2014-08-10 11:37:05 +02:00
2019-03-29 22:00:08 +01:00
angular . module ( 'ui.bootstrap.pagination' , [ 'ui.bootstrap.paging' , 'ui.bootstrap.tabindex' ] )
2016-05-16 13:33:49 +02:00
. controller ( 'UibPaginationController' , [ '$scope' , '$attrs' , '$parse' , 'uibPaging' , 'uibPaginationConfig' , function ( $scope , $attrs , $parse , uibPaging , uibPaginationConfig ) {
var ctrl = this ;
// Setup configuration parameters
var maxSize = angular . isDefined ( $attrs . maxSize ) ? $scope . $parent . $eval ( $attrs . maxSize ) : uibPaginationConfig . maxSize ,
rotate = angular . isDefined ( $attrs . rotate ) ? $scope . $parent . $eval ( $attrs . rotate ) : uibPaginationConfig . rotate ,
forceEllipses = angular . isDefined ( $attrs . forceEllipses ) ? $scope . $parent . $eval ( $attrs . forceEllipses ) : uibPaginationConfig . forceEllipses ,
boundaryLinkNumbers = angular . isDefined ( $attrs . boundaryLinkNumbers ) ? $scope . $parent . $eval ( $attrs . boundaryLinkNumbers ) : uibPaginationConfig . boundaryLinkNumbers ,
pageLabel = angular . isDefined ( $attrs . pageLabel ) ? function ( idx ) { return $scope . $parent . $eval ( $attrs . pageLabel , { $page : idx } ) ; } : angular . identity ;
$scope . boundaryLinks = angular . isDefined ( $attrs . boundaryLinks ) ? $scope . $parent . $eval ( $attrs . boundaryLinks ) : uibPaginationConfig . boundaryLinks ;
$scope . directionLinks = angular . isDefined ( $attrs . directionLinks ) ? $scope . $parent . $eval ( $attrs . directionLinks ) : uibPaginationConfig . directionLinks ;
2019-03-29 22:00:08 +01:00
$attrs . $set ( 'role' , 'menu' ) ;
2016-05-16 13:33:49 +02:00
uibPaging . create ( this , $scope , $attrs ) ;
if ( $attrs . maxSize ) {
ctrl . _watchers . push ( $scope . $parent . $watch ( $parse ( $attrs . maxSize ) , function ( value ) {
maxSize = parseInt ( value , 10 ) ;
ctrl . render ( ) ;
} ) ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// Create page object used in template
function makePage ( number , text , isActive ) {
return {
number : number ,
text : text ,
active : isActive
} ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function getPages ( currentPage , totalPages ) {
var pages = [ ] ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// Default page limits
var startPage = 1 , endPage = totalPages ;
var isMaxSized = angular . isDefined ( maxSize ) && maxSize < totalPages ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// recompute if maxSize
if ( isMaxSized ) {
if ( rotate ) {
// Current page is displayed in the middle of the visible ones
startPage = Math . max ( currentPage - Math . floor ( maxSize / 2 ) , 1 ) ;
endPage = startPage + maxSize - 1 ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// Adjust if limit is exceeded
if ( endPage > totalPages ) {
endPage = totalPages ;
startPage = endPage - maxSize + 1 ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
} else {
// Visible pages are paginated with maxSize
startPage = ( Math . ceil ( currentPage / maxSize ) - 1 ) * maxSize + 1 ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// Adjust last page if limit is exceeded
endPage = Math . min ( startPage + maxSize - 1 , totalPages ) ;
}
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// Add page number links
for ( var number = startPage ; number <= endPage ; number ++ ) {
var page = makePage ( number , pageLabel ( number ) , number === currentPage ) ;
pages . push ( page ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// Add links to move between page sets
if ( isMaxSized && maxSize > 0 && ( ! rotate || forceEllipses || boundaryLinkNumbers ) ) {
if ( startPage > 1 ) {
if ( ! boundaryLinkNumbers || startPage > 3 ) { //need ellipsis for all options unless range is too close to beginning
var previousPageSet = makePage ( startPage - 1 , '...' , false ) ;
pages . unshift ( previousPageSet ) ;
}
if ( boundaryLinkNumbers ) {
if ( startPage === 3 ) { //need to replace ellipsis when the buttons would be sequential
var secondPageLink = makePage ( 2 , '2' , false ) ;
pages . unshift ( secondPageLink ) ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
//add the first page
var firstPageLink = makePage ( 1 , '1' , false ) ;
pages . unshift ( firstPageLink ) ;
2014-08-10 11:37:05 +02:00
}
}
2016-05-16 13:33:49 +02:00
if ( endPage < totalPages ) {
if ( ! boundaryLinkNumbers || endPage < totalPages - 2 ) { //need ellipsis for all options unless range is too close to end
var nextPageSet = makePage ( endPage + 1 , '...' , false ) ;
pages . push ( nextPageSet ) ;
}
if ( boundaryLinkNumbers ) {
if ( endPage === totalPages - 2 ) { //need to replace ellipsis when the buttons would be sequential
var secondToLastPageLink = makePage ( totalPages - 1 , totalPages - 1 , false ) ;
pages . push ( secondToLastPageLink ) ;
}
//add the last page
var lastPageLink = makePage ( totalPages , totalPages , false ) ;
pages . push ( lastPageLink ) ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
}
}
return pages ;
}
var originalRender = this . render ;
this . render = function ( ) {
originalRender ( ) ;
if ( $scope . page > 0 && $scope . page <= $scope . totalPages ) {
$scope . pages = getPages ( $scope . page , $scope . totalPages ) ;
2014-08-10 11:37:05 +02:00
}
} ;
} ] )
2016-05-16 13:33:49 +02:00
. constant ( 'uibPaginationConfig' , {
2014-08-10 11:37:05 +02:00
itemsPerPage : 10 ,
2016-05-16 13:33:49 +02:00
boundaryLinks : false ,
boundaryLinkNumbers : false ,
directionLinks : true ,
firstText : 'First' ,
previousText : 'Previous' ,
nextText : 'Next' ,
lastText : 'Last' ,
rotate : true ,
forceEllipses : false
2014-08-10 11:37:05 +02:00
} )
2016-05-16 13:33:49 +02:00
. directive ( 'uibPagination' , [ '$parse' , 'uibPaginationConfig' , function ( $parse , uibPaginationConfig ) {
2014-08-10 11:37:05 +02:00
return {
scope : {
totalItems : '=' ,
2016-05-16 13:33:49 +02:00
firstText : '@' ,
2014-08-10 11:37:05 +02:00
previousText : '@' ,
2016-05-16 13:33:49 +02:00
nextText : '@' ,
lastText : '@' ,
ngDisabled : '='
} ,
require : [ 'uibPagination' , '?ngModel' ] ,
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
2016-05-16 13:33:49 +02:00
controller : 'UibPaginationController' ,
controllerAs : 'pagination' ,
templateUrl : function ( element , attrs ) {
return attrs . templateUrl || 'uib/template/pagination/pagination.html' ;
2014-08-10 11:37:05 +02:00
} ,
link : function ( scope , element , attrs , ctrls ) {
2019-03-29 22:00:08 +01:00
element . addClass ( 'pagination' ) ;
2014-08-10 11:37:05 +02:00
var paginationCtrl = ctrls [ 0 ] , ngModelCtrl = ctrls [ 1 ] ;
if ( ! ngModelCtrl ) {
return ; // do nothing if no ng-model
}
2016-05-16 13:33:49 +02:00
paginationCtrl . init ( ngModelCtrl , uibPaginationConfig ) ;
2014-08-10 11:37:05 +02:00
}
} ;
} ] ) ;
/ * *
* The following features are still outstanding : animation as a
* function , placement as a function , inside , support for more triggers than
* just mouse enter / leave , html tooltips , and selector delegation .
* /
2016-05-16 13:33:49 +02:00
angular . module ( 'ui.bootstrap.tooltip' , [ 'ui.bootstrap.position' , 'ui.bootstrap.stackedMap' ] )
2014-08-10 11:37:05 +02:00
/ * *
* The $tooltip service creates tooltip - and popover - like directives as well as
* houses global options for them .
* /
2016-05-16 13:33:49 +02:00
. provider ( '$uibTooltip' , function ( ) {
2014-08-10 11:37:05 +02:00
// The default options tooltip and popover.
var defaultOptions = {
placement : 'top' ,
2016-05-16 13:33:49 +02:00
placementClassPrefix : '' ,
2014-08-10 11:37:05 +02:00
animation : true ,
2016-05-16 13:33:49 +02:00
popupDelay : 0 ,
popupCloseDelay : 0 ,
useContentExp : false
2014-08-10 11:37:05 +02:00
} ;
// Default hide triggers for each show trigger
var triggerMap = {
'mouseenter' : 'mouseleave' ,
'click' : 'click' ,
2016-05-16 13:33:49 +02:00
'outsideClick' : 'outsideClick' ,
'focus' : 'blur' ,
'none' : ''
2014-08-10 11:37:05 +02:00
} ;
// The options specified to the provider globally.
var globalOptions = { } ;
/ * *
* ` options({}) ` allows global configuration of all tooltips in the
* application .
*
* var app = angular . module ( 'App' , [ 'ui.bootstrap.tooltip' ] , function ( $tooltipProvider ) {
* // place tooltips left instead of top by default
* $tooltipProvider . options ( { placement : 'left' } ) ;
* } ) ;
* /
2016-05-16 13:33:49 +02:00
this . options = function ( value ) {
angular . extend ( globalOptions , value ) ;
2014-08-10 11:37:05 +02:00
} ;
/ * *
* This allows you to extend the set of trigger mappings available . E . g . :
*
2016-05-16 13:33:49 +02:00
* $tooltipProvider . setTriggers ( { 'openTrigger' : 'closeTrigger' } ) ;
2014-08-10 11:37:05 +02:00
* /
2016-05-16 13:33:49 +02:00
this . setTriggers = function setTriggers ( triggers ) {
angular . extend ( triggerMap , triggers ) ;
2014-08-10 11:37:05 +02:00
} ;
/ * *
2016-05-16 13:33:49 +02:00
* This is a helper function for translating camel - case to snake _case .
2014-08-10 11:37:05 +02:00
* /
2016-05-16 13:33:49 +02:00
function snake _case ( name ) {
2014-08-10 11:37:05 +02:00
var regexp = /[A-Z]/g ;
var separator = '-' ;
return name . replace ( regexp , function ( letter , pos ) {
return ( pos ? separator : '' ) + letter . toLowerCase ( ) ;
} ) ;
}
/ * *
* Returns the actual instance of the $tooltip service .
* TODO support multiple triggers
* /
2016-05-16 13:33:49 +02:00
this . $get = [ '$window' , '$compile' , '$timeout' , '$document' , '$uibPosition' , '$interpolate' , '$rootScope' , '$parse' , '$$stackedMap' , function ( $window , $compile , $timeout , $document , $position , $interpolate , $rootScope , $parse , $$stackedMap ) {
var openedTooltips = $$stackedMap . createNew ( ) ;
2019-03-29 22:00:08 +01:00
$document . on ( 'keyup' , keypressListener ) ;
2016-05-16 13:33:49 +02:00
$rootScope . $on ( '$destroy' , function ( ) {
2019-03-29 22:00:08 +01:00
$document . off ( 'keyup' , keypressListener ) ;
2016-05-16 13:33:49 +02:00
} ) ;
function keypressListener ( e ) {
if ( e . which === 27 ) {
var last = openedTooltips . top ( ) ;
if ( last ) {
last . value . close ( ) ;
last = null ;
}
}
}
return function $tooltip ( ttType , prefix , defaultTriggerShow , options ) {
options = angular . extend ( { } , defaultOptions , globalOptions , options ) ;
2014-08-10 11:37:05 +02:00
/ * *
* Returns an object of show and hide triggers .
*
* If a trigger is supplied ,
* it is used to show the tooltip ; otherwise , it will use the ` trigger `
* option passed to the ` $ tooltipProvider.options ` method ; else it will
* default to the trigger supplied to this directive factory .
*
* The hide trigger is based on the show trigger . If the ` trigger ` option
* was passed to the ` $ tooltipProvider.options ` method , it will use the
* mapped trigger from ` triggerMap ` or the passed trigger if the map is
* undefined ; otherwise , it uses the ` triggerMap ` value of the show
* trigger ; else it will just use the show trigger .
* /
2016-05-16 13:33:49 +02:00
function getTriggers ( trigger ) {
var show = ( trigger || options . trigger || defaultTriggerShow ) . split ( ' ' ) ;
var hide = show . map ( function ( trigger ) {
return triggerMap [ trigger ] || trigger ;
} ) ;
2014-08-10 11:37:05 +02:00
return {
show : show ,
hide : hide
} ;
}
2016-05-16 13:33:49 +02:00
var directiveName = snake _case ( ttType ) ;
2014-08-10 11:37:05 +02:00
var startSym = $interpolate . startSymbol ( ) ;
var endSym = $interpolate . endSymbol ( ) ;
var template =
2016-05-16 13:33:49 +02:00
'<div ' + directiveName + '-popup ' +
'uib-title="' + startSym + 'title' + endSym + '" ' +
( options . useContentExp ?
'content-exp="contentExp()" ' :
'content="' + startSym + 'content' + endSym + '" ' ) +
'origin-scope="origScope" ' +
2019-03-29 22:00:08 +01:00
'class="uib-position-measure ' + prefix + '" ' +
'tooltip-animation-class="fade"' +
'uib-tooltip-classes ' +
'ng-class="{ in: isOpen }" ' +
2016-05-16 13:33:49 +02:00
'>' +
2014-08-10 11:37:05 +02:00
'</div>' ;
return {
2016-05-16 13:33:49 +02:00
compile : function ( tElem , tAttrs ) {
var tooltipLinker = $compile ( template ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
return function link ( scope , element , attrs , tooltipCtrl ) {
2014-08-10 11:37:05 +02:00
var tooltip ;
2016-05-16 13:33:49 +02:00
var tooltipLinkedScope ;
2014-08-10 11:37:05 +02:00
var transitionTimeout ;
2016-05-16 13:33:49 +02:00
var showTimeout ;
var hideTimeout ;
var positionTimeout ;
2019-03-29 22:00:08 +01:00
var adjustmentTimeout ;
2016-05-16 13:33:49 +02:00
var appendToBody = angular . isDefined ( options . appendToBody ) ? options . appendToBody : false ;
var triggers = getTriggers ( undefined ) ;
var hasEnableExp = angular . isDefined ( attrs [ prefix + 'Enable' ] ) ;
var ttScope = scope . $new ( true ) ;
var repositionScheduled = false ;
var isOpenParse = angular . isDefined ( attrs [ prefix + 'IsOpen' ] ) ? $parse ( attrs [ prefix + 'IsOpen' ] ) : false ;
var contentParse = options . useContentExp ? $parse ( attrs [ ttType ] ) : false ;
var observers = [ ] ;
var lastPlacement ;
var positionTooltip = function ( ) {
// check if tooltip exists and is not empty
if ( ! tooltip || ! tooltip . html ( ) ) { return ; }
if ( ! positionTimeout ) {
positionTimeout = $timeout ( function ( ) {
var ttPosition = $position . positionElements ( element , tooltip , ttScope . placement , appendToBody ) ;
2019-03-29 22:00:08 +01:00
var initialHeight = angular . isDefined ( tooltip . offsetHeight ) ? tooltip . offsetHeight : tooltip . prop ( 'offsetHeight' ) ;
var elementPos = appendToBody ? $position . offset ( element ) : $position . position ( element ) ;
2016-05-16 13:33:49 +02:00
tooltip . css ( { top : ttPosition . top + 'px' , left : ttPosition . left + 'px' } ) ;
2019-03-29 22:00:08 +01:00
var placementClasses = ttPosition . placement . split ( '-' ) ;
2016-05-16 13:33:49 +02:00
2019-03-29 22:00:08 +01:00
if ( ! tooltip . hasClass ( placementClasses [ 0 ] ) ) {
2016-05-16 13:33:49 +02:00
tooltip . removeClass ( lastPlacement . split ( '-' ) [ 0 ] ) ;
2019-03-29 22:00:08 +01:00
tooltip . addClass ( placementClasses [ 0 ] ) ;
2016-05-16 13:33:49 +02:00
}
if ( ! tooltip . hasClass ( options . placementClassPrefix + ttPosition . placement ) ) {
tooltip . removeClass ( options . placementClassPrefix + lastPlacement ) ;
tooltip . addClass ( options . placementClassPrefix + ttPosition . placement ) ;
}
2019-03-29 22:00:08 +01:00
adjustmentTimeout = $timeout ( function ( ) {
var currentHeight = angular . isDefined ( tooltip . offsetHeight ) ? tooltip . offsetHeight : tooltip . prop ( 'offsetHeight' ) ;
var adjustment = $position . adjustTop ( placementClasses , elementPos , initialHeight , currentHeight ) ;
if ( adjustment ) {
tooltip . css ( adjustment ) ;
}
adjustmentTimeout = null ;
} , 0 , false ) ;
2016-05-16 13:33:49 +02:00
// first time through tt element will have the
// uib-position-measure class or if the placement
// has changed we need to position the arrow.
if ( tooltip . hasClass ( 'uib-position-measure' ) ) {
$position . positionArrow ( tooltip , ttPosition . placement ) ;
tooltip . removeClass ( 'uib-position-measure' ) ;
} else if ( lastPlacement !== ttPosition . placement ) {
$position . positionArrow ( tooltip , ttPosition . placement ) ;
}
lastPlacement = ttPosition . placement ;
positionTimeout = null ;
} , 0 , false ) ;
}
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
// Set up the correct scope to allow transclusion later
ttScope . origScope = scope ;
2014-08-10 11:37:05 +02:00
// By default, the tooltip is not open.
// TODO add ability to start tooltip opened
2016-05-16 13:33:49 +02:00
ttScope . isOpen = false ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function toggleTooltipBind ( ) {
if ( ! ttScope . isOpen ) {
2014-08-10 11:37:05 +02:00
showTooltipBind ( ) ;
} else {
hideTooltipBind ( ) ;
}
}
// Show the tooltip with delay if specified, otherwise show it immediately
function showTooltipBind ( ) {
2016-05-16 13:33:49 +02:00
if ( hasEnableExp && ! scope . $eval ( attrs [ prefix + 'Enable' ] ) ) {
2014-08-10 11:37:05 +02:00
return ;
}
2016-05-16 13:33:49 +02:00
cancelHide ( ) ;
prepareTooltip ( ) ;
if ( ttScope . popupDelay ) {
2014-08-10 11:37:05 +02:00
// Do nothing if the tooltip was already scheduled to pop-up.
// This happens if show is triggered multiple times before any hide is triggered.
2016-05-16 13:33:49 +02:00
if ( ! showTimeout ) {
showTimeout = $timeout ( show , ttScope . popupDelay , false ) ;
2014-08-10 11:37:05 +02:00
}
} else {
2016-05-16 13:33:49 +02:00
show ( ) ;
2014-08-10 11:37:05 +02:00
}
}
2016-05-16 13:33:49 +02:00
function hideTooltipBind ( ) {
cancelShow ( ) ;
if ( ttScope . popupCloseDelay ) {
if ( ! hideTimeout ) {
hideTimeout = $timeout ( hide , ttScope . popupCloseDelay , false ) ;
}
} else {
2014-08-10 11:37:05 +02:00
hide ( ) ;
2016-05-16 13:33:49 +02:00
}
2014-08-10 11:37:05 +02:00
}
// Show the tooltip popup element.
function show ( ) {
2016-05-16 13:33:49 +02:00
cancelShow ( ) ;
cancelHide ( ) ;
2014-08-10 11:37:05 +02:00
// Don't show empty tooltips.
2016-05-16 13:33:49 +02:00
if ( ! ttScope . content ) {
2014-08-10 11:37:05 +02:00
return angular . noop ;
}
createTooltip ( ) ;
2016-05-16 13:33:49 +02:00
// And show the tooltip.
ttScope . $evalAsync ( function ( ) {
ttScope . isOpen = true ;
assignIsOpen ( true ) ;
positionTooltip ( ) ;
} ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function cancelShow ( ) {
if ( showTimeout ) {
$timeout . cancel ( showTimeout ) ;
showTimeout = null ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
if ( positionTimeout ) {
$timeout . cancel ( positionTimeout ) ;
positionTimeout = null ;
}
2014-08-10 11:37:05 +02:00
}
// Hide the tooltip popup element.
function hide ( ) {
2016-05-16 13:33:49 +02:00
if ( ! ttScope ) {
return ;
}
2014-08-10 11:37:05 +02:00
// First things first: we don't show it anymore.
2016-05-16 13:33:49 +02:00
ttScope . $evalAsync ( function ( ) {
if ( ttScope ) {
ttScope . isOpen = false ;
assignIsOpen ( false ) ;
// And now we remove it from the DOM. However, if we have animation, we
// need to wait for it to expire beforehand.
// FIXME: this is a placeholder for a port of the transitions library.
// The fade transition in TWBS is 150ms.
if ( ttScope . animation ) {
if ( ! transitionTimeout ) {
transitionTimeout = $timeout ( removeTooltip , 150 , false ) ;
}
} else {
removeTooltip ( ) ;
}
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
} ) ;
}
function cancelHide ( ) {
if ( hideTimeout ) {
$timeout . cancel ( hideTimeout ) ;
hideTimeout = null ;
}
if ( transitionTimeout ) {
$timeout . cancel ( transitionTimeout ) ;
transitionTimeout = null ;
2014-08-10 11:37:05 +02:00
}
}
function createTooltip ( ) {
// There can only be one tooltip element per directive shown at once.
if ( tooltip ) {
2016-05-16 13:33:49 +02:00
return ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
tooltipLinkedScope = ttScope . $new ( ) ;
tooltip = tooltipLinker ( tooltipLinkedScope , function ( tooltip ) {
if ( appendToBody ) {
$document . find ( 'body' ) . append ( tooltip ) ;
} else {
element . after ( tooltip ) ;
}
} ) ;
2019-03-29 22:00:08 +01:00
openedTooltips . add ( ttScope , {
close : hide
} ) ;
2016-05-16 13:33:49 +02:00
prepObservers ( ) ;
2014-08-10 11:37:05 +02:00
}
function removeTooltip ( ) {
2016-05-16 13:33:49 +02:00
cancelShow ( ) ;
cancelHide ( ) ;
unregisterObservers ( ) ;
2014-08-10 11:37:05 +02:00
if ( tooltip ) {
tooltip . remove ( ) ;
2019-03-29 22:00:08 +01:00
2014-08-10 11:37:05 +02:00
tooltip = null ;
2019-03-29 22:00:08 +01:00
if ( adjustmentTimeout ) {
$timeout . cancel ( adjustmentTimeout ) ;
}
2014-08-10 11:37:05 +02:00
}
2019-03-29 22:00:08 +01:00
openedTooltips . remove ( ttScope ) ;
2016-05-16 13:33:49 +02:00
if ( tooltipLinkedScope ) {
tooltipLinkedScope . $destroy ( ) ;
tooltipLinkedScope = null ;
}
}
/ * *
* Set the initial scope values . Once
* the tooltip is created , the observers
* will be added to keep things in sync .
* /
function prepareTooltip ( ) {
ttScope . title = attrs [ prefix + 'Title' ] ;
if ( contentParse ) {
ttScope . content = contentParse ( scope ) ;
} else {
ttScope . content = attrs [ ttType ] ;
}
ttScope . popupClass = attrs [ prefix + 'Class' ] ;
ttScope . placement = angular . isDefined ( attrs [ prefix + 'Placement' ] ) ? attrs [ prefix + 'Placement' ] : options . placement ;
var placement = $position . parsePlacement ( ttScope . placement ) ;
lastPlacement = placement [ 1 ] ? placement [ 0 ] + '-' + placement [ 1 ] : placement [ 0 ] ;
var delay = parseInt ( attrs [ prefix + 'PopupDelay' ] , 10 ) ;
var closeDelay = parseInt ( attrs [ prefix + 'PopupCloseDelay' ] , 10 ) ;
ttScope . popupDelay = ! isNaN ( delay ) ? delay : options . popupDelay ;
ttScope . popupCloseDelay = ! isNaN ( closeDelay ) ? closeDelay : options . popupCloseDelay ;
}
function assignIsOpen ( isOpen ) {
if ( isOpenParse && angular . isFunction ( isOpenParse . assign ) ) {
isOpenParse . assign ( scope , isOpen ) ;
}
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
ttScope . contentExp = function ( ) {
return ttScope . content ;
} ;
2014-08-10 11:37:05 +02:00
/ * *
* Observe the relevant attributes .
* /
2016-05-16 13:33:49 +02:00
attrs . $observe ( 'disabled' , function ( val ) {
if ( val ) {
cancelShow ( ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( val && ttScope . isOpen ) {
2014-08-10 11:37:05 +02:00
hide ( ) ;
}
} ) ;
2016-05-16 13:33:49 +02:00
if ( isOpenParse ) {
scope . $watch ( isOpenParse , function ( val ) {
if ( ttScope && ! val === ttScope . isOpen ) {
toggleTooltipBind ( ) ;
}
} ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function prepObservers ( ) {
observers . length = 0 ;
if ( contentParse ) {
observers . push (
scope . $watch ( contentParse , function ( val ) {
ttScope . content = val ;
if ( ! val && ttScope . isOpen ) {
hide ( ) ;
}
} )
) ;
observers . push (
tooltipLinkedScope . $watch ( function ( ) {
if ( ! repositionScheduled ) {
repositionScheduled = true ;
tooltipLinkedScope . $$postDigest ( function ( ) {
repositionScheduled = false ;
if ( ttScope && ttScope . isOpen ) {
positionTooltip ( ) ;
}
} ) ;
}
} )
) ;
} else {
observers . push (
attrs . $observe ( ttType , function ( val ) {
ttScope . content = val ;
if ( ! val && ttScope . isOpen ) {
hide ( ) ;
} else {
positionTooltip ( ) ;
}
} )
) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
observers . push (
attrs . $observe ( prefix + 'Title' , function ( val ) {
ttScope . title = val ;
if ( ttScope . isOpen ) {
positionTooltip ( ) ;
}
} )
) ;
observers . push (
attrs . $observe ( prefix + 'Placement' , function ( val ) {
ttScope . placement = val ? val : options . placement ;
if ( ttScope . isOpen ) {
positionTooltip ( ) ;
}
} )
) ;
}
function unregisterObservers ( ) {
if ( observers . length ) {
angular . forEach ( observers , function ( observer ) {
observer ( ) ;
} ) ;
observers . length = 0 ;
}
}
// hide tooltips/popovers for outsideClick trigger
function bodyHideTooltipBind ( e ) {
if ( ! ttScope || ! ttScope . isOpen || ! tooltip ) {
return ;
}
// make sure the tooltip/popover link or tool tooltip/popover itself were not clicked
if ( ! element [ 0 ] . contains ( e . target ) && ! tooltip [ 0 ] . contains ( e . target ) ) {
hideTooltipBind ( ) ;
}
}
2014-08-10 11:37:05 +02:00
2019-03-29 22:00:08 +01:00
// KeyboardEvent handler to hide the tooltip on Escape key press
function hideOnEscapeKey ( e ) {
if ( e . which === 27 ) {
hideTooltipBind ( ) ;
}
}
2016-05-16 13:33:49 +02:00
var unregisterTriggers = function ( ) {
triggers . show . forEach ( function ( trigger ) {
if ( trigger === 'outsideClick' ) {
element . off ( 'click' , toggleTooltipBind ) ;
} else {
element . off ( trigger , showTooltipBind ) ;
element . off ( trigger , toggleTooltipBind ) ;
}
2019-03-29 22:00:08 +01:00
element . off ( 'keypress' , hideOnEscapeKey ) ;
2016-05-16 13:33:49 +02:00
} ) ;
triggers . hide . forEach ( function ( trigger ) {
if ( trigger === 'outsideClick' ) {
$document . off ( 'click' , bodyHideTooltipBind ) ;
} else {
element . off ( trigger , hideTooltipBind ) ;
}
} ) ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
function prepTriggers ( ) {
2019-03-29 22:00:08 +01:00
var showTriggers = [ ] , hideTriggers = [ ] ;
var val = scope . $eval ( attrs [ prefix + 'Trigger' ] ) ;
2014-08-10 11:37:05 +02:00
unregisterTriggers ( ) ;
2019-03-29 22:00:08 +01:00
if ( angular . isObject ( val ) ) {
Object . keys ( val ) . forEach ( function ( key ) {
showTriggers . push ( key ) ;
hideTriggers . push ( val [ key ] ) ;
} ) ;
triggers = {
show : showTriggers ,
hide : hideTriggers
} ;
} else {
triggers = getTriggers ( val ) ;
}
2016-05-16 13:33:49 +02:00
if ( triggers . show !== 'none' ) {
triggers . show . forEach ( function ( trigger , idx ) {
if ( trigger === 'outsideClick' ) {
element . on ( 'click' , toggleTooltipBind ) ;
$document . on ( 'click' , bodyHideTooltipBind ) ;
} else if ( trigger === triggers . hide [ idx ] ) {
element . on ( trigger , toggleTooltipBind ) ;
} else if ( trigger ) {
element . on ( trigger , showTooltipBind ) ;
element . on ( triggers . hide [ idx ] , hideTooltipBind ) ;
}
2019-03-29 22:00:08 +01:00
element . on ( 'keypress' , hideOnEscapeKey ) ;
2016-05-16 13:33:49 +02:00
} ) ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
}
prepTriggers ( ) ;
2014-08-10 11:37:05 +02:00
var animation = scope . $eval ( attrs [ prefix + 'Animation' ] ) ;
2016-05-16 13:33:49 +02:00
ttScope . animation = angular . isDefined ( animation ) ? ! ! animation : options . animation ;
var appendToBodyVal ;
var appendKey = prefix + 'AppendToBody' ;
if ( appendKey in attrs && attrs [ appendKey ] === undefined ) {
appendToBodyVal = true ;
} else {
appendToBodyVal = scope . $eval ( attrs [ appendKey ] ) ;
}
appendToBody = angular . isDefined ( appendToBodyVal ) ? appendToBodyVal : appendToBody ;
// Make sure tooltip is destroyed and removed.
scope . $on ( '$destroy' , function onDestroyTooltip ( ) {
unregisterTriggers ( ) ;
removeTooltip ( ) ;
ttScope = null ;
} ) ;
} ;
}
} ;
} ;
} ] ;
} )
// This is mostly ngInclude code but with a custom scope
. directive ( 'uibTooltipTemplateTransclude' , [
'$animate' , '$sce' , '$compile' , '$templateRequest' ,
function ( $animate , $sce , $compile , $templateRequest ) {
return {
link : function ( scope , elem , attrs ) {
var origScope = scope . $eval ( attrs . tooltipTemplateTranscludeScope ) ;
var changeCounter = 0 ,
currentScope ,
previousElement ,
currentElement ;
var cleanupLastIncludeContent = function ( ) {
if ( previousElement ) {
previousElement . remove ( ) ;
previousElement = null ;
}
if ( currentScope ) {
currentScope . $destroy ( ) ;
currentScope = null ;
}
if ( currentElement ) {
$animate . leave ( currentElement ) . then ( function ( ) {
previousElement = null ;
} ) ;
previousElement = currentElement ;
currentElement = null ;
}
} ;
scope . $watch ( $sce . parseAsResourceUrl ( attrs . uibTooltipTemplateTransclude ) , function ( src ) {
var thisChangeId = ++ changeCounter ;
if ( src ) {
//set the 2nd param to true to ignore the template request error so that the inner
//contents and scope can be cleaned up.
$templateRequest ( src , true ) . then ( function ( response ) {
if ( thisChangeId !== changeCounter ) { return ; }
var newScope = origScope . $new ( ) ;
var template = response ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var clone = $compile ( template ) ( newScope , function ( clone ) {
cleanupLastIncludeContent ( ) ;
$animate . enter ( clone , elem ) ;
2014-08-10 11:37:05 +02:00
} ) ;
2016-05-16 13:33:49 +02:00
currentScope = newScope ;
currentElement = clone ;
currentScope . $emit ( '$includeContentLoaded' , src ) ;
} , function ( ) {
if ( thisChangeId === changeCounter ) {
cleanupLastIncludeContent ( ) ;
scope . $emit ( '$includeContentError' , src ) ;
}
} ) ;
scope . $emit ( '$includeContentRequested' , src ) ;
} else {
cleanupLastIncludeContent ( ) ;
}
} ) ;
scope . $on ( '$destroy' , cleanupLastIncludeContent ) ;
}
} ;
} ] )
/ * *
* Note that it ' s intentional that these classes are * not * applied through $animate .
* They must not be animated as they ' re expected to be present on the tooltip on
* initialization .
* /
. directive ( 'uibTooltipClasses' , [ '$uibPosition' , function ( $uibPosition ) {
return {
restrict : 'A' ,
link : function ( scope , element , attrs ) {
// need to set the primary position so the
// arrow has space during position measure.
// tooltip.positionTooltip()
if ( scope . placement ) {
// // There are no top-left etc... classes
// // in TWBS, so we need the primary position.
var position = $uibPosition . parsePlacement ( scope . placement ) ;
element . addClass ( position [ 0 ] ) ;
}
if ( scope . popupClass ) {
element . addClass ( scope . popupClass ) ;
}
2019-03-29 22:00:08 +01:00
if ( scope . animation ) {
2016-05-16 13:33:49 +02:00
element . addClass ( attrs . tooltipAnimationClass ) ;
}
}
} ;
} ] )
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
. directive ( 'uibTooltipPopup' , function ( ) {
return {
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
scope : { content : '@' } ,
2016-05-16 13:33:49 +02:00
templateUrl : 'uib/template/tooltip/tooltip-popup.html'
} ;
2014-08-10 11:37:05 +02:00
} )
2016-05-16 13:33:49 +02:00
. directive ( 'uibTooltip' , [ '$uibTooltip' , function ( $uibTooltip ) {
return $uibTooltip ( 'uibTooltip' , 'tooltip' , 'mouseenter' ) ;
} ] )
. directive ( 'uibTooltipTemplatePopup' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
scope : { contentExp : '&' , originScope : '&' } ,
2016-05-16 13:33:49 +02:00
templateUrl : 'uib/template/tooltip/tooltip-template-popup.html'
2014-08-10 11:37:05 +02:00
} ;
} )
2016-05-16 13:33:49 +02:00
. directive ( 'uibTooltipTemplate' , [ '$uibTooltip' , function ( $uibTooltip ) {
return $uibTooltip ( 'uibTooltipTemplate' , 'tooltip' , 'mouseenter' , {
useContentExp : true
} ) ;
2014-08-10 11:37:05 +02:00
} ] )
2016-05-16 13:33:49 +02:00
. directive ( 'uibTooltipHtmlPopup' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
scope : { contentExp : '&' } ,
2016-05-16 13:33:49 +02:00
templateUrl : 'uib/template/tooltip/tooltip-html-popup.html'
2014-08-10 11:37:05 +02:00
} ;
} )
2016-05-16 13:33:49 +02:00
. directive ( 'uibTooltipHtml' , [ '$uibTooltip' , function ( $uibTooltip ) {
return $uibTooltip ( 'uibTooltipHtml' , 'tooltip' , 'mouseenter' , {
useContentExp : true
} ) ;
2014-08-10 11:37:05 +02:00
} ] ) ;
/ * *
* The following features are still outstanding : popup delay , animation as a
* function , placement as a function , inside , support for more triggers than
2016-05-16 13:33:49 +02:00
* just mouse enter / leave , and selector delegatation .
2014-08-10 11:37:05 +02:00
* /
2016-05-16 13:33:49 +02:00
angular . module ( 'ui.bootstrap.popover' , [ 'ui.bootstrap.tooltip' ] )
. directive ( 'uibPopoverTemplatePopup' , function ( ) {
return {
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
scope : { uibTitle : '@' , contentExp : '&' , originScope : '&' } ,
2016-05-16 13:33:49 +02:00
templateUrl : 'uib/template/popover/popover-template.html'
} ;
} )
. directive ( 'uibPopoverTemplate' , [ '$uibTooltip' , function ( $uibTooltip ) {
return $uibTooltip ( 'uibPopoverTemplate' , 'popover' , 'click' , {
useContentExp : true
} ) ;
} ] )
. directive ( 'uibPopoverHtmlPopup' , function ( ) {
return {
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
scope : { contentExp : '&' , uibTitle : '@' } ,
2016-05-16 13:33:49 +02:00
templateUrl : 'uib/template/popover/popover-html.html'
} ;
} )
. directive ( 'uibPopoverHtml' , [ '$uibTooltip' , function ( $uibTooltip ) {
return $uibTooltip ( 'uibPopoverHtml' , 'popover' , 'click' , {
useContentExp : true
} ) ;
} ] )
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
. directive ( 'uibPopoverPopup' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
scope : { uibTitle : '@' , content : '@' } ,
2016-05-16 13:33:49 +02:00
templateUrl : 'uib/template/popover/popover.html'
2014-08-10 11:37:05 +02:00
} ;
} )
2016-05-16 13:33:49 +02:00
. directive ( 'uibPopover' , [ '$uibTooltip' , function ( $uibTooltip ) {
return $uibTooltip ( 'uibPopover' , 'popover' , 'click' ) ;
2014-08-10 11:37:05 +02:00
} ] ) ;
angular . module ( 'ui.bootstrap.progressbar' , [ ] )
2016-05-16 13:33:49 +02:00
. constant ( 'uibProgressConfig' , {
2014-08-10 11:37:05 +02:00
animate : true ,
max : 100
} )
2016-05-16 13:33:49 +02:00
. controller ( 'UibProgressController' , [ '$scope' , '$attrs' , 'uibProgressConfig' , function ( $scope , $attrs , progressConfig ) {
var self = this ,
animate = angular . isDefined ( $attrs . animate ) ? $scope . $parent . $eval ( $attrs . animate ) : progressConfig . animate ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
this . bars = [ ] ;
$scope . max = getMaxOrDefault ( ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
this . addBar = function ( bar , element , attrs ) {
if ( ! animate ) {
element . css ( { 'transition' : 'none' } ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
this . bars . push ( bar ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
bar . max = getMaxOrDefault ( ) ;
bar . title = attrs && angular . isDefined ( attrs . title ) ? attrs . title : 'progressbar' ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
bar . $watch ( 'value' , function ( value ) {
bar . recalculatePercentage ( ) ;
} ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
bar . recalculatePercentage = function ( ) {
var totalPercentage = self . bars . reduce ( function ( total , bar ) {
bar . percent = + ( 100 * bar . value / bar . max ) . toFixed ( 2 ) ;
return total + bar . percent ;
} , 0 ) ;
if ( totalPercentage > 100 ) {
bar . percent -= totalPercentage - 100 ;
}
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
bar . $on ( '$destroy' , function ( ) {
element = null ;
self . removeBar ( bar ) ;
} ) ;
} ;
this . removeBar = function ( bar ) {
this . bars . splice ( this . bars . indexOf ( bar ) , 1 ) ;
this . bars . forEach ( function ( bar ) {
bar . recalculatePercentage ( ) ;
} ) ;
} ;
//$attrs.$observe('maxParam', function(maxParam) {
$scope . $watch ( 'maxParam' , function ( maxParam ) {
self . bars . forEach ( function ( bar ) {
bar . max = getMaxOrDefault ( ) ;
bar . recalculatePercentage ( ) ;
} ) ;
} ) ;
function getMaxOrDefault ( ) {
return angular . isDefined ( $scope . maxParam ) ? $scope . maxParam : progressConfig . max ;
}
2014-08-10 11:37:05 +02:00
} ] )
2016-05-16 13:33:49 +02:00
. directive ( 'uibProgress' , function ( ) {
return {
replace : true ,
transclude : true ,
controller : 'UibProgressController' ,
require : 'uibProgress' ,
scope : {
maxParam : '=?max'
} ,
templateUrl : 'uib/template/progressbar/progress.html'
} ;
2014-08-10 11:37:05 +02:00
} )
2016-05-16 13:33:49 +02:00
. directive ( 'uibBar' , function ( ) {
return {
replace : true ,
transclude : true ,
require : '^uibProgress' ,
scope : {
value : '=' ,
type : '@'
} ,
templateUrl : 'uib/template/progressbar/bar.html' ,
link : function ( scope , element , attrs , progressCtrl ) {
progressCtrl . addBar ( scope , element , attrs ) ;
}
} ;
2014-08-10 11:37:05 +02:00
} )
2016-05-16 13:33:49 +02:00
. directive ( 'uibProgressbar' , function ( ) {
return {
replace : true ,
transclude : true ,
controller : 'UibProgressController' ,
scope : {
value : '=' ,
maxParam : '=?max' ,
type : '@'
} ,
templateUrl : 'uib/template/progressbar/progressbar.html' ,
link : function ( scope , element , attrs , progressCtrl ) {
progressCtrl . addBar ( scope , angular . element ( element . children ( ) [ 0 ] ) , { title : attrs . title } ) ;
}
} ;
2014-08-10 11:37:05 +02:00
} ) ;
2016-05-16 13:33:49 +02:00
2014-08-10 11:37:05 +02:00
angular . module ( 'ui.bootstrap.rating' , [ ] )
2016-05-16 13:33:49 +02:00
. constant ( 'uibRatingConfig' , {
2014-08-10 11:37:05 +02:00
max : 5 ,
stateOn : null ,
2016-05-16 13:33:49 +02:00
stateOff : null ,
enableReset : true ,
2019-03-29 22:00:08 +01:00
titles : [ 'one' , 'two' , 'three' , 'four' , 'five' ]
2014-08-10 11:37:05 +02:00
} )
2016-05-16 13:33:49 +02:00
. controller ( 'UibRatingController' , [ '$scope' , '$attrs' , 'uibRatingConfig' , function ( $scope , $attrs , ratingConfig ) {
var ngModelCtrl = { $setViewValue : angular . noop } ,
self = this ;
2014-08-10 11:37:05 +02:00
this . init = function ( ngModelCtrl _ ) {
ngModelCtrl = ngModelCtrl _ ;
ngModelCtrl . $render = this . render ;
2016-05-16 13:33:49 +02:00
ngModelCtrl . $formatters . push ( function ( value ) {
if ( angular . isNumber ( value ) && value << 0 !== value ) {
value = Math . round ( value ) ;
}
return value ;
} ) ;
2014-08-10 11:37:05 +02:00
this . stateOn = angular . isDefined ( $attrs . stateOn ) ? $scope . $parent . $eval ( $attrs . stateOn ) : ratingConfig . stateOn ;
this . stateOff = angular . isDefined ( $attrs . stateOff ) ? $scope . $parent . $eval ( $attrs . stateOff ) : ratingConfig . stateOff ;
2016-05-16 13:33:49 +02:00
this . enableReset = angular . isDefined ( $attrs . enableReset ) ?
$scope . $parent . $eval ( $attrs . enableReset ) : ratingConfig . enableReset ;
var tmpTitles = angular . isDefined ( $attrs . titles ) ? $scope . $parent . $eval ( $attrs . titles ) : ratingConfig . titles ;
this . titles = angular . isArray ( tmpTitles ) && tmpTitles . length > 0 ?
tmpTitles : ratingConfig . titles ;
var ratingStates = angular . isDefined ( $attrs . ratingStates ) ?
$scope . $parent . $eval ( $attrs . ratingStates ) :
new Array ( angular . isDefined ( $attrs . max ) ? $scope . $parent . $eval ( $attrs . max ) : ratingConfig . max ) ;
2014-08-10 11:37:05 +02:00
$scope . range = this . buildTemplateObjects ( ratingStates ) ;
} ;
this . buildTemplateObjects = function ( states ) {
for ( var i = 0 , n = states . length ; i < n ; i ++ ) {
2016-05-16 13:33:49 +02:00
states [ i ] = angular . extend ( { index : i } , { stateOn : this . stateOn , stateOff : this . stateOff , title : this . getTitle ( i ) } , states [ i ] ) ;
2014-08-10 11:37:05 +02:00
}
return states ;
} ;
2016-05-16 13:33:49 +02:00
this . getTitle = function ( index ) {
if ( index >= this . titles . length ) {
return index + 1 ;
}
return this . titles [ index ] ;
} ;
2014-08-10 11:37:05 +02:00
$scope . rate = function ( value ) {
2016-05-16 13:33:49 +02:00
if ( ! $scope . readonly && value >= 0 && value <= $scope . range . length ) {
var newViewValue = self . enableReset && ngModelCtrl . $viewValue === value ? 0 : value ;
ngModelCtrl . $setViewValue ( newViewValue ) ;
2014-08-10 11:37:05 +02:00
ngModelCtrl . $render ( ) ;
}
} ;
$scope . enter = function ( value ) {
2016-05-16 13:33:49 +02:00
if ( ! $scope . readonly ) {
2014-08-10 11:37:05 +02:00
$scope . value = value ;
}
$scope . onHover ( { value : value } ) ;
} ;
$scope . reset = function ( ) {
$scope . value = ngModelCtrl . $viewValue ;
$scope . onLeave ( ) ;
} ;
$scope . onKeydown = function ( evt ) {
if ( /(37|38|39|40)/ . test ( evt . which ) ) {
evt . preventDefault ( ) ;
evt . stopPropagation ( ) ;
2016-05-16 13:33:49 +02:00
$scope . rate ( $scope . value + ( evt . which === 38 || evt . which === 39 ? 1 : - 1 ) ) ;
2014-08-10 11:37:05 +02:00
}
} ;
this . render = function ( ) {
$scope . value = ngModelCtrl . $viewValue ;
2016-05-16 13:33:49 +02:00
$scope . title = self . getTitle ( $scope . value - 1 ) ;
2014-08-10 11:37:05 +02:00
} ;
} ] )
2016-05-16 13:33:49 +02:00
. directive ( 'uibRating' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
require : [ 'uibRating' , 'ngModel' ] ,
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
2014-08-10 11:37:05 +02:00
scope : {
2016-05-16 13:33:49 +02:00
readonly : '=?readOnly' ,
2014-08-10 11:37:05 +02:00
onHover : '&' ,
onLeave : '&'
} ,
2016-05-16 13:33:49 +02:00
controller : 'UibRatingController' ,
templateUrl : 'uib/template/rating/rating.html' ,
2014-08-10 11:37:05 +02:00
link : function ( scope , element , attrs , ctrls ) {
var ratingCtrl = ctrls [ 0 ] , ngModelCtrl = ctrls [ 1 ] ;
2016-05-16 13:33:49 +02:00
ratingCtrl . init ( ngModelCtrl ) ;
2014-08-10 11:37:05 +02:00
}
} ;
} ) ;
angular . module ( 'ui.bootstrap.tabs' , [ ] )
2016-05-16 13:33:49 +02:00
. controller ( 'UibTabsetController' , [ '$scope' , function ( $scope ) {
2014-08-10 11:37:05 +02:00
var ctrl = this ,
2016-05-16 13:33:49 +02:00
oldIndex ;
ctrl . tabs = [ ] ;
ctrl . select = function ( index , evt ) {
if ( ! destroyed ) {
var previousIndex = findTabIndex ( oldIndex ) ;
var previousSelected = ctrl . tabs [ previousIndex ] ;
if ( previousSelected ) {
previousSelected . tab . onDeselect ( {
2016-06-11 17:57:30 +02:00
$event : evt ,
$selectedIndex : index
2016-05-16 13:33:49 +02:00
} ) ;
if ( evt && evt . isDefaultPrevented ( ) ) {
return ;
}
previousSelected . tab . active = false ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var selected = ctrl . tabs [ index ] ;
if ( selected ) {
selected . tab . onSelect ( {
$event : evt
} ) ;
selected . tab . active = true ;
ctrl . active = selected . index ;
oldIndex = selected . index ;
2016-06-11 17:57:30 +02:00
} else if ( ! selected && angular . isDefined ( oldIndex ) ) {
2016-05-16 13:33:49 +02:00
ctrl . active = null ;
oldIndex = null ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
}
2014-08-10 11:37:05 +02:00
} ;
ctrl . addTab = function addTab ( tab ) {
2016-05-16 13:33:49 +02:00
ctrl . tabs . push ( {
tab : tab ,
index : tab . index
} ) ;
ctrl . tabs . sort ( function ( t1 , t2 ) {
if ( t1 . index > t2 . index ) {
return 1 ;
}
if ( t1 . index < t2 . index ) {
return - 1 ;
}
return 0 ;
} ) ;
2016-06-11 17:57:30 +02:00
if ( tab . index === ctrl . active || ! angular . isDefined ( ctrl . active ) && ctrl . tabs . length === 1 ) {
2016-05-16 13:33:49 +02:00
var newActiveIndex = findTabIndex ( tab . index ) ;
ctrl . select ( newActiveIndex ) ;
2014-08-10 11:37:05 +02:00
}
} ;
ctrl . removeTab = function removeTab ( tab ) {
2016-05-16 13:33:49 +02:00
var index ;
for ( var i = 0 ; i < ctrl . tabs . length ; i ++ ) {
if ( ctrl . tabs [ i ] . tab === tab ) {
index = i ;
break ;
}
}
if ( ctrl . tabs [ index ] . index === ctrl . active ) {
var newActiveTabIndex = index === ctrl . tabs . length - 1 ?
index - 1 : index + 1 % ctrl . tabs . length ;
ctrl . select ( newActiveTabIndex ) ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
ctrl . tabs . splice ( index , 1 ) ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
$scope . $watch ( 'tabset.active' , function ( val ) {
2016-06-11 17:57:30 +02:00
if ( angular . isDefined ( val ) && val !== oldIndex ) {
2016-05-16 13:33:49 +02:00
ctrl . select ( findTabIndex ( val ) ) ;
}
} ) ;
var destroyed ;
$scope . $on ( '$destroy' , function ( ) {
destroyed = true ;
} ) ;
function findTabIndex ( index ) {
for ( var i = 0 ; i < ctrl . tabs . length ; i ++ ) {
if ( ctrl . tabs [ i ] . index === index ) {
return i ;
}
}
}
2014-08-10 11:37:05 +02:00
} ] )
2016-05-16 13:33:49 +02:00
. directive ( 'uibTabset' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
transclude : true ,
replace : true ,
2016-05-16 13:33:49 +02:00
scope : { } ,
bindToController : {
active : '=?' ,
2014-08-10 11:37:05 +02:00
type : '@'
} ,
2016-05-16 13:33:49 +02:00
controller : 'UibTabsetController' ,
controllerAs : 'tabset' ,
templateUrl : function ( element , attrs ) {
return attrs . templateUrl || 'uib/template/tabs/tabset.html' ;
} ,
2014-08-10 11:37:05 +02:00
link : function ( scope , element , attrs ) {
2016-05-16 13:33:49 +02:00
scope . vertical = angular . isDefined ( attrs . vertical ) ?
scope . $parent . $eval ( attrs . vertical ) : false ;
scope . justified = angular . isDefined ( attrs . justified ) ?
scope . $parent . $eval ( attrs . justified ) : false ;
2014-08-10 11:37:05 +02:00
}
} ;
} )
2016-05-16 13:33:49 +02:00
. directive ( 'uibTab' , [ '$parse' , function ( $parse ) {
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
require : '^uibTabset' ,
2014-08-10 11:37:05 +02:00
replace : true ,
2016-05-16 13:33:49 +02:00
templateUrl : function ( element , attrs ) {
return attrs . templateUrl || 'uib/template/tabs/tab.html' ;
} ,
2014-08-10 11:37:05 +02:00
transclude : true ,
scope : {
heading : '@' ,
2016-05-16 13:33:49 +02:00
index : '=?' ,
classes : '@?' ,
2014-08-10 11:37:05 +02:00
onSelect : '&select' , //This callback is called in contentHeadingTransclude
//once it inserts the tab's content into the dom
onDeselect : '&deselect'
} ,
controller : function ( ) {
//Empty controller so other directives can require being 'under' a tab
} ,
2016-05-16 13:33:49 +02:00
controllerAs : 'tab' ,
link : function ( scope , elm , attrs , tabsetCtrl , transclude ) {
scope . disabled = false ;
if ( attrs . disable ) {
scope . $parent . $watch ( $parse ( attrs . disable ) , function ( value ) {
scope . disabled = ! ! value ;
2014-08-10 11:37:05 +02:00
} ) ;
2016-05-16 13:33:49 +02:00
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( angular . isUndefined ( attrs . index ) ) {
if ( tabsetCtrl . tabs && tabsetCtrl . tabs . length ) {
scope . index = Math . max . apply ( null , tabsetCtrl . tabs . map ( function ( t ) { return t . index ; } ) ) + 1 ;
} else {
scope . index = 0 ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( angular . isUndefined ( attrs . classes ) ) {
scope . classes = '' ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
scope . select = function ( evt ) {
if ( ! scope . disabled ) {
var index ;
for ( var i = 0 ; i < tabsetCtrl . tabs . length ; i ++ ) {
if ( tabsetCtrl . tabs [ i ] . tab === scope ) {
index = i ;
break ;
}
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
tabsetCtrl . select ( index , evt ) ;
}
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
tabsetCtrl . addTab ( scope ) ;
scope . $on ( '$destroy' , function ( ) {
tabsetCtrl . removeTab ( scope ) ;
} ) ;
//We need to transclude later, once the content container is ready.
//when this link happens, we're inside a tab heading.
scope . $transcludeFn = transclude ;
2014-08-10 11:37:05 +02:00
}
} ;
} ] )
2016-05-16 13:33:49 +02:00
. directive ( 'uibTabHeadingTransclude' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
restrict : 'A' ,
2016-05-16 13:33:49 +02:00
require : '^uibTab' ,
link : function ( scope , elm ) {
2014-08-10 11:37:05 +02:00
scope . $watch ( 'headingElement' , function updateHeadingElement ( heading ) {
if ( heading ) {
elm . html ( '' ) ;
elm . append ( heading ) ;
}
} ) ;
}
} ;
2016-05-16 13:33:49 +02:00
} )
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
. directive ( 'uibTabContentTransclude' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
restrict : 'A' ,
2016-05-16 13:33:49 +02:00
require : '^uibTabset' ,
2014-08-10 11:37:05 +02:00
link : function ( scope , elm , attrs ) {
2016-05-16 13:33:49 +02:00
var tab = scope . $eval ( attrs . uibTabContentTransclude ) . tab ;
2014-08-10 11:37:05 +02:00
//Now our tab is ready to be transcluded: both the tab heading area
//and the tab content area are loaded. Transclude 'em both.
tab . $transcludeFn ( tab . $parent , function ( contents ) {
angular . forEach ( contents , function ( node ) {
if ( isTabHeading ( node ) ) {
//Let tabHeadingTransclude know.
tab . headingElement = node ;
} else {
elm . append ( node ) ;
}
} ) ;
} ) ;
}
} ;
2016-05-16 13:33:49 +02:00
2014-08-10 11:37:05 +02:00
function isTabHeading ( node ) {
2016-05-16 13:33:49 +02:00
return node . tagName && (
node . hasAttribute ( 'uib-tab-heading' ) ||
node . hasAttribute ( 'data-uib-tab-heading' ) ||
node . hasAttribute ( 'x-uib-tab-heading' ) ||
node . tagName . toLowerCase ( ) === 'uib-tab-heading' ||
node . tagName . toLowerCase ( ) === 'data-uib-tab-heading' ||
node . tagName . toLowerCase ( ) === 'x-uib-tab-heading' ||
node . tagName . toLowerCase ( ) === 'uib:tab-heading'
2014-08-10 11:37:05 +02:00
) ;
}
2016-05-16 13:33:49 +02:00
} ) ;
2014-08-10 11:37:05 +02:00
angular . module ( 'ui.bootstrap.timepicker' , [ ] )
2016-05-16 13:33:49 +02:00
. constant ( 'uibTimepickerConfig' , {
2014-08-10 11:37:05 +02:00
hourStep : 1 ,
minuteStep : 1 ,
2016-05-16 13:33:49 +02:00
secondStep : 1 ,
2014-08-10 11:37:05 +02:00
showMeridian : true ,
2016-05-16 13:33:49 +02:00
showSeconds : false ,
2014-08-10 11:37:05 +02:00
meridians : null ,
readonlyInput : false ,
2016-05-16 13:33:49 +02:00
mousewheel : true ,
arrowkeys : true ,
showSpinners : true ,
templateUrl : 'uib/template/timepicker/timepicker.html'
2014-08-10 11:37:05 +02:00
} )
2016-05-16 13:33:49 +02:00
. controller ( 'UibTimepickerController' , [ '$scope' , '$element' , '$attrs' , '$parse' , '$log' , '$locale' , 'uibTimepickerConfig' , function ( $scope , $element , $attrs , $parse , $log , $locale , timepickerConfig ) {
2019-03-29 22:00:08 +01:00
var hoursModelCtrl , minutesModelCtrl , secondsModelCtrl ;
2014-08-10 11:37:05 +02:00
var selected = new Date ( ) ,
2016-05-16 13:33:49 +02:00
watchers = [ ] ,
ngModelCtrl = { $setViewValue : angular . noop } , // nullModelCtrl
meridians = angular . isDefined ( $attrs . meridians ) ? $scope . $parent . $eval ( $attrs . meridians ) : timepickerConfig . meridians || $locale . DATETIME _FORMATS . AMPMS ,
padHours = angular . isDefined ( $attrs . padHours ) ? $scope . $parent . $eval ( $attrs . padHours ) : true ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$scope . tabindex = angular . isDefined ( $attrs . tabindex ) ? $attrs . tabindex : 0 ;
$element . removeAttr ( 'tabindex' ) ;
this . init = function ( ngModelCtrl _ , inputs ) {
2014-08-10 11:37:05 +02:00
ngModelCtrl = ngModelCtrl _ ;
ngModelCtrl . $render = this . render ;
2016-05-16 13:33:49 +02:00
ngModelCtrl . $formatters . unshift ( function ( modelValue ) {
return modelValue ? new Date ( modelValue ) : null ;
} ) ;
2014-08-10 11:37:05 +02:00
var hoursInputEl = inputs . eq ( 0 ) ,
2016-05-16 13:33:49 +02:00
minutesInputEl = inputs . eq ( 1 ) ,
secondsInputEl = inputs . eq ( 2 ) ;
2014-08-10 11:37:05 +02:00
2019-03-29 22:00:08 +01:00
hoursModelCtrl = hoursInputEl . controller ( 'ngModel' ) ;
minutesModelCtrl = minutesInputEl . controller ( 'ngModel' ) ;
secondsModelCtrl = secondsInputEl . controller ( 'ngModel' ) ;
2014-08-10 11:37:05 +02:00
var mousewheel = angular . isDefined ( $attrs . mousewheel ) ? $scope . $parent . $eval ( $attrs . mousewheel ) : timepickerConfig . mousewheel ;
2016-05-16 13:33:49 +02:00
if ( mousewheel ) {
this . setupMousewheelEvents ( hoursInputEl , minutesInputEl , secondsInputEl ) ;
}
var arrowkeys = angular . isDefined ( $attrs . arrowkeys ) ? $scope . $parent . $eval ( $attrs . arrowkeys ) : timepickerConfig . arrowkeys ;
if ( arrowkeys ) {
this . setupArrowkeyEvents ( hoursInputEl , minutesInputEl , secondsInputEl ) ;
2014-08-10 11:37:05 +02:00
}
$scope . readonlyInput = angular . isDefined ( $attrs . readonlyInput ) ? $scope . $parent . $eval ( $attrs . readonlyInput ) : timepickerConfig . readonlyInput ;
2016-05-16 13:33:49 +02:00
this . setupInputEvents ( hoursInputEl , minutesInputEl , secondsInputEl ) ;
2014-08-10 11:37:05 +02:00
} ;
var hourStep = timepickerConfig . hourStep ;
if ( $attrs . hourStep ) {
2016-05-16 13:33:49 +02:00
watchers . push ( $scope . $parent . $watch ( $parse ( $attrs . hourStep ) , function ( value ) {
hourStep = + value ;
} ) ) ;
2014-08-10 11:37:05 +02:00
}
var minuteStep = timepickerConfig . minuteStep ;
if ( $attrs . minuteStep ) {
2016-05-16 13:33:49 +02:00
watchers . push ( $scope . $parent . $watch ( $parse ( $attrs . minuteStep ) , function ( value ) {
minuteStep = + value ;
} ) ) ;
}
var min ;
watchers . push ( $scope . $parent . $watch ( $parse ( $attrs . min ) , function ( value ) {
var dt = new Date ( value ) ;
min = isNaN ( dt ) ? undefined : dt ;
} ) ) ;
var max ;
watchers . push ( $scope . $parent . $watch ( $parse ( $attrs . max ) , function ( value ) {
var dt = new Date ( value ) ;
max = isNaN ( dt ) ? undefined : dt ;
} ) ) ;
var disabled = false ;
if ( $attrs . ngDisabled ) {
watchers . push ( $scope . $parent . $watch ( $parse ( $attrs . ngDisabled ) , function ( value ) {
disabled = value ;
} ) ) ;
}
$scope . noIncrementHours = function ( ) {
var incrementedSelected = addMinutes ( selected , hourStep * 60 ) ;
return disabled || incrementedSelected > max ||
incrementedSelected < selected && incrementedSelected < min ;
} ;
$scope . noDecrementHours = function ( ) {
var decrementedSelected = addMinutes ( selected , - hourStep * 60 ) ;
return disabled || decrementedSelected < min ||
decrementedSelected > selected && decrementedSelected > max ;
} ;
$scope . noIncrementMinutes = function ( ) {
var incrementedSelected = addMinutes ( selected , minuteStep ) ;
return disabled || incrementedSelected > max ||
incrementedSelected < selected && incrementedSelected < min ;
} ;
$scope . noDecrementMinutes = function ( ) {
var decrementedSelected = addMinutes ( selected , - minuteStep ) ;
return disabled || decrementedSelected < min ||
decrementedSelected > selected && decrementedSelected > max ;
} ;
$scope . noIncrementSeconds = function ( ) {
var incrementedSelected = addSeconds ( selected , secondStep ) ;
return disabled || incrementedSelected > max ||
incrementedSelected < selected && incrementedSelected < min ;
} ;
$scope . noDecrementSeconds = function ( ) {
var decrementedSelected = addSeconds ( selected , - secondStep ) ;
return disabled || decrementedSelected < min ||
decrementedSelected > selected && decrementedSelected > max ;
} ;
$scope . noToggleMeridian = function ( ) {
if ( selected . getHours ( ) < 12 ) {
return disabled || addMinutes ( selected , 12 * 60 ) > max ;
}
return disabled || addMinutes ( selected , - 12 * 60 ) < min ;
} ;
var secondStep = timepickerConfig . secondStep ;
if ( $attrs . secondStep ) {
watchers . push ( $scope . $parent . $watch ( $parse ( $attrs . secondStep ) , function ( value ) {
secondStep = + value ;
} ) ) ;
}
$scope . showSeconds = timepickerConfig . showSeconds ;
if ( $attrs . showSeconds ) {
watchers . push ( $scope . $parent . $watch ( $parse ( $attrs . showSeconds ) , function ( value ) {
$scope . showSeconds = ! ! value ;
} ) ) ;
2014-08-10 11:37:05 +02:00
}
// 12H / 24H mode
$scope . showMeridian = timepickerConfig . showMeridian ;
if ( $attrs . showMeridian ) {
2016-05-16 13:33:49 +02:00
watchers . push ( $scope . $parent . $watch ( $parse ( $attrs . showMeridian ) , function ( value ) {
2014-08-10 11:37:05 +02:00
$scope . showMeridian = ! ! value ;
2016-05-16 13:33:49 +02:00
if ( ngModelCtrl . $error . time ) {
2014-08-10 11:37:05 +02:00
// Evaluate from template
var hours = getHoursFromTemplate ( ) , minutes = getMinutesFromTemplate ( ) ;
2016-05-16 13:33:49 +02:00
if ( angular . isDefined ( hours ) && angular . isDefined ( minutes ) ) {
selected . setHours ( hours ) ;
2014-08-10 11:37:05 +02:00
refresh ( ) ;
}
} else {
updateTemplate ( ) ;
}
2016-05-16 13:33:49 +02:00
} ) ) ;
2014-08-10 11:37:05 +02:00
}
// Get $scope.hours in 24H mode if valid
2016-05-16 13:33:49 +02:00
function getHoursFromTemplate ( ) {
var hours = + $scope . hours ;
var valid = $scope . showMeridian ? hours > 0 && hours < 13 :
hours >= 0 && hours < 24 ;
if ( ! valid || $scope . hours === '' ) {
2014-08-10 11:37:05 +02:00
return undefined ;
}
2016-05-16 13:33:49 +02:00
if ( $scope . showMeridian ) {
if ( hours === 12 ) {
2014-08-10 11:37:05 +02:00
hours = 0 ;
}
2016-05-16 13:33:49 +02:00
if ( $scope . meridian === meridians [ 1 ] ) {
2014-08-10 11:37:05 +02:00
hours = hours + 12 ;
}
}
return hours ;
}
function getMinutesFromTemplate ( ) {
2016-05-16 13:33:49 +02:00
var minutes = + $scope . minutes ;
var valid = minutes >= 0 && minutes < 60 ;
if ( ! valid || $scope . minutes === '' ) {
return undefined ;
}
return minutes ;
}
function getSecondsFromTemplate ( ) {
var seconds = + $scope . seconds ;
return seconds >= 0 && seconds < 60 ? seconds : undefined ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
function pad ( value , noPad ) {
if ( value === null ) {
return '' ;
}
return angular . isDefined ( value ) && value . toString ( ) . length < 2 && ! noPad ?
'0' + value : value . toString ( ) ;
2014-08-10 11:37:05 +02:00
}
// Respond on mousewheel spin
2016-05-16 13:33:49 +02:00
this . setupMousewheelEvents = function ( hoursInputEl , minutesInputEl , secondsInputEl ) {
2014-08-10 11:37:05 +02:00
var isScrollingUp = function ( e ) {
if ( e . originalEvent ) {
e = e . originalEvent ;
}
//pick correct delta variable depending on event
2016-05-16 13:33:49 +02:00
var delta = e . wheelDelta ? e . wheelDelta : - e . deltaY ;
return e . detail || delta > 0 ;
2014-08-10 11:37:05 +02:00
} ;
2019-03-29 22:00:08 +01:00
hoursInputEl . on ( 'mousewheel wheel' , function ( e ) {
2016-05-16 13:33:49 +02:00
if ( ! disabled ) {
$scope . $apply ( isScrollingUp ( e ) ? $scope . incrementHours ( ) : $scope . decrementHours ( ) ) ;
}
2014-08-10 11:37:05 +02:00
e . preventDefault ( ) ;
} ) ;
2019-03-29 22:00:08 +01:00
minutesInputEl . on ( 'mousewheel wheel' , function ( e ) {
2016-05-16 13:33:49 +02:00
if ( ! disabled ) {
$scope . $apply ( isScrollingUp ( e ) ? $scope . incrementMinutes ( ) : $scope . decrementMinutes ( ) ) ;
}
e . preventDefault ( ) ;
} ) ;
2019-03-29 22:00:08 +01:00
secondsInputEl . on ( 'mousewheel wheel' , function ( e ) {
2016-05-16 13:33:49 +02:00
if ( ! disabled ) {
$scope . $apply ( isScrollingUp ( e ) ? $scope . incrementSeconds ( ) : $scope . decrementSeconds ( ) ) ;
}
2014-08-10 11:37:05 +02:00
e . preventDefault ( ) ;
} ) ;
2016-05-16 13:33:49 +02:00
} ;
// Respond on up/down arrowkeys
this . setupArrowkeyEvents = function ( hoursInputEl , minutesInputEl , secondsInputEl ) {
2019-03-29 22:00:08 +01:00
hoursInputEl . on ( 'keydown' , function ( e ) {
2016-05-16 13:33:49 +02:00
if ( ! disabled ) {
if ( e . which === 38 ) { // up
e . preventDefault ( ) ;
$scope . incrementHours ( ) ;
$scope . $apply ( ) ;
} else if ( e . which === 40 ) { // down
e . preventDefault ( ) ;
$scope . decrementHours ( ) ;
$scope . $apply ( ) ;
}
}
} ) ;
2014-08-10 11:37:05 +02:00
2019-03-29 22:00:08 +01:00
minutesInputEl . on ( 'keydown' , function ( e ) {
2016-05-16 13:33:49 +02:00
if ( ! disabled ) {
if ( e . which === 38 ) { // up
e . preventDefault ( ) ;
$scope . incrementMinutes ( ) ;
$scope . $apply ( ) ;
} else if ( e . which === 40 ) { // down
e . preventDefault ( ) ;
$scope . decrementMinutes ( ) ;
$scope . $apply ( ) ;
}
}
} ) ;
2019-03-29 22:00:08 +01:00
secondsInputEl . on ( 'keydown' , function ( e ) {
2016-05-16 13:33:49 +02:00
if ( ! disabled ) {
if ( e . which === 38 ) { // up
e . preventDefault ( ) ;
$scope . incrementSeconds ( ) ;
$scope . $apply ( ) ;
} else if ( e . which === 40 ) { // down
e . preventDefault ( ) ;
$scope . decrementSeconds ( ) ;
$scope . $apply ( ) ;
}
}
} ) ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
this . setupInputEvents = function ( hoursInputEl , minutesInputEl , secondsInputEl ) {
if ( $scope . readonlyInput ) {
2014-08-10 11:37:05 +02:00
$scope . updateHours = angular . noop ;
$scope . updateMinutes = angular . noop ;
2016-05-16 13:33:49 +02:00
$scope . updateSeconds = angular . noop ;
2014-08-10 11:37:05 +02:00
return ;
}
2016-05-16 13:33:49 +02:00
var invalidate = function ( invalidHours , invalidMinutes , invalidSeconds ) {
ngModelCtrl . $setViewValue ( null ) ;
2014-08-10 11:37:05 +02:00
ngModelCtrl . $setValidity ( 'time' , false ) ;
if ( angular . isDefined ( invalidHours ) ) {
$scope . invalidHours = invalidHours ;
2019-03-29 22:00:08 +01:00
if ( hoursModelCtrl ) {
hoursModelCtrl . $setValidity ( 'hours' , false ) ;
}
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
2014-08-10 11:37:05 +02:00
if ( angular . isDefined ( invalidMinutes ) ) {
$scope . invalidMinutes = invalidMinutes ;
2019-03-29 22:00:08 +01:00
if ( minutesModelCtrl ) {
minutesModelCtrl . $setValidity ( 'minutes' , false ) ;
}
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
if ( angular . isDefined ( invalidSeconds ) ) {
$scope . invalidSeconds = invalidSeconds ;
2019-03-29 22:00:08 +01:00
if ( secondsModelCtrl ) {
secondsModelCtrl . $setValidity ( 'seconds' , false ) ;
}
2016-05-16 13:33:49 +02:00
}
2014-08-10 11:37:05 +02:00
} ;
$scope . updateHours = function ( ) {
2016-05-16 13:33:49 +02:00
var hours = getHoursFromTemplate ( ) ,
minutes = getMinutesFromTemplate ( ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
ngModelCtrl . $setDirty ( ) ;
if ( angular . isDefined ( hours ) && angular . isDefined ( minutes ) ) {
selected . setHours ( hours ) ;
selected . setMinutes ( minutes ) ;
if ( selected < min || selected > max ) {
invalidate ( true ) ;
} else {
refresh ( 'h' ) ;
}
2014-08-10 11:37:05 +02:00
} else {
invalidate ( true ) ;
}
} ;
2019-03-29 22:00:08 +01:00
hoursInputEl . on ( 'blur' , function ( e ) {
2016-05-16 13:33:49 +02:00
ngModelCtrl . $setTouched ( ) ;
if ( modelIsEmpty ( ) ) {
makeValid ( ) ;
} else if ( $scope . hours === null || $scope . hours === '' ) {
invalidate ( true ) ;
} else if ( ! $scope . invalidHours && $scope . hours < 10 ) {
$scope . $apply ( function ( ) {
$scope . hours = pad ( $scope . hours , ! padHours ) ;
2014-08-10 11:37:05 +02:00
} ) ;
}
} ) ;
$scope . updateMinutes = function ( ) {
2016-05-16 13:33:49 +02:00
var minutes = getMinutesFromTemplate ( ) ,
hours = getHoursFromTemplate ( ) ;
ngModelCtrl . $setDirty ( ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( angular . isDefined ( minutes ) && angular . isDefined ( hours ) ) {
selected . setHours ( hours ) ;
selected . setMinutes ( minutes ) ;
if ( selected < min || selected > max ) {
invalidate ( undefined , true ) ;
} else {
refresh ( 'm' ) ;
}
2014-08-10 11:37:05 +02:00
} else {
invalidate ( undefined , true ) ;
}
} ;
2019-03-29 22:00:08 +01:00
minutesInputEl . on ( 'blur' , function ( e ) {
2016-05-16 13:33:49 +02:00
ngModelCtrl . $setTouched ( ) ;
if ( modelIsEmpty ( ) ) {
makeValid ( ) ;
} else if ( $scope . minutes === null ) {
invalidate ( undefined , true ) ;
} else if ( ! $scope . invalidMinutes && $scope . minutes < 10 ) {
$scope . $apply ( function ( ) {
$scope . minutes = pad ( $scope . minutes ) ;
} ) ;
}
} ) ;
$scope . updateSeconds = function ( ) {
var seconds = getSecondsFromTemplate ( ) ;
ngModelCtrl . $setDirty ( ) ;
if ( angular . isDefined ( seconds ) ) {
selected . setSeconds ( seconds ) ;
refresh ( 's' ) ;
} else {
invalidate ( undefined , undefined , true ) ;
}
} ;
2019-03-29 22:00:08 +01:00
secondsInputEl . on ( 'blur' , function ( e ) {
2016-05-16 13:33:49 +02:00
if ( modelIsEmpty ( ) ) {
makeValid ( ) ;
} else if ( ! $scope . invalidSeconds && $scope . seconds < 10 ) {
2014-08-10 11:37:05 +02:00
$scope . $apply ( function ( ) {
2016-05-16 13:33:49 +02:00
$scope . seconds = pad ( $scope . seconds ) ;
2014-08-10 11:37:05 +02:00
} ) ;
}
} ) ;
} ;
this . render = function ( ) {
2016-05-16 13:33:49 +02:00
var date = ngModelCtrl . $viewValue ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( isNaN ( date ) ) {
2014-08-10 11:37:05 +02:00
ngModelCtrl . $setValidity ( 'time' , false ) ;
$log . error ( 'Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.' ) ;
} else {
2016-05-16 13:33:49 +02:00
if ( date ) {
2014-08-10 11:37:05 +02:00
selected = date ;
}
2016-05-16 13:33:49 +02:00
if ( selected < min || selected > max ) {
ngModelCtrl . $setValidity ( 'time' , false ) ;
$scope . invalidHours = true ;
$scope . invalidMinutes = true ;
} else {
makeValid ( ) ;
}
2014-08-10 11:37:05 +02:00
updateTemplate ( ) ;
}
} ;
// Call internally when we know that model is valid.
2016-05-16 13:33:49 +02:00
function refresh ( keyboardChange ) {
2014-08-10 11:37:05 +02:00
makeValid ( ) ;
2016-05-16 13:33:49 +02:00
ngModelCtrl . $setViewValue ( new Date ( selected ) ) ;
updateTemplate ( keyboardChange ) ;
2014-08-10 11:37:05 +02:00
}
function makeValid ( ) {
2019-03-29 22:00:08 +01:00
if ( hoursModelCtrl ) {
hoursModelCtrl . $setValidity ( 'hours' , true ) ;
}
if ( minutesModelCtrl ) {
minutesModelCtrl . $setValidity ( 'minutes' , true ) ;
}
if ( secondsModelCtrl ) {
secondsModelCtrl . $setValidity ( 'seconds' , true ) ;
}
2014-08-10 11:37:05 +02:00
ngModelCtrl . $setValidity ( 'time' , true ) ;
$scope . invalidHours = false ;
$scope . invalidMinutes = false ;
2016-05-16 13:33:49 +02:00
$scope . invalidSeconds = false ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
function updateTemplate ( keyboardChange ) {
if ( ! ngModelCtrl . $modelValue ) {
$scope . hours = null ;
$scope . minutes = null ;
$scope . seconds = null ;
$scope . meridian = meridians [ 0 ] ;
} else {
var hours = selected . getHours ( ) ,
minutes = selected . getMinutes ( ) ,
seconds = selected . getSeconds ( ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( $scope . showMeridian ) {
hours = hours === 0 || hours === 12 ? 12 : hours % 12 ; // Convert 24 to 12 hour system
}
$scope . hours = keyboardChange === 'h' ? hours : pad ( hours , ! padHours ) ;
if ( keyboardChange !== 'm' ) {
$scope . minutes = pad ( minutes ) ;
}
$scope . meridian = selected . getHours ( ) < 12 ? meridians [ 0 ] : meridians [ 1 ] ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( keyboardChange !== 's' ) {
$scope . seconds = pad ( seconds ) ;
}
$scope . meridian = selected . getHours ( ) < 12 ? meridians [ 0 ] : meridians [ 1 ] ;
}
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
function addSecondsToSelected ( seconds ) {
selected = addSeconds ( selected , seconds ) ;
2014-08-10 11:37:05 +02:00
refresh ( ) ;
}
2016-05-16 13:33:49 +02:00
function addMinutes ( selected , minutes ) {
return addSeconds ( selected , minutes * 60 ) ;
}
function addSeconds ( date , seconds ) {
var dt = new Date ( date . getTime ( ) + seconds * 1000 ) ;
var newDate = new Date ( date ) ;
newDate . setHours ( dt . getHours ( ) , dt . getMinutes ( ) , dt . getSeconds ( ) ) ;
return newDate ;
}
function modelIsEmpty ( ) {
return ( $scope . hours === null || $scope . hours === '' ) &&
( $scope . minutes === null || $scope . minutes === '' ) &&
( ! $scope . showSeconds || $scope . showSeconds && ( $scope . seconds === null || $scope . seconds === '' ) ) ;
}
$scope . showSpinners = angular . isDefined ( $attrs . showSpinners ) ?
$scope . $parent . $eval ( $attrs . showSpinners ) : timepickerConfig . showSpinners ;
2014-08-10 11:37:05 +02:00
$scope . incrementHours = function ( ) {
2016-05-16 13:33:49 +02:00
if ( ! $scope . noIncrementHours ( ) ) {
addSecondsToSelected ( hourStep * 60 * 60 ) ;
}
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
2014-08-10 11:37:05 +02:00
$scope . decrementHours = function ( ) {
2016-05-16 13:33:49 +02:00
if ( ! $scope . noDecrementHours ( ) ) {
addSecondsToSelected ( - hourStep * 60 * 60 ) ;
}
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
2014-08-10 11:37:05 +02:00
$scope . incrementMinutes = function ( ) {
2016-05-16 13:33:49 +02:00
if ( ! $scope . noIncrementMinutes ( ) ) {
addSecondsToSelected ( minuteStep * 60 ) ;
}
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
2014-08-10 11:37:05 +02:00
$scope . decrementMinutes = function ( ) {
2016-05-16 13:33:49 +02:00
if ( ! $scope . noDecrementMinutes ( ) ) {
addSecondsToSelected ( - minuteStep * 60 ) ;
}
} ;
$scope . incrementSeconds = function ( ) {
if ( ! $scope . noIncrementSeconds ( ) ) {
addSecondsToSelected ( secondStep ) ;
}
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
$scope . decrementSeconds = function ( ) {
if ( ! $scope . noDecrementSeconds ( ) ) {
addSecondsToSelected ( - secondStep ) ;
}
} ;
2014-08-10 11:37:05 +02:00
$scope . toggleMeridian = function ( ) {
2016-05-16 13:33:49 +02:00
var minutes = getMinutesFromTemplate ( ) ,
hours = getHoursFromTemplate ( ) ;
if ( ! $scope . noToggleMeridian ( ) ) {
if ( angular . isDefined ( minutes ) && angular . isDefined ( hours ) ) {
addSecondsToSelected ( 12 * 60 * ( selected . getHours ( ) < 12 ? 60 : - 60 ) ) ;
} else {
$scope . meridian = $scope . meridian === meridians [ 0 ] ? meridians [ 1 ] : meridians [ 0 ] ;
}
}
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
$scope . blur = function ( ) {
ngModelCtrl . $setTouched ( ) ;
} ;
$scope . $on ( '$destroy' , function ( ) {
while ( watchers . length ) {
watchers . shift ( ) ( ) ;
}
} ) ;
2014-08-10 11:37:05 +02:00
} ] )
2016-05-16 13:33:49 +02:00
. directive ( 'uibTimepicker' , [ 'uibTimepickerConfig' , function ( uibTimepickerConfig ) {
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
require : [ 'uibTimepicker' , '?^ngModel' ] ,
2019-03-29 22:00:08 +01:00
restrict : 'A' ,
2016-05-16 13:33:49 +02:00
controller : 'UibTimepickerController' ,
controllerAs : 'timepicker' ,
2014-08-10 11:37:05 +02:00
scope : { } ,
2016-05-16 13:33:49 +02:00
templateUrl : function ( element , attrs ) {
return attrs . templateUrl || uibTimepickerConfig . templateUrl ;
} ,
2014-08-10 11:37:05 +02:00
link : function ( scope , element , attrs , ctrls ) {
var timepickerCtrl = ctrls [ 0 ] , ngModelCtrl = ctrls [ 1 ] ;
2016-05-16 13:33:49 +02:00
if ( ngModelCtrl ) {
timepickerCtrl . init ( ngModelCtrl , element . find ( 'input' ) ) ;
2014-08-10 11:37:05 +02:00
}
}
} ;
2016-05-16 13:33:49 +02:00
} ] ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
angular . module ( 'ui.bootstrap.typeahead' , [ 'ui.bootstrap.debounce' , 'ui.bootstrap.position' ] )
2014-08-10 11:37:05 +02:00
/ * *
* A helper service that can parse typeahead ' s syntax ( string provided by users )
* Extracted to a separate service for ease of unit testing
* /
2016-05-16 13:33:49 +02:00
. factory ( 'uibTypeaheadParser' , [ '$parse' , function ( $parse ) {
2019-03-29 22:00:08 +01:00
// 000001111111100000000000002222222200000000000000003333333333333330000000000044444444000
2016-05-16 13:33:49 +02:00
var TYPEAHEAD _REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/ ;
return {
parse : function ( input ) {
var match = input . match ( TYPEAHEAD _REGEXP ) ;
if ( ! match ) {
throw new Error (
'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
' but got "' + input + '".' ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
return {
itemName : match [ 3 ] ,
source : $parse ( match [ 4 ] ) ,
viewMapper : $parse ( match [ 2 ] || match [ 1 ] ) ,
modelMapper : $parse ( match [ 1 ] )
} ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
} ;
} ] )
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
. controller ( 'UibTypeaheadController' , [ '$scope' , '$element' , '$attrs' , '$compile' , '$parse' , '$q' , '$timeout' , '$document' , '$window' , '$rootScope' , '$$debounce' , '$uibPosition' , 'uibTypeaheadParser' ,
function ( originalScope , element , attrs , $compile , $parse , $q , $timeout , $document , $window , $rootScope , $$debounce , $position , typeaheadParser ) {
var HOT _KEYS = [ 9 , 13 , 27 , 38 , 40 ] ;
var eventDebounceTime = 200 ;
var modelCtrl , ngModelOptions ;
//SUPPORTED ATTRIBUTES (OPTIONS)
//minimal no of characters that needs to be entered before typeahead kicks-in
var minLength = originalScope . $eval ( attrs . typeaheadMinLength ) ;
if ( ! minLength && minLength !== 0 ) {
minLength = 1 ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
originalScope . $watch ( attrs . typeaheadMinLength , function ( newVal ) {
minLength = ! newVal && newVal !== 0 ? 1 : newVal ;
} ) ;
2016-06-11 17:57:30 +02:00
2016-05-16 13:33:49 +02:00
//minimal wait time after last character typed before typeahead kicks-in
var waitTime = originalScope . $eval ( attrs . typeaheadWaitMs ) || 0 ;
//should it restrict model values to the ones selected from the popup only?
var isEditable = originalScope . $eval ( attrs . typeaheadEditable ) !== false ;
originalScope . $watch ( attrs . typeaheadEditable , function ( newVal ) {
isEditable = newVal !== false ;
} ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//binding to a variable that indicates if matches are being retrieved asynchronously
var isLoadingSetter = $parse ( attrs . typeaheadLoading ) . assign || angular . noop ;
2014-08-10 11:37:05 +02:00
2016-06-11 17:57:30 +02:00
//a function to determine if an event should cause selection
var isSelectEvent = attrs . typeaheadShouldSelect ? $parse ( attrs . typeaheadShouldSelect ) : function ( scope , vals ) {
var evt = vals . $event ;
return evt . which === 13 || evt . which === 9 ;
} ;
2016-05-16 13:33:49 +02:00
//a callback executed when a match is selected
var onSelectCallback = $parse ( attrs . typeaheadOnSelect ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//should it select highlighted popup value when losing focus?
var isSelectOnBlur = angular . isDefined ( attrs . typeaheadSelectOnBlur ) ? originalScope . $eval ( attrs . typeaheadSelectOnBlur ) : false ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//binding to a variable that indicates if there were no results after the query is completed
var isNoResultsSetter = $parse ( attrs . typeaheadNoResults ) . assign || angular . noop ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var inputFormatter = attrs . typeaheadInputFormatter ? $parse ( attrs . typeaheadInputFormatter ) : undefined ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var appendToBody = attrs . typeaheadAppendToBody ? originalScope . $eval ( attrs . typeaheadAppendToBody ) : false ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var appendTo = attrs . typeaheadAppendTo ?
originalScope . $eval ( attrs . typeaheadAppendTo ) : null ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var focusFirst = originalScope . $eval ( attrs . typeaheadFocusFirst ) !== false ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//If input matches an item of the list exactly, select it automatically
var selectOnExact = attrs . typeaheadSelectOnExact ? originalScope . $eval ( attrs . typeaheadSelectOnExact ) : false ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//binding to a variable that indicates if dropdown is open
var isOpenSetter = $parse ( attrs . typeaheadIsOpen ) . assign || angular . noop ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var showHint = originalScope . $eval ( attrs . typeaheadShowHint ) || false ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//INTERNAL VARIABLES
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//model setter executed upon match selection
var parsedModel = $parse ( attrs . ngModel ) ;
var invokeModelSetter = $parse ( attrs . ngModel + '($$$p)' ) ;
var $setModelValue = function ( scope , newValue ) {
if ( angular . isFunction ( parsedModel ( originalScope ) ) &&
2019-03-29 22:00:08 +01:00
ngModelOptions . getOption ( 'getterSetter' ) ) {
2016-05-16 13:33:49 +02:00
return invokeModelSetter ( scope , { $$$p : newValue } ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
return parsedModel . assign ( scope , newValue ) ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//expressions used by typeahead
var parserResult = typeaheadParser . parse ( attrs . uibTypeahead ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var hasFocus ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//Used to avoid bug in iOS webview where iOS keyboard does not fire
//mousedown & mouseup events
//Issue #3699
var selected ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//create a child scope for the typeahead directive so we are not polluting original scope
//with typeahead-specific data (matches, query etc.)
var scope = originalScope . $new ( ) ;
var offDestroy = originalScope . $on ( '$destroy' , function ( ) {
scope . $destroy ( ) ;
} ) ;
scope . $on ( '$destroy' , offDestroy ) ;
// WAI-ARIA
var popupId = 'typeahead-' + scope . $id + '-' + Math . floor ( Math . random ( ) * 10000 ) ;
element . attr ( {
'aria-autocomplete' : 'list' ,
'aria-expanded' : false ,
'aria-owns' : popupId
} ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var inputsContainer , hintInputElem ;
//add read-only input to show hint
if ( showHint ) {
inputsContainer = angular . element ( '<div></div>' ) ;
inputsContainer . css ( 'position' , 'relative' ) ;
element . after ( inputsContainer ) ;
hintInputElem = element . clone ( ) ;
hintInputElem . attr ( 'placeholder' , '' ) ;
hintInputElem . attr ( 'tabindex' , '-1' ) ;
hintInputElem . val ( '' ) ;
hintInputElem . css ( {
'position' : 'absolute' ,
'top' : '0px' ,
'left' : '0px' ,
'border-color' : 'transparent' ,
'box-shadow' : 'none' ,
'opacity' : 1 ,
'background' : 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)' ,
'color' : '#999'
} ) ;
element . css ( {
'position' : 'relative' ,
'vertical-align' : 'top' ,
'background-color' : 'transparent'
2014-08-10 11:37:05 +02:00
} ) ;
2019-03-29 22:00:08 +01:00
if ( hintInputElem . attr ( 'id' ) ) {
hintInputElem . removeAttr ( 'id' ) ; // remove duplicate id if present.
}
2016-05-16 13:33:49 +02:00
inputsContainer . append ( hintInputElem ) ;
hintInputElem . after ( element ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//pop-up element used to display matches
var popUpEl = angular . element ( '<div uib-typeahead-popup></div>' ) ;
popUpEl . attr ( {
id : popupId ,
matches : 'matches' ,
active : 'activeIdx' ,
select : 'select(activeIdx, evt)' ,
'move-in-progress' : 'moveInProgress' ,
query : 'query' ,
position : 'position' ,
'assign-is-open' : 'assignIsOpen(isOpen)' ,
debounce : 'debounceUpdate'
} ) ;
//custom item template
if ( angular . isDefined ( attrs . typeaheadTemplateUrl ) ) {
popUpEl . attr ( 'template-url' , attrs . typeaheadTemplateUrl ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( angular . isDefined ( attrs . typeaheadPopupTemplateUrl ) ) {
popUpEl . attr ( 'popup-template-url' , attrs . typeaheadPopupTemplateUrl ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var resetHint = function ( ) {
if ( showHint ) {
hintInputElem . val ( '' ) ;
}
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var resetMatches = function ( ) {
scope . matches = [ ] ;
scope . activeIdx = - 1 ;
element . attr ( 'aria-expanded' , false ) ;
resetHint ( ) ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var getMatchId = function ( index ) {
return popupId + '-option-' + index ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
// This attribute is added or removed automatically when the `activeIdx` changes.
scope . $watch ( 'activeIdx' , function ( index ) {
if ( index < 0 ) {
element . removeAttr ( 'aria-activedescendant' ) ;
} else {
element . attr ( 'aria-activedescendant' , getMatchId ( index ) ) ;
}
} ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var inputIsExactMatch = function ( inputValue , index ) {
if ( scope . matches . length > index && inputValue ) {
return inputValue . toUpperCase ( ) === scope . matches [ index ] . label . toUpperCase ( ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
return false ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var getMatchesAsync = function ( inputValue , evt ) {
var locals = { $viewValue : inputValue } ;
isLoadingSetter ( originalScope , true ) ;
isNoResultsSetter ( originalScope , false ) ;
$q . when ( parserResult . source ( originalScope , locals ) ) . then ( function ( matches ) {
//it might happen that several async queries were in progress if a user were typing fast
//but we are interested only in responses that correspond to the current view value
var onCurrentRequest = inputValue === modelCtrl . $viewValue ;
if ( onCurrentRequest && hasFocus ) {
if ( matches && matches . length > 0 ) {
scope . activeIdx = focusFirst ? 0 : - 1 ;
isNoResultsSetter ( originalScope , false ) ;
scope . matches . length = 0 ;
//transform labels
for ( var i = 0 ; i < matches . length ; i ++ ) {
locals [ parserResult . itemName ] = matches [ i ] ;
scope . matches . push ( {
id : getMatchId ( i ) ,
label : parserResult . viewMapper ( scope , locals ) ,
model : matches [ i ]
} ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
scope . query = inputValue ;
//position pop-up with matches - we need to re-calculate its position each time we are opening a window
//with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
//due to other elements being rendered
recalculatePosition ( ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
element . attr ( 'aria-expanded' , true ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//Select the single remaining option if user input matches
if ( selectOnExact && scope . matches . length === 1 && inputIsExactMatch ( inputValue , 0 ) ) {
if ( angular . isNumber ( scope . debounceUpdate ) || angular . isObject ( scope . debounceUpdate ) ) {
$$debounce ( function ( ) {
scope . select ( 0 , evt ) ;
} , angular . isNumber ( scope . debounceUpdate ) ? scope . debounceUpdate : scope . debounceUpdate [ 'default' ] ) ;
} else {
scope . select ( 0 , evt ) ;
}
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( showHint ) {
var firstLabel = scope . matches [ 0 ] . label ;
if ( angular . isString ( inputValue ) &&
inputValue . length > 0 &&
firstLabel . slice ( 0 , inputValue . length ) . toUpperCase ( ) === inputValue . toUpperCase ( ) ) {
hintInputElem . val ( inputValue + firstLabel . slice ( inputValue . length ) ) ;
} else {
hintInputElem . val ( '' ) ;
}
2014-08-10 11:37:05 +02:00
}
} else {
2016-05-16 13:33:49 +02:00
resetMatches ( ) ;
isNoResultsSetter ( originalScope , true ) ;
2014-08-10 11:37:05 +02:00
}
}
2016-05-16 13:33:49 +02:00
if ( onCurrentRequest ) {
isLoadingSetter ( originalScope , false ) ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
} , function ( ) {
resetMatches ( ) ;
isLoadingSetter ( originalScope , false ) ;
isNoResultsSetter ( originalScope , true ) ;
2014-08-10 11:37:05 +02:00
} ) ;
2016-05-16 13:33:49 +02:00
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// bind events only if appendToBody params exist - performance feature
if ( appendToBody ) {
angular . element ( $window ) . on ( 'resize' , fireRecalculating ) ;
$document . find ( 'body' ) . on ( 'scroll' , fireRecalculating ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// Declare the debounced function outside recalculating for
// proper debouncing
var debouncedRecalculate = $$debounce ( function ( ) {
// if popup is visible
if ( scope . matches . length ) {
recalculatePosition ( ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
scope . moveInProgress = false ;
} , eventDebounceTime ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// Default progress type
scope . moveInProgress = false ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
function fireRecalculating ( ) {
if ( ! scope . moveInProgress ) {
scope . moveInProgress = true ;
scope . $digest ( ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
debouncedRecalculate ( ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// recalculate actual position and set new values to scope
// after digest loop is popup in right position
function recalculatePosition ( ) {
scope . position = appendToBody ? $position . offset ( element ) : $position . position ( element ) ;
scope . position . top += element . prop ( 'offsetHeight' ) ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//we need to propagate user's query so we can higlight matches
scope . query = undefined ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
var timeoutPromise ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var scheduleSearchWithTimeout = function ( inputValue ) {
timeoutPromise = $timeout ( function ( ) {
getMatchesAsync ( inputValue ) ;
} , waitTime ) ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
var cancelPreviousTimeout = function ( ) {
if ( timeoutPromise ) {
$timeout . cancel ( timeoutPromise ) ;
}
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
resetMatches ( ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
scope . assignIsOpen = function ( isOpen ) {
isOpenSetter ( originalScope , isOpen ) ;
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
scope . select = function ( activeIdx , evt ) {
//called from within the $digest() cycle
var locals = { } ;
var model , item ;
selected = true ;
locals [ parserResult . itemName ] = item = scope . matches [ activeIdx ] . model ;
model = parserResult . modelMapper ( originalScope , locals ) ;
$setModelValue ( originalScope , model ) ;
modelCtrl . $setValidity ( 'editable' , true ) ;
modelCtrl . $setValidity ( 'parse' , true ) ;
onSelectCallback ( originalScope , {
$item : item ,
$model : model ,
$label : parserResult . viewMapper ( originalScope , locals ) ,
$event : evt
} ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
resetMatches ( ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//return focus to the input element if a match was selected via a mouse click event
// use timeout to avoid $rootScope:inprog error
if ( scope . $eval ( attrs . typeaheadFocusOnSelect ) !== false ) {
$timeout ( function ( ) { element [ 0 ] . focus ( ) ; } , 0 , false ) ;
}
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
//bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
element . on ( 'keydown' , function ( evt ) {
//typeahead is open and an "interesting" key was pressed
if ( scope . matches . length === 0 || HOT _KEYS . indexOf ( evt . which ) === - 1 ) {
return ;
}
2016-06-11 17:57:30 +02:00
var shouldSelect = isSelectEvent ( originalScope , { $event : evt } ) ;
2016-05-16 13:33:49 +02:00
/ * *
* if there ' s nothing selected ( i . e . focusFirst ) and enter or tab is hit
* or
* shift + tab is pressed to bring focus to the previous element
* then clear the results
* /
2016-06-11 17:57:30 +02:00
if ( scope . activeIdx === - 1 && shouldSelect || evt . which === 9 && ! ! evt . shiftKey ) {
2016-05-16 13:33:49 +02:00
resetMatches ( ) ;
scope . $digest ( ) ;
return ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
evt . preventDefault ( ) ;
var target ;
switch ( evt . which ) {
2016-06-11 17:57:30 +02:00
case 27 : // escape
2014-08-10 11:37:05 +02:00
evt . stopPropagation ( ) ;
resetMatches ( ) ;
2016-05-16 13:33:49 +02:00
originalScope . $digest ( ) ;
break ;
2016-06-11 17:57:30 +02:00
case 38 : // up arrow
2016-05-16 13:33:49 +02:00
scope . activeIdx = ( scope . activeIdx > 0 ? scope . activeIdx : scope . matches . length ) - 1 ;
scope . $digest ( ) ;
2019-03-29 22:00:08 +01:00
target = popUpEl [ 0 ] . querySelectorAll ( '.uib-typeahead-match' ) [ scope . activeIdx ] ;
2016-05-16 13:33:49 +02:00
target . parentNode . scrollTop = target . offsetTop ;
break ;
2016-06-11 17:57:30 +02:00
case 40 : // down arrow
2016-05-16 13:33:49 +02:00
scope . activeIdx = ( scope . activeIdx + 1 ) % scope . matches . length ;
2014-08-10 11:37:05 +02:00
scope . $digest ( ) ;
2019-03-29 22:00:08 +01:00
target = popUpEl [ 0 ] . querySelectorAll ( '.uib-typeahead-match' ) [ scope . activeIdx ] ;
2016-05-16 13:33:49 +02:00
target . parentNode . scrollTop = target . offsetTop ;
break ;
2016-06-11 17:57:30 +02:00
default :
if ( shouldSelect ) {
scope . $apply ( function ( ) {
if ( angular . isNumber ( scope . debounceUpdate ) || angular . isObject ( scope . debounceUpdate ) ) {
$$debounce ( function ( ) {
scope . select ( scope . activeIdx , evt ) ;
} , angular . isNumber ( scope . debounceUpdate ) ? scope . debounceUpdate : scope . debounceUpdate [ 'default' ] ) ;
} else {
scope . select ( scope . activeIdx , evt ) ;
}
} ) ;
}
2016-05-16 13:33:49 +02:00
}
} ) ;
2019-03-29 22:00:08 +01:00
element . on ( 'focus' , function ( evt ) {
2016-05-16 13:33:49 +02:00
hasFocus = true ;
if ( minLength === 0 && ! modelCtrl . $viewValue ) {
$timeout ( function ( ) {
getMatchesAsync ( modelCtrl . $viewValue , evt ) ;
} , 0 ) ;
}
} ) ;
2019-03-29 22:00:08 +01:00
element . on ( 'blur' , function ( evt ) {
2016-05-16 13:33:49 +02:00
if ( isSelectOnBlur && scope . matches . length && scope . activeIdx !== - 1 && ! selected ) {
selected = true ;
scope . $apply ( function ( ) {
if ( angular . isObject ( scope . debounceUpdate ) && angular . isNumber ( scope . debounceUpdate . blur ) ) {
$$debounce ( function ( ) {
scope . select ( scope . activeIdx , evt ) ;
} , scope . debounceUpdate . blur ) ;
} else {
scope . select ( scope . activeIdx , evt ) ;
}
} ) ;
}
if ( ! isEditable && modelCtrl . $error . editable ) {
modelCtrl . $setViewValue ( ) ;
2019-03-29 22:00:08 +01:00
scope . $apply ( function ( ) {
// Reset validity as we are clearing
modelCtrl . $setValidity ( 'editable' , true ) ;
modelCtrl . $setValidity ( 'parse' , true ) ;
} ) ;
2016-05-16 13:33:49 +02:00
element . val ( '' ) ;
}
hasFocus = false ;
selected = false ;
} ) ;
// Keep reference to click handler to unbind it.
var dismissClickHandler = function ( evt ) {
// Issue #3973
// Firefox treats right click as a click on document
if ( element [ 0 ] !== evt . target && evt . which !== 3 && scope . matches . length !== 0 ) {
resetMatches ( ) ;
if ( ! $rootScope . $$phase ) {
originalScope . $digest ( ) ;
2014-08-10 11:37:05 +02:00
}
2016-05-16 13:33:49 +02:00
}
} ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
$document . on ( 'click' , dismissClickHandler ) ;
originalScope . $on ( '$destroy' , function ( ) {
$document . off ( 'click' , dismissClickHandler ) ;
if ( appendToBody || appendTo ) {
$popup . remove ( ) ;
}
if ( appendToBody ) {
angular . element ( $window ) . off ( 'resize' , fireRecalculating ) ;
$document . find ( 'body' ) . off ( 'scroll' , fireRecalculating ) ;
}
// Prevent jQuery cache memory leak
popUpEl . remove ( ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
if ( showHint ) {
inputsContainer . remove ( ) ;
}
} ) ;
var $popup = $compile ( popUpEl ) ( scope ) ;
if ( appendToBody ) {
$document . find ( 'body' ) . append ( $popup ) ;
} else if ( appendTo ) {
angular . element ( appendTo ) . eq ( 0 ) . append ( $popup ) ;
} else {
element . after ( $popup ) ;
}
2019-03-29 22:00:08 +01:00
this . init = function ( _modelCtrl ) {
2016-05-16 13:33:49 +02:00
modelCtrl = _modelCtrl ;
2019-03-29 22:00:08 +01:00
ngModelOptions = extractOptions ( modelCtrl ) ;
2016-05-16 13:33:49 +02:00
2019-03-29 22:00:08 +01:00
scope . debounceUpdate = $parse ( ngModelOptions . getOption ( 'debounce' ) ) ( originalScope ) ;
2016-05-16 13:33:49 +02:00
//plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
//$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
modelCtrl . $parsers . unshift ( function ( inputValue ) {
hasFocus = true ;
if ( minLength === 0 || inputValue && inputValue . length >= minLength ) {
if ( waitTime > 0 ) {
cancelPreviousTimeout ( ) ;
scheduleSearchWithTimeout ( inputValue ) ;
} else {
getMatchesAsync ( inputValue ) ;
}
} else {
isLoadingSetter ( originalScope , false ) ;
cancelPreviousTimeout ( ) ;
2014-08-10 11:37:05 +02:00
resetMatches ( ) ;
}
2016-05-16 13:33:49 +02:00
if ( isEditable ) {
return inputValue ;
}
if ( ! inputValue ) {
// Reset in case user had typed something previously.
modelCtrl . $setValidity ( 'editable' , true ) ;
return null ;
}
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
modelCtrl . $setValidity ( 'editable' , false ) ;
return undefined ;
2014-08-10 11:37:05 +02:00
} ) ;
2016-05-16 13:33:49 +02:00
modelCtrl . $formatters . push ( function ( modelValue ) {
var candidateViewValue , emptyViewValue ;
var locals = { } ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
// The validity may be set to false via $parsers (see above) if
// the model is restricted to selected values. If the model
// is set manually it is considered to be valid.
if ( ! isEditable ) {
modelCtrl . $setValidity ( 'editable' , true ) ;
}
if ( inputFormatter ) {
locals . $model = modelValue ;
return inputFormatter ( originalScope , locals ) ;
}
//it might happen that we don't have enough info to properly render input value
//we need to check for this situation and simply return model value if we can't apply custom formatting
locals [ parserResult . itemName ] = modelValue ;
candidateViewValue = parserResult . viewMapper ( originalScope , locals ) ;
locals [ parserResult . itemName ] = undefined ;
emptyViewValue = parserResult . viewMapper ( originalScope , locals ) ;
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue ;
} ) ;
} ;
2019-03-29 22:00:08 +01:00
function extractOptions ( ngModelCtrl ) {
var ngModelOptions ;
if ( angular . version . minor < 6 ) { // in angular < 1.6 $options could be missing
// guarantee a value
ngModelOptions = ngModelCtrl . $options || { } ;
// mimic 1.6+ api
ngModelOptions . getOption = function ( key ) {
return ngModelOptions [ key ] ;
} ;
} else { // in angular >=1.6 $options is always present
ngModelOptions = ngModelCtrl . $options ;
}
return ngModelOptions ;
}
2016-05-16 13:33:49 +02:00
} ] )
. directive ( 'uibTypeahead' , function ( ) {
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
controller : 'UibTypeaheadController' ,
2019-03-29 22:00:08 +01:00
require : [ 'ngModel' , 'uibTypeahead' ] ,
2016-05-16 13:33:49 +02:00
link : function ( originalScope , element , attrs , ctrls ) {
2019-03-29 22:00:08 +01:00
ctrls [ 1 ] . init ( ctrls [ 0 ] ) ;
2016-05-16 13:33:49 +02:00
}
} ;
} )
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
. directive ( 'uibTypeaheadPopup' , [ '$$debounce' , function ( $$debounce ) {
return {
scope : {
matches : '=' ,
query : '=' ,
active : '=' ,
position : '&' ,
moveInProgress : '=' ,
select : '&' ,
assignIsOpen : '&' ,
debounce : '&'
} ,
replace : true ,
templateUrl : function ( element , attrs ) {
return attrs . popupTemplateUrl || 'uib/template/typeahead/typeahead-popup.html' ;
} ,
link : function ( scope , element , attrs ) {
2014-08-10 11:37:05 +02:00
scope . templateUrl = attrs . templateUrl ;
2016-05-16 13:33:49 +02:00
scope . isOpen = function ( ) {
var isDropdownOpen = scope . matches . length > 0 ;
scope . assignIsOpen ( { isOpen : isDropdownOpen } ) ;
return isDropdownOpen ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
scope . isActive = function ( matchIdx ) {
return scope . active === matchIdx ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
scope . selectActive = function ( matchIdx ) {
2014-08-10 11:37:05 +02:00
scope . active = matchIdx ;
} ;
2016-05-16 13:33:49 +02:00
scope . selectMatch = function ( activeIdx , evt ) {
var debounce = scope . debounce ( ) ;
if ( angular . isNumber ( debounce ) || angular . isObject ( debounce ) ) {
$$debounce ( function ( ) {
scope . select ( { activeIdx : activeIdx , evt : evt } ) ;
} , angular . isNumber ( debounce ) ? debounce : debounce [ 'default' ] ) ;
} else {
scope . select ( { activeIdx : activeIdx , evt : evt } ) ;
}
2014-08-10 11:37:05 +02:00
} ;
}
} ;
2016-05-16 13:33:49 +02:00
} ] )
2014-08-10 11:37:05 +02:00
2016-05-16 13:33:49 +02:00
. directive ( 'uibTypeaheadMatch' , [ '$templateRequest' , '$compile' , '$parse' , function ( $templateRequest , $compile , $parse ) {
2014-08-10 11:37:05 +02:00
return {
2016-05-16 13:33:49 +02:00
scope : {
index : '=' ,
match : '=' ,
query : '='
2014-08-10 11:37:05 +02:00
} ,
2016-05-16 13:33:49 +02:00
link : function ( scope , element , attrs ) {
var tplUrl = $parse ( attrs . templateUrl ) ( scope . $parent ) || 'uib/template/typeahead/typeahead-match.html' ;
$templateRequest ( tplUrl ) . then ( function ( tplContent ) {
var tplEl = angular . element ( tplContent . trim ( ) ) ;
element . replaceWith ( tplEl ) ;
$compile ( tplEl ) ( scope ) ;
2014-08-10 11:37:05 +02:00
} ) ;
}
} ;
} ] )
2016-05-16 13:33:49 +02:00
. filter ( 'uibTypeaheadHighlight' , [ '$sce' , '$injector' , '$log' , function ( $sce , $injector , $log ) {
var isSanitizePresent ;
isSanitizePresent = $injector . has ( '$sanitize' ) ;
2014-08-10 11:37:05 +02:00
function escapeRegexp ( queryToEscape ) {
2016-05-16 13:33:49 +02:00
// Regex: capture the whole query string and replace it with the string that will be used to match
// the results, for example if the capture is "a" the result will be \a
2014-08-10 11:37:05 +02:00
return queryToEscape . replace ( /([.?*+^$[\]\\(){}|-])/g , '\\$1' ) ;
}
2016-05-16 13:33:49 +02:00
function containsHtml ( matchItem ) {
return /<.*>/g . test ( matchItem ) ;
}
2014-08-10 11:37:05 +02:00
return function ( matchItem , query ) {
2016-05-16 13:33:49 +02:00
if ( ! isSanitizePresent && containsHtml ( matchItem ) ) {
$log . warn ( 'Unsafe use of typeahead please use ngSanitize' ) ; // Warn the user about the danger
}
matchItem = query ? ( '' + matchItem ) . replace ( new RegExp ( escapeRegexp ( query ) , 'gi' ) , '<strong>$&</strong>' ) : matchItem ; // Replaces the capture string with a the same string inside of a "strong" tag
if ( ! isSanitizePresent ) {
matchItem = $sce . trustAsHtml ( matchItem ) ; // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
}
return matchItem ;
2014-08-10 11:37:05 +02:00
} ;
2016-05-16 13:33:49 +02:00
} ] ) ;
angular . module ( 'ui.bootstrap.carousel' ) . run ( function ( ) { ! angular . $$csp ( ) . noInlineStyle && ! angular . $$uibCarouselCss && angular . element ( document ) . find ( 'head' ) . prepend ( '<style type="text/css">.ng-animate.item:not(.left):not(.right){-webkit-transition:0s ease-in-out left;transition:0s ease-in-out left}</style>' ) ; angular . $$uibCarouselCss = true ; } ) ;
angular . module ( 'ui.bootstrap.datepicker' ) . run ( function ( ) { ! angular . $$csp ( ) . noInlineStyle && ! angular . $$uibDatepickerCss && angular . element ( document ) . find ( 'head' ) . prepend ( '<style type="text/css">.uib-datepicker .uib-title{width:100%;}.uib-day button,.uib-month button,.uib-year button{min-width:100%;}.uib-left,.uib-right{width:100%}</style>' ) ; angular . $$uibDatepickerCss = true ; } ) ;
angular . module ( 'ui.bootstrap.position' ) . run ( function ( ) { ! angular . $$csp ( ) . noInlineStyle && ! angular . $$uibPositionCss && angular . element ( document ) . find ( 'head' ) . prepend ( '<style type="text/css">.uib-position-measure{display:block !important;visibility:hidden !important;position:absolute !important;top:-9999px !important;left:-9999px !important;}.uib-position-scrollbar-measure{position:absolute !important;top:-9999px !important;width:50px !important;height:50px !important;overflow:scroll !important;}.uib-position-body-scrollbar-measure{overflow:scroll !important;}</style>' ) ; angular . $$uibPositionCss = true ; } ) ;
angular . module ( 'ui.bootstrap.datepickerPopup' ) . run ( function ( ) { ! angular . $$csp ( ) . noInlineStyle && ! angular . $$uibDatepickerpopupCss && angular . element ( document ) . find ( 'head' ) . prepend ( '<style type="text/css">.uib-datepicker-popup.dropdown-menu{display:block;float:none;margin:0;}.uib-button-bar{padding:10px 9px 2px;}</style>' ) ; angular . $$uibDatepickerpopupCss = true ; } ) ;
angular . module ( 'ui.bootstrap.tooltip' ) . run ( function ( ) { ! angular . $$csp ( ) . noInlineStyle && ! angular . $$uibTooltipCss && angular . element ( document ) . find ( 'head' ) . prepend ( '<style type="text/css">[uib-tooltip-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-bottom > .tooltip-arrow,[uib-popover-popup].popover.top-left > .arrow,[uib-popover-popup].popover.top-right > .arrow,[uib-popover-popup].popover.bottom-left > .arrow,[uib-popover-popup].popover.bottom-right > .arrow,[uib-popover-popup].popover.left-top > .arrow,[uib-popover-popup].popover.left-bottom > .arrow,[uib-popover-popup].popover.right-top > .arrow,[uib-popover-popup].popover.right-bottom > .arrow,[uib-popover-html-popup].popover.top-left > .arrow,[uib-popover-html-popup].popover.top-right > .arrow,[uib-popover-html-popup].popover.bottom-left > .arrow,[uib-popover-html-popup].popover.bottom-right > .arrow,[uib-popover-html-popup].popover.left-top > .arrow,[uib-popover-html-popup].popover.left-bottom > .arrow,[uib-popover-html-popup].popover.right-top > .arrow,[uib-popover-html-popup].popover.right-bottom > .arrow,[uib-popover-template-popup].popover.top-left > .arrow,[uib-popover-template-popup].popover.top-right > .arrow,[uib-popover-template-popup].popover.bottom-left > .arrow,[uib-popover-template-popup].popover.bottom-right > .arrow,[uib-popover-template-popup].popover.left-top > .arrow,[uib-popover-template-popup].popover.left-bottom > .arrow,[uib-popover-template-popup].popover.right-top > .arrow,[uib-popover-template-popup].popover.right-bottom > .arrow{top:auto;bottom:auto;left:auto;right:auto;margin:0;}[uib-popover-popup].popover,[uib-popover-html-popup].popover,[uib-popover-template-popup].popover{display:block !important;}</style>' ) ; angular . $$uibTooltipCss = true ; } ) ;
angular . module ( 'ui.bootstrap.timepicker' ) . run ( function ( ) { ! angular . $$csp ( ) . noInlineStyle && ! angular . $$uibTimepickerCss && angular . element ( document ) . find ( 'head' ) . prepend ( '<style type="text/css">.uib-time input{width:50px;}</style>' ) ; angular . $$uibTimepickerCss = true ; } ) ;
angular . module ( 'ui.bootstrap.typeahead' ) . run ( function ( ) { ! angular . $$csp ( ) . noInlineStyle && ! angular . $$uibTypeaheadCss && angular . element ( document ) . find ( 'head' ) . prepend ( '<style type="text/css">[uib-typeahead-popup].dropdown-menu{display:block;}</style>' ) ; angular . $$uibTypeaheadCss = true ; } ) ;