-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/modal global loading indicator #229
base: master
Are you sure you want to change the base?
Changes from all commits
e6f1261
7ad82f2
43ac8d0
246ee55
5aa6586
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,32 @@ | ||
angular.module('mwUI.Modal') | ||
|
||
.directive('mwModal', function (mwModalTmpl) { | ||
.directive('mwModal', function (mwModalTmpl, Loading) { | ||
return { | ||
restrict: 'A', | ||
scope: { | ||
title: '@' | ||
}, | ||
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(); | ||
}; | ||
} | ||
}; | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}; | ||
|
||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add $timeout.flush()... i expect that the test will crash There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it will fail because there are no $timeouts to flush but this is the expected behaviour There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a check in afterEach to verify that there is no outstanding $timeout task |
||
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(); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have a service called 'Loading' (see relution/common). How are name collisions handled
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will use the one the is registered first, the other one is just ignored. Next step is to remove the Loading service from the portal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So in fact you moved the loading service into the UI-Kit...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes with some small modifications, a merge request for the portal will come as soon as this mr is released in a new uikit version