/**
|
* angular-strap
|
* @version v2.0.3 - 2014-05-30
|
* @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="/gi;
|
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';
|
}
|
// 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();
|
});
|
};
|
// 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"/>');
|
$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 () {
|
scope.$emit(options.prefixEvent + '.show.before', $modal);
|
var parent = options.container ? findElement(options.container) : null;
|
var after = options.container ? null : options.element;
|
// Fetch a cloned element linked from template
|
modalElement = $modal.$element = modalLinker(scope, function (clonedElement, scope) {
|
});
|
// 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, function () {
|
});
|
}
|
$animate.enter(modalElement, parent, after, function () {
|
scope.$emit(options.prefixEvent + '.show', $modal);
|
});
|
scope.$isShown = true;
|
scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest();
|
// 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);
|
}
|
if (options.keyboard) {
|
modalElement.on('keyup', $modal.$onKeyUp);
|
}
|
};
|
$modal.hide = function () {
|
scope.$emit(options.prefixEvent + '.hide.before', $modal);
|
$animate.leave(modalElement, function () {
|
scope.$emit(options.prefixEvent + '.hide', $modal);
|
bodyElement.removeClass(options.prefixClass + '-open');
|
if (options.animation) {
|
bodyElement.addClass(options.prefixClass + '-with-' + options.animation);
|
}
|
});
|
if (options.backdrop) {
|
$animate.leave(backdropElement, function () {
|
});
|
}
|
scope.$isShown = false;
|
scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest();
|
// Unbind events
|
if (options.backdrop) {
|
modalElement.off('click', hideOnBackdropClick);
|
backdropElement.off('click', hideOnBackdropClick);
|
}
|
if (options.keyboard) {
|
modalElement.off('keyup', $modal.$onKeyUp);
|
}
|
};
|
$modal.toggle = function () {
|
scope.$isShown ? $modal.hide() : $modal.show();
|
};
|
$modal.focus = function () {
|
modalElement[0].focus();
|
};
|
// Protected methods
|
$modal.$onKeyUp = function (evt) {
|
evt.which === 27 && $modal.hide();
|
};
|
// Private methods
|
function hideOnBackdropClick(evt) {
|
if (evt.target !== evt.currentTarget)
|
return;
|
options.backdrop === 'static' ? $modal.focus() : $modal.hide();
|
}
|
return $modal;
|
}
|
// Helper functions
|
function findElement(query, element) {
|
return angular.element((element || document).querySelectorAll(query));
|
}
|
function fetchTemplate(template) {
|
return $q.when($templateCache.get(template) || $http.get(template)).then(function (res) {
|
if (angular.isObject(res)) {
|
$templateCache.put(template, res.data);
|
return res.data;
|
}
|
return res;
|
});
|
}
|
return ModalFactory;
|
}
|
];
|
}).directive('bsModal', [
|
'$window',
|
'$location',
|
'$sce',
|
'$modal',
|
function ($window, $location, $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',
|
'backdrop',
|
'keyboard',
|
'html',
|
'container',
|
'animation'
|
], function (key) {
|
if (angular.isDefined(attr[key]))
|
options[key] = attr[key];
|
});
|
// 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 () {
|
modal.destroy();
|
options = null;
|
modal = null;
|
});
|
}
|
};
|
}
|
]);
|