diff --git a/src/mw-modal/directives/mw_modal.js b/src/mw-modal/directives/mw_modal.js index 4453f9b1..ba418c26 100644 --- a/src/mw-modal/directives/mw_modal.js +++ b/src/mw-modal/directives/mw_modal.js @@ -1,6 +1,6 @@ angular.module('mwUI.Modal') - .directive('mwModal', function (mwModalTmpl) { + .directive('mwModal', function (mwModalTmpl, Loading) { return { restrict: 'A', scope: { @@ -8,21 +8,25 @@ angular.module('mwUI.Modal') }, transclude: true, templateUrl: 'uikit/mw-modal/directives/templates/mw_modal.html', - controller: function($scope){ - this.addClass = function(styleClass){ + controller: function ($scope) { + this.addClass = function (styleClass) { $scope.addClass(styleClass); }; }, link: function (scope, el) { scope.$emit('COMPILE:FINISHED'); scope.mwModalTmpl = mwModalTmpl; - scope.addClass = function(styleClass){ + scope.addClass = function (styleClass) { el.addClass(styleClass); }; - if(scope.title){ + if (scope.title) { scope.addClass('has-header'); } + + scope.showLoadingSpinner = function () { + return Loading.isLoading(); + }; } }; }); \ No newline at end of file diff --git a/src/mw-modal/directives/templates/mw_modal.html b/src/mw-modal/directives/templates/mw_modal.html index 3cc74e7b..b62670d5 100644 --- a/src/mw-modal/directives/templates/mw_modal.html +++ b/src/mw-modal/directives/templates/mw_modal.html @@ -6,6 +6,10 @@ ng-src="{{mwModalTmpl.getLogoPath()}}" class="pull-left logo"/> +
+
+
diff --git a/src/mw-modal/styles/_mw_modal.scss b/src/mw-modal/styles/_mw_modal.scss index c85a376d..eabbb275 100644 --- a/src/mw-modal/styles/_mw_modal.scss +++ b/src/mw-modal/styles/_mw_modal.scss @@ -125,6 +125,9 @@ $windowPadding: 60; box-shadow: 0 -1px 4px rgba(122,122,122,.5); position: relative; z-index: 1000; + display: flex; + overflow: hidden; + white-space: nowrap; .logo{ margin-top: 4px; @@ -132,6 +135,12 @@ $windowPadding: 60; height: 16px; } + h4{ + flex-grow: 1; + overflow: hidden; + text-overflow: ellipsis; + } + } .body-holder{ diff --git a/src/mw-utils/mw_utils.js b/src/mw-utils/mw_utils.js index ee5e6415..a36b9cb5 100644 --- a/src/mw-utils/mw_utils.js +++ b/src/mw-utils/mw_utils.js @@ -26,6 +26,7 @@ angular.module('mwUI.Utils', ['mwUI.i18n','mwUI.Modal']); // @include ./services/mw_bootstrap_breakpoint.js // @include ./services/mw_browser_title_handler.js // @include ./services/mw_callback_handler.js +// @include ./services/mw_loading_service.js // @include ./services/mw_scheduler.js // @include ./services/mw_url_storage.js // @include ./services/mw_runtime_storage.js @@ -35,6 +36,25 @@ angular.module('mwUI.Utils', ['mwUI.i18n','mwUI.Modal']); // @include ./shims/route_to_regex.js // @include ./shims/deprecation_warning.js -angular.module('mwUI.Utils').config(function(i18nProvider){ +angular.module('mwUI.Utils').config(function($provide, $httpProvider, i18nProvider){ i18nProvider.addResource('mw-utils/i18n', 'uikit'); + + $provide.factory('requestInterceptorForLoadingService', function ($q, Loading) { + return { + request: function(request){ + Loading.start(); + return request; + }, + response: function (response) { + Loading.done(); + return response; + }, + responseError: function (response) { + Loading.done(); + return $q.reject(response); + } + }; + }); + + $httpProvider.interceptors.push('requestInterceptorForLoadingService'); }); \ No newline at end of file diff --git a/src/mw-utils/services/mw_loading_service.js b/src/mw-utils/services/mw_loading_service.js new file mode 100644 index 00000000..52da84ec --- /dev/null +++ b/src/mw-utils/services/mw_loading_service.js @@ -0,0 +1,142 @@ +'use strict'; + +angular.module('mwUI.Utils') + +/** + * @ngdoc service + * @name mwUI.Utils.service:Loading + * + * @description + * This service manages loading processes. It can be used e.g. by a httpInterceptor to register a loading process + */ + .service('Loading', function ($timeout) { + + var itemsToLoad = 0, + itemsAlreadyLoaded = 0, + loading = false, + keysToLoad = {}, + waitUntilDoneTimout, + doneCallbacks = [], + startCallbacks = []; + + var reset = function () { + itemsToLoad = 0; + itemsAlreadyLoaded = 0; + }; + + var executeCallbacks = function (callbacks, args, scope) { + scope = scope || this; + callbacks.forEach(function (callback) { + callback.apply(scope, args); + }); + }; + + var setToDone = function () { + if (waitUntilDoneTimout) { + $timeout.cancel(waitUntilDoneTimout); + } + waitUntilDoneTimout = $timeout(function () { + loading = false; + executeCallbacks(doneCallbacks); + reset(); + }, 100); + }; + + var registerCallback = function (array, callback) { + if (typeof callback === 'function') { + array.push(callback); + } else { + throw new Error('Callback has to be a function'); + } + }; + + /** + * @ngdoc function + * @name start + * @methodOf mwUI.Utils.service:Loading + * @description + * Starts a loading process. When a key is passed a specified loading process is registered otherwise + * a global loading process is registered + * @param {String} key a unique key to start the loading process + */ + this.start = function (key) { + if (key) { + keysToLoad[key] = true; + } else { + itemsToLoad++; + if (!loading) { + executeCallbacks(startCallbacks); + loading = true; + $timeout.cancel(waitUntilDoneTimout); + } else { + $timeout.cancel(waitUntilDoneTimout); + } + } + }; + + /** + * @ngdoc function + * @name done + * @methodOf mwUI.Utils.service:Loading + * @description + * Stops a loading process identified by a unique key. + * When no key is specified a global loading process counter is increased. As soon as the global loading + * process counter is the same as the length of registered global loading processes a debounced callback is called + * @param {String} key the unique key which belongs to the animation (optional) + */ + this.done = function (key) { + if (key) { + delete keysToLoad[key]; + } else { + if (itemsToLoad !== 0) { + itemsAlreadyLoaded++; + if (itemsToLoad === itemsAlreadyLoaded) { + setToDone(); + } + } + } + }; + + /** + * @ngdoc function + * @name isLoading + * @methodOf mwUI.Utils.service:Loading + * @description + * When a key is specified it returns whether the loading process for that key is active + * otherwise it returns whether at least one process is active or none at all + * @param {String} key the unique key which belongs to the loading process (optional) + * @return {Boolean} Returns true if a loading is currently active for the given key + */ + this.isLoading = function (key) { + if (key) { + return keysToLoad[key] || false; + } else { + return loading; + } + }; + + /** + * @ngdoc function + * @name registerDoneCallback + * @methodOf mwUI.Utils.service:Loading + * @description + * Registers a callback function which gets called when all loading processes are done + * @param {Function} callback the callback function + */ + this.registerDoneCallback = function (callback) { + registerCallback(doneCallbacks, callback); + }; + + /** + * @ngdoc function + * @name registerStartCallback + * @methodOf mwUI.Utils.service:Loading + * @description + * Registers a callback function which gets called when the loading starts + * @param {Function} callback the callback function + */ + this.registerStartCallback = function (callback) { + registerCallback(startCallbacks, callback); + }; + + }); \ No newline at end of file diff --git a/src/mw-utils/services/mw_loading_service_test.js b/src/mw-utils/services/mw_loading_service_test.js new file mode 100644 index 00000000..d8da58f7 --- /dev/null +++ b/src/mw-utils/services/mw_loading_service_test.js @@ -0,0 +1,93 @@ +'use strict'; + +describe('Loading', function () { + var service, rootScope; + var $timeout; + beforeEach(module('mwUI.Utils')); + + afterEach(function () { + $timeout.verifyNoPendingTasks(); + service = null; + rootScope = null; + $timeout = null; + }); + + beforeEach(inject(function (_$rootScope_, _Loading_, _$timeout_) { + rootScope = _$rootScope_; + service = _Loading_; + $timeout = _$timeout_; + })); + + it('can be initialized', function () { + expect(service).toBeDefined(); + }); + + it('stores a loading key', function () { + service.start('test-key'); + expect(service.isLoading('test-key')).toBeTruthy(); + service.done('test-key'); + expect(service.isLoading('test-key')).toBeFalsy(); + }); + + it('isLoading can handle undefined as key', function () { + expect(service.isLoading(undefined)).toBeFalsy(); + }); + + it('isLoading can handle undefined as key and returns true when global loading process is in progress', function () { + service.start(); + expect(service.isLoading()).toBeTruthy(); + }); + + it('isLoading can handle undefined as key and returns false when no global loading process is in progress anymore', function () { + service.start(); + service.done(); + $timeout.flush(); + + expect(service.isLoading()).toBeFalsy(); + }); + + it('isLoading can handle undefined as key and returns true when at least one global loading process is in progress', function () { + service.start(); + service.start(); + + service.done(); + + expect(service.isLoading()).toBeTruthy(); + }); + + it('isLoading can handle undefined as key and returns false when all global loading process are done', function () { + service.start(); + service.start(); + + service.done(); + service.done(); + $timeout.flush(); + + expect(service.isLoading()).toBeFalsy(); + }); + + it('isLoading can handle null as key', function () { + expect(service.isLoading(null)).toBeFalsy(); + }); + + it('isLoading can unknown key', function () { + expect(service.isLoading('something')).toBeFalsy(); + }); + + it('sets a loading session with callbacks when no key is provided', function () { + var startCallback = jasmine.createSpy('startSpy'); + var doneCallback = jasmine.createSpy('doneSpy'); + service.registerStartCallback(startCallback); + service.registerDoneCallback(doneCallback); + service.start(); + rootScope.$digest(); + expect(service.isLoading()).toBeTruthy(); + service.done(); + rootScope.$digest(); + $timeout.flush(); + expect(service.isLoading()).toBeFalsy(); + + expect(startCallback).toHaveBeenCalled(); + expect(doneCallback).toHaveBeenCalled(); + }); +}); \ No newline at end of file