UNPKG

angular-kendo-window

Version:

Angular directive to dynamically create Kendo UI windows with a separate template and controller.

376 lines 20.4 kB
angular.module('kendo.window', []) .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; } }; } }; }) .directive('kWindowFrame', [ '$kModalStack', '$q', '$injector', '$timeout', function ($modalStack, $q, $injector, $timeout) { return { scope: { index: '@' }, replace: true, transclude: true, template: '<div kendo-window="myKendoWindow" class="custom-kendo-window" k-options="options" modal-render="{{$isRendered}}" tabindex="-1" role="dialog"><div><div k-window-transclude></div></div></div>', link: function (scope, element, attrs) { var windowInstance = $modalStack.getTop().value; var deactivateFunction = windowInstance.options.deactivate; var activateFunction = windowInstance.options.activate; var resizeFunction = windowInstance.options.resize; var maximizeFunction = windowInstance.options.maximize; var minimizeFunction = windowInstance.options.minimize; var closeFunction = windowInstance.options.close; windowInstance.options.deactivate = function () { if (deactivateFunction) deactivateFunction(); windowInstance.modalScope.$close(); scope.myKendoWindow.destroy(); }; windowInstance.options.activate = function () { if (activateFunction) activateFunction(); windowInstance.openedDeferred.resolve(); windowInstance.modalScope.$windowOpened = true; var modal = $modalStack.getTop(); if (modal) { // Notify {@link $modalStack} that modal is rendered. $modalStack.modalRendered(modal.key); } //Set focus on the current window. //This is so the esc key will close the top window. $(windowInstance.myKendoWindow.element).focus(); }; windowInstance.options.resize = function (e) { if (resizeFunction) resizeFunction(e); if (windowInstance.onResize != null) windowInstance.onResize(e); }; windowInstance.options.close = function (e) { if (closeFunction !== undefined && closeFunction !== null) closeFunction(e); if (windowInstance.onClose != null) windowInstance.onClose(e); }; windowInstance.options.minimize = function (e) { if (minimizeFunction !== undefined && minimizeFunction !== null) minimizeFunction(e); if (windowInstance.onMinimize != null) windowInstance.onMinimize(e); }; windowInstance.options.maximize = function (e) { if (maximizeFunction !== undefined && maximizeFunction !== null) maximizeFunction(e); if (windowInstance.onMaximize != null) windowInstance.onMaximize(e); }; scope.options = windowInstance.options; 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; // Deferred object that will be resolved when this modal is render. var modalRenderDeferObj = $q.defer(); // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready. // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template. attrs.$observe('modalRender', function (value) { if (value == 'true') { modalRenderDeferObj.resolve(); } }); modalRenderDeferObj.promise.then(function () { $timeout(function () { scope.$apply(); $timeout(function () { if (scope.options.position === undefined) { scope.myKendoWindow.center().open(); } else { scope.myKendoWindow.open(); } windowInstance.myKendoWindow = scope.myKendoWindow; }, 100); }, 100); scope.$on($modalStack.NOW_CLOSING_EVENT, function (e, windowScope) { scope.myKendoWindow.close(); }); }); } }; } ]) .directive('kWindowTransclude', function () { return { link: function ($scope, $element, $attrs, controller, $transclude) { $transclude($scope.$parent, function (clone) { $element.empty(); $element.append(clone); }); } }; }) .factory('$kModalStack', [ '$document', '$compile', '$rootScope', '$q', '$injector', '$$stackedMap', function ($document, $compile, $rootScope, $q, $injector, $$stackedMap) { var openedWindows = $$stackedMap.createNew(); var $modalStack = { NOW_CLOSING_EVENT: 'modal.stack.now-closing', open: null, close: null, dismiss: null, dismissAll: null, getTop: null, modalRendered: null }; function removeModalWindow(windowInstance, elementToReceiveFocus) { var body = $document.find('body').eq(0); var modalWindow = openedWindows.get(windowInstance).value; //clean up the stack openedWindows.remove(windowInstance); modalWindow.modalScope.$broadcast($modalStack.NOW_CLOSING_EVENT, modalWindow.modalScope); //move focus to specified element if available, or else to body if (elementToReceiveFocus && elementToReceiveFocus.focus) { elementToReceiveFocus.focus(); } else { body.focus(); } } $modalStack.open = function (windowInstance, modal) { var modalOpener = $document[0].activeElement; openedWindows.add(windowInstance, { deferred: modal.deferred, renderDeferred: modal.renderDeferred, openedDeferred: modal.openedDeferred, modalScope: modal.scope, options: modal.options, onClose: windowInstance.onClose, onResize: windowInstance.onResize, onMaximize: windowInstance.onMaximize, onMinimize: windowInstance.onMinimize }); var body = $document.find('body').eq(0); var angularDomEl = angular.element('<div k-window-frame="modal-window" style="display:none;"></div>'); angularDomEl.attr({ 'index': openedWindows.length() - 1 }).html(modal.content); var modalDomEl = $compile(angularDomEl)(modal.scope); openedWindows.top().value.modalDomEl = modalDomEl; openedWindows.top().value.modalOpener = modalOpener; body.append(modalDomEl); }; function broadcastClosing(modalWindow, resultOrReason, closing) { return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented; } $modalStack.close = function (windowInstance, result) { var modalWindow = openedWindows.get(windowInstance); if (modalWindow && broadcastClosing(modalWindow, result, true)) { modalWindow.value.modalScope.$$kDestructionScheduled = true; modalWindow.value.deferred.resolve(result); removeModalWindow(windowInstance, modalWindow.value.modalOpener); return true; } return !modalWindow; }; $modalStack.dismiss = function (windowInstance, reason) { var modalWindow = openedWindows.get(windowInstance); if (modalWindow && broadcastClosing(modalWindow, reason, false)) { modalWindow.value.modalScope.$$kDestructionScheduled = true; modalWindow.value.deferred.reject(reason); removeModalWindow(windowInstance, modalWindow.value.modalOpener); return true; } return !modalWindow; }; $modalStack.dismissAll = function (reason) { var topModal = this.getTop(); while (topModal && this.dismiss(topModal.key, reason)) { topModal = this.getTop(); } }; $modalStack.getTop = function () { return openedWindows.top(); }; $modalStack.modalRendered = function (windowInstance) { var modalWindow = openedWindows.get(windowInstance); if (modalWindow) { modalWindow.value.renderDeferred.resolve(); } }; return $modalStack; } ]) .provider('$kWindow', function () { var $modalProvider = { options: {}, $get: ['$injector', '$rootScope', '$q', '$templateRequest', '$controller', '$kModalStack', function ($injector, $rootScope, $q, $templateRequest, $controller, $modalStack) { function getTemplatePromise(options) { return options.template ? $q.when(options.template) : $templateRequest(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl); } function getResolvePromises(resolves) { var promisesArr = []; angular.forEach(resolves, function (value) { if (angular.isFunction(value) || angular.isArray(value)) { promisesArr.push($q.when($injector.invoke(value))); } else if (angular.isString(value)) { promisesArr.push($q.when($injector.get(value))); } else { promisesArr.push($q.when(value)); } }); return promisesArr; } var promiseChain = null; var $modal = { getPromiseChain: function () { return promiseChain; }, open: function (modalOptions) { var modalResultDeferred = $q.defer(); var modalOpenedDeferred = $q.defer(); var modalRenderDeferred = $q.defer(); var modalScope = null; //prepare an instance of a modal to be injected into controllers and returned to a caller var windowInstance = { id: $modalStack.length, result: modalResultDeferred.promise, opened: modalOpenedDeferred.promise, rendered: modalRenderDeferred.promise, onClose: null, onResize: null, onMinimize: null, onMaximize: null, options: modalOptions.options, cancelResult: modalOptions.cancelResult, close: function (result) { return $modalStack.close(windowInstance, result); }, dismiss: function (reason) { return $modalStack.dismiss(windowInstance, 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))); function resolveWithTemplate() { return templateAndResolvePromise; } // 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) { modalScope = (modalOptions.scope || $rootScope).$new(); modalScope.$close = windowInstance.close; modalScope.$dismiss = windowInstance.dismiss; modalScope.$on('$destroy', function () { if (!modalScope.$$kDestructionScheduled) { modalScope.$dismiss('$kUnscheduledDestruction'); } }); var ctrlInstance = {}; var resolveIter = 1; //controllers if (modalOptions.controller) { var ctrlLocals = { $scope: modalScope, $windowInstance: windowInstance }; angular.forEach(modalOptions.resolve, function (value, key) { ctrlLocals[key] = tplAndVars[resolveIter++]; }); ctrlInstance = $controller(modalOptions.controller, ctrlLocals); if (modalOptions.controllerAs) { if (modalOptions.bindToController) { angular.extend(ctrlInstance, modalScope); } modalScope[modalOptions.controllerAs] = ctrlInstance; } } $modalStack.open(windowInstance, { scope: modalScope, deferred: modalResultDeferred, openedDeferred: modalOpenedDeferred, renderDeferred: modalRenderDeferred, content: tplAndVars[0], options: modalOptions.options === undefined ? {} : modalOptions.options }); }, function resolveError(reason) { modalOpenedDeferred.reject(reason); modalResultDeferred.reject(reason); }) .finally(function () { if (promiseChain === samePromise) { promiseChain = null; } }); return windowInstance; } }; return $modal; } ] }; return $modalProvider; });