357 lines
11 KiB
JavaScript
357 lines
11 KiB
JavaScript
/**
|
|
* angular-strap
|
|
* @version v2.2.1 - 2015-03-10
|
|
* @link http://mgcrea.github.io/angular-strap
|
|
* @author Olivier Louvignes (olivier@mg-crea.com)
|
|
* @license MIT License, http://www.opensource.org/licenses/MIT
|
|
*/
|
|
'use strict';
|
|
|
|
angular.module('mgcrea.ngStrap.modal', ['mgcrea.ngStrap.helpers.dimensions'])
|
|
|
|
.provider('$modal', function() {
|
|
|
|
var defaults = this.defaults = {
|
|
animation: 'am-fade',
|
|
backdropAnimation: 'am-fade',
|
|
prefixClass: 'modal',
|
|
prefixEvent: 'modal',
|
|
placement: 'top',
|
|
template: 'modal/modal.tpl.html',
|
|
contentTemplate: false,
|
|
container: false,
|
|
element: null,
|
|
backdrop: true,
|
|
keyboard: true,
|
|
html: false,
|
|
show: true
|
|
};
|
|
|
|
this.$get = ["$window", "$rootScope", "$compile", "$q", "$templateCache", "$http", "$animate", "$timeout", "$sce", "dimensions", function($window, $rootScope, $compile, $q, $templateCache, $http, $animate, $timeout, $sce, dimensions) {
|
|
|
|
var forEach = angular.forEach;
|
|
var trim = String.prototype.trim;
|
|
var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
|
|
var bodyElement = angular.element($window.document.body);
|
|
var htmlReplaceRegExp = /ng-bind="/ig;
|
|
|
|
function ModalFactory(config) {
|
|
|
|
var $modal = {};
|
|
|
|
// Common vars
|
|
var options = $modal.$options = angular.extend({}, defaults, config);
|
|
$modal.$promise = fetchTemplate(options.template);
|
|
var scope = $modal.$scope = options.scope && options.scope.$new() || $rootScope.$new();
|
|
if(!options.element && !options.container) {
|
|
options.container = 'body';
|
|
}
|
|
|
|
// store $id to identify the triggering element in events
|
|
// give priority to options.id, otherwise, try to use
|
|
// element id if defined
|
|
$modal.$id = options.id || options.element && options.element.attr('id') || '';
|
|
|
|
// Support scope as string options
|
|
forEach(['title', 'content'], function(key) {
|
|
if(options[key]) scope[key] = $sce.trustAsHtml(options[key]);
|
|
});
|
|
|
|
// Provide scope helpers
|
|
scope.$hide = function() {
|
|
scope.$$postDigest(function() {
|
|
$modal.hide();
|
|
});
|
|
};
|
|
scope.$show = function() {
|
|
scope.$$postDigest(function() {
|
|
$modal.show();
|
|
});
|
|
};
|
|
scope.$toggle = function() {
|
|
scope.$$postDigest(function() {
|
|
$modal.toggle();
|
|
});
|
|
};
|
|
// Publish isShown as a protected var on scope
|
|
$modal.$isShown = scope.$isShown = false;
|
|
|
|
// Support contentTemplate option
|
|
if(options.contentTemplate) {
|
|
$modal.$promise = $modal.$promise.then(function(template) {
|
|
var templateEl = angular.element(template);
|
|
return fetchTemplate(options.contentTemplate)
|
|
.then(function(contentTemplate) {
|
|
var contentEl = findElement('[ng-bind="content"]', templateEl[0]).removeAttr('ng-bind').html(contentTemplate);
|
|
// Drop the default footer as you probably don't want it if you use a custom contentTemplate
|
|
if(!config.template) contentEl.next().remove();
|
|
return templateEl[0].outerHTML;
|
|
});
|
|
});
|
|
}
|
|
|
|
// Fetch, compile then initialize modal
|
|
var modalLinker, modalElement;
|
|
var backdropElement = angular.element('<div class="' + options.prefixClass + '-backdrop"/>');
|
|
backdropElement.css({position:'fixed', top:'0px', left:'0px', bottom:'0px', right:'0px', 'z-index': 1038});
|
|
$modal.$promise.then(function(template) {
|
|
if(angular.isObject(template)) template = template.data;
|
|
if(options.html) template = template.replace(htmlReplaceRegExp, 'ng-bind-html="');
|
|
template = trim.apply(template);
|
|
modalLinker = $compile(template);
|
|
$modal.init();
|
|
});
|
|
|
|
$modal.init = function() {
|
|
|
|
// Options: show
|
|
if(options.show) {
|
|
scope.$$postDigest(function() {
|
|
$modal.show();
|
|
});
|
|
}
|
|
|
|
};
|
|
|
|
$modal.destroy = function() {
|
|
|
|
// Remove element
|
|
if(modalElement) {
|
|
modalElement.remove();
|
|
modalElement = null;
|
|
}
|
|
if(backdropElement) {
|
|
backdropElement.remove();
|
|
backdropElement = null;
|
|
}
|
|
|
|
// Destroy scope
|
|
scope.$destroy();
|
|
|
|
};
|
|
|
|
$modal.show = function() {
|
|
if($modal.$isShown) return;
|
|
|
|
var parent, after;
|
|
if(angular.isElement(options.container)) {
|
|
parent = options.container;
|
|
after = options.container[0].lastChild ? angular.element(options.container[0].lastChild) : null;
|
|
} else {
|
|
if (options.container) {
|
|
parent = findElement(options.container);
|
|
after = parent[0].lastChild ? angular.element(parent[0].lastChild) : null;
|
|
} else {
|
|
parent = null;
|
|
after = options.element;
|
|
}
|
|
}
|
|
|
|
// Fetch a cloned element linked from template
|
|
modalElement = $modal.$element = modalLinker(scope, function(clonedElement, scope) {});
|
|
|
|
if(scope.$emit(options.prefixEvent + '.show.before', $modal).defaultPrevented) {
|
|
return;
|
|
}
|
|
|
|
// Set the initial positioning.
|
|
modalElement.css({display: 'block'}).addClass(options.placement);
|
|
|
|
// Options: animation
|
|
if(options.animation) {
|
|
if(options.backdrop) {
|
|
backdropElement.addClass(options.backdropAnimation);
|
|
}
|
|
modalElement.addClass(options.animation);
|
|
}
|
|
|
|
if(options.backdrop) {
|
|
$animate.enter(backdropElement, bodyElement, null);
|
|
}
|
|
// Support v1.3+ $animate
|
|
// https://github.com/angular/angular.js/commit/bf0f5502b1bbfddc5cdd2f138efd9188b8c652a9
|
|
var promise = $animate.enter(modalElement, parent, after, enterAnimateCallback);
|
|
if(promise && promise.then) promise.then(enterAnimateCallback);
|
|
|
|
$modal.$isShown = scope.$isShown = true;
|
|
safeDigest(scope);
|
|
// Focus once the enter-animation has started
|
|
// Weird PhantomJS bug hack
|
|
var el = modalElement[0];
|
|
requestAnimationFrame(function() {
|
|
el.focus();
|
|
});
|
|
|
|
bodyElement.addClass(options.prefixClass + '-open');
|
|
if(options.animation) {
|
|
bodyElement.addClass(options.prefixClass + '-with-' + options.animation);
|
|
}
|
|
|
|
// Bind events
|
|
if(options.backdrop) {
|
|
modalElement.on('click', hideOnBackdropClick);
|
|
backdropElement.on('click', hideOnBackdropClick);
|
|
backdropElement.on('wheel', preventEventDefault);
|
|
}
|
|
if(options.keyboard) {
|
|
modalElement.on('keyup', $modal.$onKeyUp);
|
|
}
|
|
};
|
|
|
|
function enterAnimateCallback() {
|
|
scope.$emit(options.prefixEvent + '.show', $modal);
|
|
}
|
|
|
|
$modal.hide = function() {
|
|
if(!$modal.$isShown) return;
|
|
|
|
if(scope.$emit(options.prefixEvent + '.hide.before', $modal).defaultPrevented) {
|
|
return;
|
|
}
|
|
var promise = $animate.leave(modalElement, leaveAnimateCallback);
|
|
// Support v1.3+ $animate
|
|
// https://github.com/angular/angular.js/commit/bf0f5502b1bbfddc5cdd2f138efd9188b8c652a9
|
|
if(promise && promise.then) promise.then(leaveAnimateCallback);
|
|
|
|
if(options.backdrop) {
|
|
$animate.leave(backdropElement);
|
|
}
|
|
$modal.$isShown = scope.$isShown = false;
|
|
safeDigest(scope);
|
|
|
|
// Unbind events
|
|
if(options.backdrop) {
|
|
modalElement.off('click', hideOnBackdropClick);
|
|
backdropElement.off('click', hideOnBackdropClick);
|
|
backdropElement.off('wheel', preventEventDefault);
|
|
}
|
|
if(options.keyboard) {
|
|
modalElement.off('keyup', $modal.$onKeyUp);
|
|
}
|
|
};
|
|
|
|
function leaveAnimateCallback() {
|
|
scope.$emit(options.prefixEvent + '.hide', $modal);
|
|
bodyElement.removeClass(options.prefixClass + '-open');
|
|
if(options.animation) {
|
|
bodyElement.removeClass(options.prefixClass + '-with-' + options.animation);
|
|
}
|
|
}
|
|
|
|
$modal.toggle = function() {
|
|
|
|
$modal.$isShown ? $modal.hide() : $modal.show();
|
|
|
|
};
|
|
|
|
$modal.focus = function() {
|
|
modalElement[0].focus();
|
|
};
|
|
|
|
// Protected methods
|
|
|
|
$modal.$onKeyUp = function(evt) {
|
|
|
|
if (evt.which === 27 && $modal.$isShown) {
|
|
$modal.hide();
|
|
evt.stopPropagation();
|
|
}
|
|
|
|
};
|
|
|
|
// Private methods
|
|
|
|
function hideOnBackdropClick(evt) {
|
|
if(evt.target !== evt.currentTarget) return;
|
|
options.backdrop === 'static' ? $modal.focus() : $modal.hide();
|
|
}
|
|
|
|
function preventEventDefault(evt) {
|
|
evt.preventDefault();
|
|
}
|
|
|
|
return $modal;
|
|
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
function safeDigest(scope) {
|
|
scope.$$phase || (scope.$root && scope.$root.$$phase) || scope.$digest();
|
|
}
|
|
|
|
function findElement(query, element) {
|
|
return angular.element((element || document).querySelectorAll(query));
|
|
}
|
|
|
|
var fetchPromises = {};
|
|
function fetchTemplate(template) {
|
|
if(fetchPromises[template]) return fetchPromises[template];
|
|
return (fetchPromises[template] = $http.get(template, {cache: $templateCache}).then(function(res) {
|
|
return res.data;
|
|
}));
|
|
}
|
|
|
|
return ModalFactory;
|
|
|
|
}];
|
|
|
|
})
|
|
|
|
.directive('bsModal', ["$window", "$sce", "$modal", function($window, $sce, $modal) {
|
|
|
|
return {
|
|
restrict: 'EAC',
|
|
scope: true,
|
|
link: function postLink(scope, element, attr, transclusion) {
|
|
|
|
// Directive options
|
|
var options = {scope: scope, element: element, show: false};
|
|
angular.forEach(['template', 'contentTemplate', 'placement', 'container', 'animation', 'id'], function(key) {
|
|
if(angular.isDefined(attr[key])) options[key] = attr[key];
|
|
});
|
|
|
|
// use string regex match for boolean values
|
|
var falseValueRegExp = /^(false|0|)$/;
|
|
angular.forEach(['keyboard', 'html'], function(key) {
|
|
if(angular.isDefined(attr[key])) options[key] = !falseValueRegExp.test(attr[key]);
|
|
});
|
|
|
|
if(angular.isDefined(attr.backdrop)) {
|
|
options.backdrop = falseValueRegExp.test(attr.backdrop) ? false : attr.backdrop;
|
|
}
|
|
|
|
// Support scope as data-attrs
|
|
angular.forEach(['title', 'content'], function(key) {
|
|
attr[key] && attr.$observe(key, function(newValue, oldValue) {
|
|
scope[key] = $sce.trustAsHtml(newValue);
|
|
});
|
|
});
|
|
|
|
// Support scope as an object
|
|
attr.bsModal && scope.$watch(attr.bsModal, function(newValue, oldValue) {
|
|
if(angular.isObject(newValue)) {
|
|
angular.extend(scope, newValue);
|
|
} else {
|
|
scope.content = newValue;
|
|
}
|
|
}, true);
|
|
|
|
// Initialize modal
|
|
var modal = $modal(options);
|
|
|
|
// Trigger
|
|
element.on(attr.trigger || 'click', modal.toggle);
|
|
|
|
// Garbage collection
|
|
scope.$on('$destroy', function() {
|
|
if (modal) modal.destroy();
|
|
options = null;
|
|
modal = null;
|
|
});
|
|
|
|
}
|
|
};
|
|
|
|
}]);
|