|
|
|
/*
|
|
|
|
* angular-ui-bootstrap
|
|
|
|
* http://angular-ui.github.io/bootstrap/
|
|
|
|
|
|
|
|
* Version: 0.12.0 - 2014-11-16
|
|
|
|
* License: MIT
|
|
|
|
*/
|
|
|
|
angular.module("ui.bootstrap", ["ui.bootstrap.tpls","ui.bootstrap.dropdown","ui.bootstrap.tooltip","ui.bootstrap.position","ui.bootstrap.bindHtml","ui.bootstrap.modal","ui.bootstrap.transition"]);
|
|
|
|
angular.module("ui.bootstrap.tpls", ["template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/modal/backdrop.html","template/modal/window.html"]);
|
|
|
|
angular.module('ui.bootstrap.dropdown', [])
|
|
|
|
|
|
|
|
.constant('dropdownConfig', {
|
|
|
|
openClass: 'open'
|
|
|
|
})
|
|
|
|
|
|
|
|
.service('dropdownService', ['$document', function($document) {
|
|
|
|
var openScope = null;
|
|
|
|
|
|
|
|
this.open = function( dropdownScope ) {
|
|
|
|
if ( !openScope ) {
|
|
|
|
$document.bind('click', closeDropdown);
|
|
|
|
$document.bind('keydown', escapeKeyBind);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( openScope && openScope !== dropdownScope ) {
|
|
|
|
openScope.isOpen = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
openScope = dropdownScope;
|
|
|
|
};
|
|
|
|
|
|
|
|
this.close = function( dropdownScope ) {
|
|
|
|
if ( openScope === dropdownScope ) {
|
|
|
|
openScope = null;
|
|
|
|
$document.unbind('click', closeDropdown);
|
|
|
|
$document.unbind('keydown', escapeKeyBind);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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.
|
|
|
|
if (!openScope) { return; }
|
|
|
|
|
|
|
|
var toggleElement = openScope.getToggleElement();
|
|
|
|
if ( evt && toggleElement && toggleElement[0].contains(evt.target) ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
openScope.$apply(function() {
|
|
|
|
openScope.isOpen = false;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
var escapeKeyBind = function( evt ) {
|
|
|
|
if ( evt.which === 27 ) {
|
|
|
|
openScope.focusToggleElement();
|
|
|
|
closeDropdown();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}])
|
|
|
|
|
|
|
|
.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) {
|
|
|
|
var self = this,
|
|
|
|
scope = $scope.$new(), // create a child scope so we are not polluting original one
|
|
|
|
openClass = dropdownConfig.openClass,
|
|
|
|
getIsOpen,
|
|
|
|
setIsOpen = angular.noop,
|
|
|
|
toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop;
|
|
|
|
|
|
|
|
this.init = function( element ) {
|
|
|
|
self.$element = element;
|
|
|
|
|
|
|
|
if ( $attrs.isOpen ) {
|
|
|
|
getIsOpen = $parse($attrs.isOpen);
|
|
|
|
setIsOpen = getIsOpen.assign;
|
|
|
|
|
|
|
|
$scope.$watch(getIsOpen, function(value) {
|
|
|
|
scope.isOpen = !!value;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
this.toggle = function( open ) {
|
|
|
|
return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Allow other directives to watch status
|
|
|
|
this.isOpen = function() {
|
|
|
|
return scope.isOpen;
|
|
|
|
};
|
|
|
|
|
|
|
|
scope.getToggleElement = function() {
|
|
|
|
return self.toggleElement;
|
|
|
|
};
|
|
|
|
|
|
|
|
scope.focusToggleElement = function() {
|
|
|
|
if ( self.toggleElement ) {
|
|
|
|
self.toggleElement[0].focus();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
scope.$watch('isOpen', function( isOpen, wasOpen ) {
|
|
|
|
$animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass);
|
|
|
|
|
|
|
|
if ( isOpen ) {
|
|
|
|
scope.focusToggleElement();
|
|
|
|
dropdownService.open( scope );
|
|
|
|
} else {
|
|
|
|
dropdownService.close( scope );
|
|
|
|
}
|
|
|
|
|
|
|
|
setIsOpen($scope, isOpen);
|
|
|
|
if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
|
|
|
|
toggleInvoker($scope, { open: !!isOpen });
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
$scope.$on('$locationChangeSuccess', function() {
|
|
|
|
scope.isOpen = false;
|
|
|
|
});
|
|
|
|
|
|
|
|
$scope.$on('$destroy', function() {
|
|
|
|
scope.$destroy();
|
|
|
|
});
|
|
|
|
}])
|
|
|
|
|
|
|
|
.directive('dropdown', function() {
|
|
|
|
return {
|
|
|
|
controller: 'DropdownController',
|
|
|
|
link: function(scope, element, attrs, dropdownCtrl) {
|
|
|
|
dropdownCtrl.init( element );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})
|
|
|
|
|
|
|
|
.directive('dropdownToggle', function() {
|
|
|
|
return {
|
|
|
|
require: '?^dropdown',
|
|
|
|
link: function(scope, element, attrs, dropdownCtrl) {
|
|
|
|
if ( !dropdownCtrl ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dropdownCtrl.toggleElement = element;
|
|
|
|
|
|
|
|
var toggleDropdown = function(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
if ( !element.hasClass('disabled') && !attrs.disabled ) {
|
|
|
|
scope.$apply(function() {
|
|
|
|
dropdownCtrl.toggle();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
element.bind('click', toggleDropdown);
|
|
|
|
|
|
|
|
// WAI-ARIA
|
|
|
|
element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
|
|
|
|
scope.$watch(dropdownCtrl.isOpen, function( isOpen ) {
|
|
|
|
element.attr('aria-expanded', !!isOpen);
|
|
|
|
});
|
|
|
|
|
|
|
|
scope.$on('$destroy', function() {
|
|
|
|
element.unbind('click', toggleDropdown);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] )
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The $tooltip service creates tooltip- and popover-like directives as well as
|
|
|
|
* houses global options for them.
|
|
|
|
*/
|
|
|
|
.provider( '$tooltip', function () {
|
|
|
|
// The default options tooltip and popover.
|
|
|
|
var defaultOptions = {
|
|
|
|
placement: 'top',
|
|
|
|
animation: true,
|
|
|
|
popupDelay: 0
|
|
|
|
};
|
|
|
|
|
|
|
|
// Default hide triggers for each show trigger
|
|
|
|
var triggerMap = {
|
|
|
|
'mouseenter': 'mouseleave',
|
|
|
|
'click': 'click',
|
|
|
|
'focus': 'blur'
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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' } );
|
|
|
|
* });
|
|
|
|
*/
|
|
|
|
this.options = function( value ) {
|
|
|
|
angular.extend( globalOptions, value );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This allows you to extend the set of trigger mappings available. E.g.:
|
|
|
|
*
|
|
|
|
* $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
|
|
|
|
*/
|
|
|
|
this.setTriggers = function setTriggers ( triggers ) {
|
|
|
|
angular.extend( triggerMap, triggers );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This is a helper function for translating camel-case to snake-case.
|
|
|
|
*/
|
|
|
|
function snake_case(name){
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
this.$get = [ '$window', '$compile', '$timeout', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $document, $position, $interpolate ) {
|
|
|
|
return function $tooltip ( type, prefix, defaultTriggerShow ) {
|
|
|
|
var options = angular.extend( {}, defaultOptions, globalOptions );
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
function getTriggers ( trigger ) {
|
|
|
|
var show = trigger || options.trigger || defaultTriggerShow;
|
|
|
|
var hide = triggerMap[show] || show;
|
|
|
|
return {
|
|
|
|
show: show,
|
|
|
|
hide: hide
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
var directiveName = snake_case( type );
|
|
|
|
|
|
|
|
var startSym = $interpolate.startSymbol();
|
|
|
|
var endSym = $interpolate.endSymbol();
|
|
|
|
var template =
|
|
|
|
'<div '+ directiveName +'-popup '+
|
|
|
|
'title="'+startSym+'title'+endSym+'" '+
|
|
|
|
'content="'+startSym+'content'+endSym+'" '+
|
|
|
|
'placement="'+startSym+'placement'+endSym+'" '+
|
|
|
|
'animation="animation" '+
|
|
|
|
'is-open="isOpen"'+
|
|
|
|
'>'+
|
|
|
|
'</div>';
|
|
|
|
|
|
|
|
return {
|
|
|
|
restrict: 'EA',
|
|
|
|
compile: function (tElem, tAttrs) {
|
|
|
|
var tooltipLinker = $compile( template );
|
|
|
|
|
|
|
|
return function link ( scope, element, attrs ) {
|
|
|
|
var tooltip;
|
|
|
|
var tooltipLinkedScope;
|
|
|
|
var transitionTimeout;
|
|
|
|
var popupTimeout;
|
|
|
|
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 positionTooltip = function () {
|
|
|
|
|
|
|
|
var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
|
|
|
|
ttPosition.top += 'px';
|
|
|
|
ttPosition.left += 'px';
|
|
|
|
|
|
|
|
// Now set the calculated positioning.
|
|
|
|
tooltip.css( ttPosition );
|
|
|
|
};
|
|
|
|
|
|
|
|
// By default, the tooltip is not open.
|
|
|
|
// TODO add ability to start tooltip opened
|
|
|
|
ttScope.isOpen = false;
|
|
|
|
|
|
|
|
function toggleTooltipBind () {
|
|
|
|
if ( ! ttScope.isOpen ) {
|
|
|
|
showTooltipBind();
|
|
|
|
} else {
|
|
|
|
hideTooltipBind();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Show the tooltip with delay if specified, otherwise show it immediately
|
|
|
|
function showTooltipBind() {
|
|
|
|
if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
prepareTooltip();
|
|
|
|
|
|
|
|
if ( ttScope.popupDelay ) {
|
|
|
|
// Do nothing if the tooltip was already scheduled to pop-up.
|
|
|
|
// This happens if show is triggered multiple times before any hide is triggered.
|
|
|
|
if (!popupTimeout) {
|
|
|
|
popupTimeout = $timeout( show, ttScope.popupDelay, false );
|
|
|
|
popupTimeout.then(function(reposition){reposition();});
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
show()();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function hideTooltipBind () {
|
|
|
|
scope.$apply(function () {
|
|
|
|
hide();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Show the tooltip popup element.
|
|
|
|
function show() {
|
|
|
|
|
|
|
|
popupTimeout = null;
|
|
|
|
|
|
|
|
// If there is a pending remove transition, we must cancel it, lest the
|
|
|
|
// tooltip be mysteriously removed.
|
|
|
|
if ( transitionTimeout ) {
|
|
|
|
$timeout.cancel( transitionTimeout );
|
|
|
|
transitionTimeout = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't show empty tooltips.
|
|
|
|
if ( ! ttScope.content ) {
|
|
|
|
return angular.noop;
|
|
|
|
}
|
|
|
|
|
|
|
|
createTooltip();
|
|
|
|
|
|
|
|
// Set the initial positioning.
|
|
|
|
tooltip.css({ top: 0, left: 0, display: 'block' });
|
|
|
|
|
|
|
|
// Now we add it to the DOM because need some info about it. But it's not
|
|
|
|
// visible yet anyway.
|
|
|
|
if ( appendToBody ) {
|
|
|
|
$document.find( 'body' ).append( tooltip );
|
|
|
|
} else {
|
|
|
|
element.after( tooltip );
|
|
|
|
}
|
|
|
|
|
|
|
|
positionTooltip();
|
|
|
|
|
|
|
|
// And show the tooltip.
|
|
|
|
ttScope.isOpen = true;
|
|
|
|
ttScope.$digest(); // digest required as $apply is not called
|
|
|
|
|
|
|
|
// Return positioning function as promise callback for correct
|
|
|
|
// positioning after draw.
|
|
|
|
return positionTooltip;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hide the tooltip popup element.
|
|
|
|
function hide() {
|
|
|
|
// First things first: we don't show it anymore.
|
|
|
|
ttScope.isOpen = false;
|
|
|
|
|
|
|
|
//if tooltip is going to be shown after delay, we must cancel this
|
|
|
|
$timeout.cancel( popupTimeout );
|
|
|
|
popupTimeout = null;
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
if ( ttScope.animation ) {
|
|
|
|
if (!transitionTimeout) {
|
|
|
|
transitionTimeout = $timeout(removeTooltip, 500);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
removeTooltip();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function createTooltip() {
|
|
|
|
// There can only be one tooltip element per directive shown at once.
|
|
|
|
if (tooltip) {
|
|
|
|
removeTooltip();
|
|
|
|
}
|
|
|
|
tooltipLinkedScope = ttScope.$new();
|
|
|
|
tooltip = tooltipLinker(tooltipLinkedScope, angular.noop);
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeTooltip() {
|
|
|
|
transitionTimeout = null;
|
|
|
|
if (tooltip) {
|
|
|
|
tooltip.remove();
|
|
|
|
tooltip = null;
|
|
|
|
}
|
|
|
|
if (tooltipLinkedScope) {
|
|
|
|
tooltipLinkedScope.$destroy();
|
|
|
|
tooltipLinkedScope = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function prepareTooltip() {
|
|
|
|
prepPlacement();
|
|
|
|
prepPopupDelay();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Observe the relevant attributes.
|
|
|
|
*/
|
|
|
|
attrs.$observe( type, function ( val ) {
|
|
|
|
ttScope.content = val;
|
|
|
|
|
|
|
|
if (!val && ttScope.isOpen ) {
|
|
|
|
hide();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
attrs.$observe( prefix+'Title', function ( val ) {
|
|
|
|
ttScope.title = val;
|
|
|
|
});
|
|
|
|
|
|
|
|
function prepPlacement() {
|
|
|
|
var val = attrs[ prefix + 'Placement' ];
|
|
|
|
ttScope.placement = angular.isDefined( val ) ? val : options.placement;
|
|
|
|
}
|
|
|
|
|
|
|
|
function prepPopupDelay() {
|
|
|
|
var val = attrs[ prefix + 'PopupDelay' ];
|
|
|
|
var delay = parseInt( val, 10 );
|
|
|
|
ttScope.popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
|
|
|
|
}
|
|
|
|
|
|
|
|
var unregisterTriggers = function () {
|
|
|
|
element.unbind(triggers.show, showTooltipBind);
|
|
|
|
element.unbind(triggers.hide, hideTooltipBind);
|
|
|
|
};
|
|
|
|
|
|
|
|
function prepTriggers() {
|
|
|
|
var val = attrs[ prefix + 'Trigger' ];
|
|
|
|
unregisterTriggers();
|
|
|
|
|
|
|
|
triggers = getTriggers( val );
|
|
|
|
|
|
|
|
if ( triggers.show === triggers.hide ) {
|
|
|
|
element.bind( triggers.show, toggleTooltipBind );
|
|
|
|
} else {
|
|
|
|
element.bind( triggers.show, showTooltipBind );
|
|
|
|
element.bind( triggers.hide, hideTooltipBind );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
prepTriggers();
|
|
|
|
|
|
|
|
var animation = scope.$eval(attrs[prefix + 'Animation']);
|
|
|
|
ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
|
|
|
|
|
|
|
|
var appendToBodyVal = scope.$eval(attrs[prefix + 'AppendToBody']);
|
|
|
|
appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
|
|
|
|
|
|
|
|
// if a tooltip is attached to <body> we need to remove it on
|
|
|
|
// location change as its parent scope will probably not be destroyed
|
|
|
|
// by the change.
|
|
|
|
if ( appendToBody ) {
|
|
|
|
scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
|
|
|
|
if ( ttScope.isOpen ) {
|
|
|
|
hide();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure tooltip is destroyed and removed.
|
|
|
|
scope.$on('$destroy', function onDestroyTooltip() {
|
|
|
|
$timeout.cancel( transitionTimeout );
|
|
|
|
$timeout.cancel( popupTimeout );
|
|
|
|
unregisterTriggers();
|
|
|
|
removeTooltip();
|
|
|
|
ttScope = null;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}];
|
|
|
|
})
|
|
|
|
|
|
|
|
.directive( 'tooltipPopup', function () {
|
|
|
|
return {
|
|
|
|
restrict: 'EA',
|
|
|
|
replace: true,
|
|
|
|
scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
|
|
|
|
templateUrl: 'template/tooltip/tooltip-popup.html'
|
|
|
|
};
|
|
|
|
})
|
|
|
|
|
|
|
|
.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
|
|
|
|
return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
|
|
|
|
}])
|
|
|
|
|
|
|
|
.directive( 'tooltipHtmlUnsafePopup', function () {
|
|
|
|
return {
|
|
|
|
restrict: 'EA',
|
|
|
|
replace: true,
|
|
|
|
scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
|
|
|
|
templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
|
|
|
|
};
|
|
|
|
})
|
|
|
|
|
|
|
|
.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
|
|
|
|
return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
|
|
|
|
}]);
|
|
|
|
|
|
|
|
angular.module('ui.bootstrap.position', [])
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A set of utility methods that can be use to retrieve position of DOM elements.
|
|
|
|
* It is meant to be used where we need to absolute-position DOM elements in
|
|
|
|
* relation to other, existing elements (this is the case for tooltips, popovers,
|
|
|
|
* typeahead suggestions etc.).
|
|
|
|
*/
|
|
|
|
.factory('$position', ['$document', '$window', function ($document, $window) {
|
|
|
|
|
|
|
|
function getStyle(el, cssprop) {
|
|
|
|
if (el.currentStyle) { //IE
|
|
|
|
return el.currentStyle[cssprop];
|
|
|
|
} else if ($window.getComputedStyle) {
|
|
|
|
return $window.getComputedStyle(el)[cssprop];
|
|
|
|
}
|
|
|
|
// finally try and get inline style
|
|
|
|
return el.style[cssprop];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if a given element is statically positioned
|
|
|
|
* @param element - raw DOM element
|
|
|
|
*/
|
|
|
|
function isStaticPositioned(element) {
|
|
|
|
return (getStyle(element, 'position') || 'static' ) === 'static';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* returns the closest, non-statically positioned parentOffset of a given element
|
|
|
|
* @param element
|
|
|
|
*/
|
|
|
|
var parentOffsetEl = function (element) {
|
|
|
|
var docDomEl = $document[0];
|
|
|
|
var offsetParent = element.offsetParent || docDomEl;
|
|
|
|
while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
|
|
|
|
offsetParent = offsetParent.offsetParent;
|
|
|
|
}
|
|
|
|
return offsetParent || docDomEl;
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
/**
|
|
|
|
* Provides read-only equivalent of jQuery's position function:
|
|
|
|
* http://api.jquery.com/position/
|
|
|
|
*/
|
|
|
|
position: function (element) {
|
|
|
|
var elBCR = this.offset(element);
|
|
|
|
var offsetParentBCR = { top: 0, left: 0 };
|
|
|
|
var offsetParentEl = parentOffsetEl(element[0]);
|
|
|
|
if (offsetParentEl != $document[0]) {
|
|
|
|
offsetParentBCR = this.offset(angular.element(offsetParentEl));
|
|
|
|
offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
|
|
|
|
offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
|
|
|
|
}
|
|
|
|
|
|
|
|
var boundingClientRect = element[0].getBoundingClientRect();
|
|
|
|
return {
|
|
|
|
width: boundingClientRect.width || element.prop('offsetWidth'),
|
|
|
|
height: boundingClientRect.height || element.prop('offsetHeight'),
|
|
|
|
top: elBCR.top - offsetParentBCR.top,
|
|
|
|
left: elBCR.left - offsetParentBCR.left
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Provides read-only equivalent of jQuery's offset function:
|
|
|
|
* http://api.jquery.com/offset/
|
|
|
|
*/
|
|
|
|
offset: function (element) {
|
|
|
|
var boundingClientRect = element[0].getBoundingClientRect();
|
|
|
|
return {
|
|
|
|
width: boundingClientRect.width || element.prop('offsetWidth'),
|
|
|
|
height: boundingClientRect.height || element.prop('offsetHeight'),
|
|
|
|
top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
|
|
|
|
left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Provides coordinates for the targetEl in relation to hostEl
|
|
|
|
*/
|
|
|
|
positionElements: function (hostEl, targetEl, positionStr, appendToBody) {
|
|
|
|
|
|
|
|
var positionStrParts = positionStr.split('-');
|
|
|
|
var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center';
|
|
|
|
|
|
|
|
var hostElPos,
|
|
|
|
targetElWidth,
|
|
|
|
targetElHeight,
|
|
|
|
targetElPos;
|
|
|
|
|
|
|
|
hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl);
|
|
|
|
|
|
|
|
targetElWidth = targetEl.prop('offsetWidth');
|
|
|
|
targetElHeight = targetEl.prop('offsetHeight');
|
|
|
|
|
|
|
|
var shiftWidth = {
|
|
|
|
center: function () {
|
|
|
|
return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2;
|
|
|
|
},
|
|
|
|
left: function () {
|
|
|
|
return hostElPos.left;
|
|
|
|
},
|
|
|
|
right: function () {
|
|
|
|
return hostElPos.left + hostElPos.width;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var shiftHeight = {
|
|
|
|
center: function () {
|
|
|
|
return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2;
|
|
|
|
},
|
|
|
|
top: function () {
|
|
|
|
return hostElPos.top;
|
|
|
|
},
|
|
|
|
bottom: function () {
|
|
|
|
return hostElPos.top + hostElPos.height;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
switch (pos0) {
|
|
|
|
case 'right':
|
|
|
|
targetElPos = {
|
|
|
|
top: shiftHeight[pos1](),
|
|
|
|
left: shiftWidth[pos0]()
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
case 'left':
|
|
|
|
targetElPos = {
|
|
|
|
top: shiftHeight[pos1](),
|
|
|
|
left: hostElPos.left - targetElWidth
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
case 'bottom':
|
|
|
|
targetElPos = {
|
|
|
|
top: shiftHeight[pos0](),
|
|
|
|
left: shiftWidth[pos1]()
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
targetElPos = {
|
|
|
|
top: hostElPos.top - targetElHeight,
|
|
|
|
left: shiftWidth[pos1]()
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return targetElPos;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}]);
|
|
|
|
|
|
|
|
angular.module('ui.bootstrap.bindHtml', [])
|
|
|
|
|
|
|
|
.directive('bindHtmlUnsafe', function () {
|
|
|
|
return function (scope, element, attr) {
|
|
|
|
element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe);
|
|
|
|
scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) {
|
|
|
|
element.html(value || '');
|
|
|
|
});
|
|
|
|
};
|
|
|
|
});
|
|
|
|
angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A helper, internal data structure that acts as a map but also allows getting / removing
|
|
|
|
* elements in the LIFO order
|
|
|
|
*/
|
|
|
|
.factory('$$stackedMap', function () {
|
|
|
|
return {
|
|
|
|
createNew: function () {
|
|
|
|
var stack = [];
|
|
|
|
|
|
|
|
return {
|
|
|
|
add: function (key, value) {
|
|
|
|
stack.push({
|
|
|
|
key: key,
|
|
|
|
value: value
|
|
|
|
});
|
|
|
|
},
|
|
|
|
get: function (key) {
|
|
|
|
for (var i = 0; i < stack.length; i++) {
|
|
|
|
if (key == stack[i].key) {
|
|
|
|
return stack[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
keys: function() {
|
|
|
|
var keys = [];
|
|
|
|
for (var i = 0; i < stack.length; i++) {
|
|
|
|
keys.push(stack[i].key);
|
|
|
|
}
|
|
|
|
return keys;
|
|
|
|
},
|
|
|
|
top: function () {
|
|
|
|
return stack[stack.length - 1];
|
|
|
|
},
|
|
|
|
remove: function (key) {
|
|
|
|
var idx = -1;
|
|
|
|
for (var i = 0; i < stack.length; i++) {
|
|
|
|
if (key == stack[i].key) {
|
|
|
|
idx = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return stack.splice(idx, 1)[0];
|
|
|
|
},
|
|
|
|
removeTop: function () {
|
|
|
|
return stack.splice(stack.length - 1, 1)[0];
|
|
|
|
},
|
|
|
|
length: function () {
|
|
|
|
return stack.length;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A helper directive for the $modal service. It creates a backdrop element.
|
|
|
|
*/
|
|
|
|
.directive('modalBackdrop', ['$timeout', function ($timeout) {
|
|
|
|
return {
|
|
|
|
restrict: 'EA',
|
|
|
|
replace: true,
|
|
|
|
templateUrl: 'template/modal/backdrop.html',
|
|
|
|
link: function (scope, element, attrs) {
|
|
|
|
scope.backdropClass = attrs.backdropClass || '';
|
|
|
|
|
|
|
|
scope.animate = false;
|
|
|
|
|
|
|
|
//trigger CSS transitions
|
|
|
|
$timeout(function () {
|
|
|
|
scope.animate = true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}])
|
|
|
|
|
|
|
|
.directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
|
|
|
|
return {
|
|
|
|
restrict: 'EA',
|
|
|
|
scope: {
|
|
|
|
index: '@',
|
|
|
|
animate: '='
|
|
|
|
},
|
|
|
|
replace: true,
|
|
|
|
transclude: true,
|
|
|
|
templateUrl: function(tElement, tAttrs) {
|
|
|
|
return tAttrs.templateUrl || 'template/modal/window.html';
|
|
|
|
},
|
|
|
|
link: function (scope, element, attrs) {
|
|
|
|
element.addClass(attrs.windowClass || '');
|
|
|
|
scope.size = attrs.size;
|
|
|
|
|
|
|
|
// moved from template to fix issue #2280
|
|
|
|
element.on('click', function(evt) {
|
|
|
|
scope.close(evt);
|
|
|
|
});
|
|
|
|
|
|
|
|
$modalStack.registerObserverCallback(function(hiddenBySingle) {
|
|
|
|
scope.hiddenBySingle = hiddenBySingle || false;
|
|
|
|
});
|
|
|
|
|
|
|
|
$timeout(function () {
|
|
|
|
// trigger CSS transitions
|
|
|
|
scope.animate = true;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 (!element[0].querySelectorAll('[autofocus]').length) {
|
|
|
|
element[0].focus();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
scope.close = function (evt) {
|
|
|
|
var modal = $modalStack.getTop();
|
|
|
|
if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) {
|
|
|
|
evt.preventDefault();
|
|
|
|
evt.stopPropagation();
|
|
|
|
$modalStack.dismiss(modal.key, 'backdrop click');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}])
|
|
|
|
|
|
|
|
.directive('modalTransclude', function () {
|
|
|
|
return {
|
|
|
|
link: function($scope, $element, $attrs, controller, $transclude) {
|
|
|
|
$transclude($scope.$parent, function(clone) {
|
|
|
|
$element.empty();
|
|
|
|
$element.append(clone);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})
|
|
|
|
|
|
|
|
.factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap',
|
|
|
|
function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) {
|
|
|
|
|
|
|
|
var OPENED_MODAL_CLASS = 'modal-open';
|
|
|
|
|
|
|
|
var backdropDomEl, backdropScope;
|
|
|
|
var openedWindows = $$stackedMap.createNew();
|
|
|
|
var $modalStack = {};
|
|
|
|
var observerCallbacks = [];
|
|
|
|
|
|
|
|
function notifyObservers() {
|
|
|
|
angular.forEach(observerCallbacks, function(callback, index) {
|
|
|
|
var hasSingleParent = false,
|
|
|
|
opened = openedWindows.keys();
|
|
|
|
|
|
|
|
for (var i = 0; i < opened.length; i++) {
|
|
|
|
var item = openedWindows.get(opened[i]).value;
|
|
|
|
|
|
|
|
if (item.backdrop == 'single' && item.index > index) {
|
|
|
|
hasSingleParent = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(hasSingleParent);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return topBackdropIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
$rootScope.$watch(backdropIndex, function(newBackdropIndex){
|
|
|
|
if (backdropScope) {
|
|
|
|
backdropScope.index = newBackdropIndex;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
function removeModalWindow(modalInstance) {
|
|
|
|
var body = $document.find('body').eq(0);
|
|
|
|
var modalWindow = openedWindows.get(modalInstance).value;
|
|
|
|
|
|
|
|
//clean up the stack
|
|
|
|
openedWindows.remove(modalInstance);
|
|
|
|
|
|
|
|
//clean up the observer
|
|
|
|
observerCallbacks.splice(modalWindow.index, 1);
|
|
|
|
|
|
|
|
//remove window DOM element
|
|
|
|
removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 0, function() {
|
|
|
|
modalWindow.modalScope.$destroy();
|
|
|
|
body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0);
|
|
|
|
checkRemoveBackdrop();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function checkRemoveBackdrop() {
|
|
|
|
//remove backdrop if no longer needed
|
|
|
|
if (backdropDomEl && backdropIndex() == -1) {
|
|
|
|
var backdropScopeRef = backdropScope;
|
|
|
|
removeAfterAnimate(backdropDomEl, backdropScope, 0, function () {
|
|
|
|
backdropScopeRef.$destroy();
|
|
|
|
backdropScopeRef = null;
|
|
|
|
});
|
|
|
|
backdropDomEl = undefined;
|
|
|
|
backdropScope = undefined;
|
|
|
|
} else {
|
|
|
|
notifyObservers();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeAfterAnimate(domEl, scope, emulateTime, done) {
|
|
|
|
// Closing animation
|
|
|
|
scope.animate = false;
|
|
|
|
|
|
|
|
var transitionEndEventName = $transition.transitionEndEventName;
|
|
|
|
if (transitionEndEventName) {
|
|
|
|
// transition out
|
|
|
|
var timeout = $timeout(afterAnimating, emulateTime);
|
|
|
|
|
|
|
|
domEl.bind(transitionEndEventName, function () {
|
|
|
|
$timeout.cancel(timeout);
|
|
|
|
afterAnimating();
|
|
|
|
scope.$apply();
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// Ensure this call is async
|
|
|
|
$timeout(afterAnimating);
|
|
|
|
}
|
|
|
|
|
|
|
|
function afterAnimating() {
|
|
|
|
if (afterAnimating.done) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
afterAnimating.done = true;
|
|
|
|
|
|
|
|
domEl.remove();
|
|
|
|
if (done) {
|
|
|
|
done();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$document.bind('keydown', function (evt) {
|
|
|
|
var modal;
|
|
|
|
|
|
|
|
if (evt.which === 27) {
|
|
|
|
modal = openedWindows.top();
|
|
|
|
if (modal && modal.value.keyboard) {
|
|
|
|
evt.preventDefault();
|
|
|
|
$rootScope.$apply(function () {
|
|
|
|
$modalStack.dismiss(modal.key, 'escape key press');
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
$modalStack.registerObserverCallback = function(callback){
|
|
|
|
observerCallbacks.push(callback);
|
|
|
|
};
|
|
|
|
|
|
|
|
$modalStack.open = function (modalInstance, modal) {
|
|
|
|
openedWindows.add(modalInstance, {
|
|
|
|
deferred: modal.deferred,
|
|
|
|
modalScope: modal.scope,
|
|
|
|
backdrop: modal.backdrop,
|
|
|
|
keyboard: modal.keyboard
|
|
|
|
});
|
|
|
|
|
|
|
|
var body = $document.find('body').eq(0),
|
|
|
|
currBackdropIndex = backdropIndex();
|
|
|
|
|
|
|
|
if (currBackdropIndex >= 0 && !backdropDomEl) {
|
|
|
|
backdropScope = $rootScope.$new(true);
|
|
|
|
backdropScope.index = currBackdropIndex;
|
|
|
|
var angularBackgroundDomEl = angular.element('<div modal-backdrop></div>');
|
|
|
|
angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass);
|
|
|
|
backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope);
|
|
|
|
body.append(backdropDomEl);
|
|
|
|
}
|
|
|
|
|
|
|
|
var angularDomEl = angular.element('<div modal-window></div>'),
|
|
|
|
index = openedWindows.length() - 1;
|
|
|
|
|
|
|
|
angularDomEl.attr({
|
|
|
|
'template-url': modal.windowTemplateUrl,
|
|
|
|
'window-class': modal.windowClass,
|
|
|
|
'size': modal.size,
|
|
|
|
'index': index,
|
|
|
|
'animate': 'animate'
|
|
|
|
}).html(modal.content);
|
|
|
|
|
|
|
|
var modalDomEl = $compile(angularDomEl)(modal.scope);
|
|
|
|
openedWindows.top().value.modalDomEl = modalDomEl;
|
|
|
|
openedWindows.top().value.index = index;
|
|
|
|
body.append(modalDomEl);
|
|
|
|
body.addClass(OPENED_MODAL_CLASS);
|
|
|
|
|
|
|
|
notifyObservers();
|
|
|
|
};
|
|
|
|
|
|
|
|
$modalStack.close = function (modalInstance, result) {
|
|
|
|
var modalWindow = openedWindows.get(modalInstance);
|
|
|
|
if (modalWindow) {
|
|
|
|
modalWindow.value.deferred.resolve(result);
|
|
|
|
removeModalWindow(modalInstance);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
$modalStack.dismiss = function (modalInstance, reason) {
|
|
|
|
var modalWindow = openedWindows.get(modalInstance);
|
|
|
|
if (modalWindow) {
|
|
|
|
modalWindow.value.deferred.reject(reason);
|
|
|
|
removeModalWindow(modalInstance);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
$modalStack.dismissAll = function (reason) {
|
|
|
|
var topModal = this.getTop();
|
|
|
|
while (topModal) {
|
|
|
|
this.dismiss(topModal.key, reason);
|
|
|
|
topModal = this.getTop();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
$modalStack.getTop = function () {
|
|
|
|
return openedWindows.top();
|
|
|
|
};
|
|
|
|
|
|
|
|
return $modalStack;
|
|
|
|
}])
|
|
|
|
|
|
|
|
.provider('$modal', function () {
|
|
|
|
|
|
|
|
var $modalProvider = {
|
|
|
|
options: {
|
|
|
|
backdrop: true, //can be also false or 'static'
|
|
|
|
keyboard: true
|
|
|
|
},
|
|
|
|
$get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack',
|
|
|
|
function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) {
|
|
|
|
|
|
|
|
var $modal = {};
|
|
|
|
|
|
|
|
function getTemplatePromise(options) {
|
|
|
|
return options.template ? $q.when(options.template) :
|
|
|
|
$http.get(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl,
|
|
|
|
{cache: $templateCache}).then(function (result) {
|
|
|
|
return result.data;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getResolvePromises(resolves) {
|
|
|
|
var promisesArr = [];
|
|
|
|
angular.forEach(resolves, function (value) {
|
|
|
|
if (angular.isFunction(value) || angular.isArray(value)) {
|
|
|
|
promisesArr.push($q.when($injector.invoke(value)));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return promisesArr;
|
|
|
|
}
|
|
|
|
|
|
|
|
$modal.open = function (modalOptions) {
|
|
|
|
|
|
|
|
var modalResultDeferred = $q.defer();
|
|
|
|
var modalOpenedDeferred = $q.defer();
|
|
|
|
|
|
|
|
//prepare an instance of a modal to be injected into controllers and returned to a caller
|
|
|
|
var modalInstance = {
|
|
|
|
result: modalResultDeferred.promise,
|
|
|
|
opened: modalOpenedDeferred.promise,
|
|
|
|
close: function (result) {
|
|
|
|
$modalStack.close(modalInstance, result);
|
|
|
|
},
|
|
|
|
dismiss: function (reason) {
|
|
|
|
$modalStack.dismiss(modalInstance, reason);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//merge and clean up options
|
|
|
|
modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
|
|
|
|
modalOptions.resolve = modalOptions.resolve || {};
|
|
|
|
|
|
|
|
//verify options
|
|
|
|
if (!modalOptions.template && !modalOptions.templateUrl) {
|
|
|
|
throw new Error('One of template or templateUrl options is required.');
|
|
|
|
}
|
|
|
|
|
|
|
|
var templateAndResolvePromise =
|
|
|
|
$q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
|
|
|
|
|
|
|
|
|
|
|
|
templateAndResolvePromise.then(function resolveSuccess(tplAndVars) {
|
|
|
|
|
|
|
|
var modalScope = (modalOptions.scope || $rootScope).$new();
|
|
|
|
modalScope.$close = modalInstance.close;
|
|
|
|
modalScope.$dismiss = modalInstance.dismiss;
|
|
|
|
|
|
|
|
var ctrlInstance, ctrlLocals = {};
|
|
|
|
var resolveIter = 1;
|
|
|
|
|
|
|
|
//controllers
|
|
|
|
if (modalOptions.controller) {
|
|
|
|
ctrlLocals.$scope = modalScope;
|
|
|
|
ctrlLocals.$modalInstance = modalInstance;
|
|
|
|
angular.forEach(modalOptions.resolve, function (value, key) {
|
|
|
|
ctrlLocals[key] = tplAndVars[resolveIter++];
|
|
|
|
});
|
|
|
|
|
|
|
|
ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
|
|
|
|
if (modalOptions.controllerAs) {
|
|
|
|
modalScope[modalOptions.controllerAs] = ctrlInstance;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$modalStack.open(modalInstance, {
|
|
|
|
scope: modalScope,
|
|
|
|
deferred: modalResultDeferred,
|
|
|
|
content: tplAndVars[0],
|
|
|
|
backdrop: modalOptions.backdrop,
|
|
|
|
keyboard: modalOptions.keyboard,
|
|
|
|
backdropClass: modalOptions.backdropClass,
|
|
|
|
windowClass: modalOptions.windowClass,
|
|
|
|
windowTemplateUrl: modalOptions.windowTemplateUrl,
|
|
|
|
size: modalOptions.size
|
|
|
|
});
|
|
|
|
|
|
|
|
}, function resolveError(reason) {
|
|
|
|
modalResultDeferred.reject(reason);
|
|
|
|
});
|
|
|
|
|
|
|
|
templateAndResolvePromise.then(function () {
|
|
|
|
modalOpenedDeferred.resolve(true);
|
|
|
|
}, function () {
|
|
|
|
modalOpenedDeferred.reject(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
return modalInstance;
|
|
|
|
};
|
|
|
|
|
|
|
|
return $modal;
|
|
|
|
}]
|
|
|
|
};
|
|
|
|
|
|
|
|
return $modalProvider;
|
|
|
|
});
|
|
|
|
|
|
|
|
angular.module('ui.bootstrap.transition', [])
|
|
|
|
|
|
|
|
/**
|
|
|
|
* $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
|
|
|
|
* @param {DOMElement} element The DOMElement that will be animated.
|
|
|
|
* @param {string|object|function} trigger The thing that will cause the transition to start:
|
|
|
|
* - As a string, it represents the css class to be added to the element.
|
|
|
|
* - As an object, it represents a hash of style attributes to be applied to the element.
|
|
|
|
* - As a function, it represents a function to be called that will cause the transition to occur.
|
|
|
|
* @return {Promise} A promise that is resolved when the transition finishes.
|
|
|
|
*/
|
|
|
|
.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
|
|
|
|
|
|
|
|
var $transition = function(element, trigger, options) {
|
|
|
|
options = options || {};
|
|
|
|
var deferred = $q.defer();
|
|
|
|
var endEventName = $transition[options.animation ? 'animationEndEventName' : 'transitionEndEventName'];
|
|
|
|
|
|
|
|
var transitionEndHandler = function(event) {
|
|
|
|
$rootScope.$apply(function() {
|
|
|
|
element.unbind(endEventName, transitionEndHandler);
|
|
|
|
deferred.resolve(element);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
if (endEventName) {
|
|
|
|
element.bind(endEventName, transitionEndHandler);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
|
|
|
|
$timeout(function() {
|
|
|
|
if ( angular.isString(trigger) ) {
|
|
|
|
element.addClass(trigger);
|
|
|
|
} else if ( angular.isFunction(trigger) ) {
|
|
|
|
trigger(element);
|
|
|
|
} else if ( angular.isObject(trigger) ) {
|
|
|
|
element.css(trigger);
|
|
|
|
}
|
|
|
|
//If browser does not support transitions, instantly resolve
|
|
|
|
if ( !endEventName ) {
|
|
|
|
deferred.resolve(element);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Add our custom cancel function to the promise that is returned
|
|
|
|
// We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
|
|
|
|
// i.e. it will therefore never raise a transitionEnd event for that transition
|
|
|
|
deferred.promise.cancel = function() {
|
|
|
|
if ( endEventName ) {
|
|
|
|
element.unbind(endEventName, transitionEndHandler);
|
|
|
|
}
|
|
|
|
deferred.reject('Transition cancelled');
|
|
|
|
};
|
|
|
|
|
|
|
|
return deferred.promise;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Work out the name of the transitionEnd event
|
|
|
|
var transElement = document.createElement('trans');
|
|
|
|
var transitionEndEventNames = {
|
|
|
|
'WebkitTransition': 'webkitTransitionEnd',
|
|
|
|
'MozTransition': 'transitionend',
|
|
|
|
'OTransition': 'oTransitionEnd',
|
|
|
|
'transition': 'transitionend'
|
|
|
|
};
|
|
|
|
var animationEndEventNames = {
|
|
|
|
'WebkitTransition': 'webkitAnimationEnd',
|
|
|
|
'MozTransition': 'animationend',
|
|
|
|
'OTransition': 'oAnimationEnd',
|
|
|
|
'transition': 'animationend'
|
|
|
|
};
|
|
|
|
function findEndEventName(endEventNames) {
|
|
|
|
for (var name in endEventNames){
|
|
|
|
if (transElement.style[name] !== undefined) {
|
|
|
|
return endEventNames[name];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
|
|
|
|
$transition.animationEndEventName = findEndEventName(animationEndEventNames);
|
|
|
|
return $transition;
|
|
|
|
}]);
|
|
|
|
|
|
|
|
angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache) {
|
|
|
|
$templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html",
|
|
|
|
"<div class=\"tooltip {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
|
|
|
|
" <div class=\"tooltip-arrow\"></div>\n" +
|
|
|
|
" <div class=\"tooltip-inner\" bind-html-unsafe=\"content\"></div>\n" +
|
|
|
|
"</div>\n" +
|
|
|
|
"");
|
|
|
|
}]);
|
|
|
|
|
|
|
|
angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) {
|
|
|
|
$templateCache.put("template/tooltip/tooltip-popup.html",
|
|
|
|
"<div class=\"tooltip {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
|
|
|
|
" <div class=\"tooltip-arrow\"></div>\n" +
|
|
|
|
" <div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" +
|
|
|
|
"</div>\n" +
|
|
|
|
"");
|
|
|
|
}]);
|
|
|
|
|
|
|
|
angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) {
|
|
|
|
$templateCache.put("template/modal/backdrop.html",
|
|
|
|
"<div class=\"modal-backdrop fade {{ backdropClass }}\"\n" +
|
|
|
|
" ng-class=\"{in: animate}\"\n" +
|
|
|
|
" ng-style=\"{'z-index': 1040 + (index && 1 || 0) + index*10}\"\n" +
|
|
|
|
"></div>\n" +
|
|
|
|
"");
|
|
|
|
}]);
|
|
|
|
|
|
|
|
angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) {
|
|
|
|
$templateCache.put("template/modal/window.html",
|
|
|
|
"<div tabindex=\"-1\" role=\"dialog\" class=\"modal fade\" ng-class=\"{in: animate}\" ng-style=\"{'z-index': 1050 + index*10, display: hiddenBySingle ? 'none' : 'block'}\">\n" +
|
|
|
|
" <div class=\"modal_close_wrap\" ng-click=\"close($event)\">\n" +
|
|
|
|
" <div class=\"modal_close\"></div>\n" +
|
|
|
|
" </div>\n" +
|
|
|
|
" <div class=\"modal-dialog\" ng-class=\"{'modal-sm': size == 'sm', 'modal-lg': size == 'lg'}\"><div class=\"modal-content\" modal-transclude></div></div>\n" +
|
|
|
|
"</div>");
|
|
|
|
}]);
|