UNPKG

angular-cached-resource

Version:

An AngularJS module to interact with RESTful resources, even when browser is offline

292 lines (233 loc) 9.03 kB
'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 = 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'; } // 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.$$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.$$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', 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; }); } }; });