Skip to content
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

Use css3 for showing/hiding the loading bar and spinner. #322

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@
"angular-1.4": "angular#1.4",
"angular-mocks": "~1.2.9",
"angular-mocks-1.3": "angular-mocks#1.3",
"angular-mocks-1.4": "angular-mocks#1.4",
"angular-animate": "~1.2.9",
"angular-animate-1.3": "angular-animate#1.3",
"angular-animate-1.4": "angular-animate#1.4"
"angular-mocks-1.4": "angular-mocks#1.4"
},
"resolutions": {
"angular": "~1.2.23"
Expand Down
57 changes: 38 additions & 19 deletions src/loading-bar.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,29 @@
#loading-bar-spinner {
pointer-events: none;
-webkit-pointer-events: none;
-webkit-transition: 350ms linear all;
-moz-transition: 350ms linear all;
-o-transition: 350ms linear all;
transition: 350ms linear all;
}

#loading-bar.ng-enter,
#loading-bar.ng-leave.ng-leave-active,
#loading-bar-spinner.ng-enter,
#loading-bar-spinner.ng-leave.ng-leave-active {
opacity: 0;
-webkit-animation: fade linear 350ms;
-moz-animation: fade linear 350ms;
-ms-animation: fade linear 350ms;
-o-animation: fade linear 350ms;
animation: fade linear 350ms;

-webkit-transition: opacity 350ms linear;
-moz-transition: opacity 350ms linear;
-o-transition: opacity 350ms linear;
transition: opacity 350ms linear;
}

#loading-bar.ng-enter.ng-enter-active,
#loading-bar.ng-leave,
#loading-bar-spinner.ng-enter.ng-enter-active,
#loading-bar-spinner.ng-leave {
opacity: 1;
#loading-bar.out,
#loading-bar-spinner.out {
opacity: 0;
}

#loading-bar .bar {
-webkit-transition: width 350ms;
-moz-transition: width 350ms;
-o-transition: width 350ms;
transition: width 350ms;
-webkit-transition: width 350ms linear;
-moz-transition: width 350ms linear;
-o-transition: width 350ms linear;
transition: width 350ms linear;

background: #29d;
position: fixed;
Expand Down Expand Up @@ -102,3 +100,24 @@
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

@-webkit-keyframes fade {
0% { opacity: 0; }
100% { opacity: 1; }
}
@-moz-keyframes fade {
0% { opacity: 0; }
100% { opacity: 1; }
}
@-o-keyframes fade {
0% { opacity: 0; }
100% { opacity: 1; }
}
@-ms-keyframes fade {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes fade {
0% { opacity: 0; }
100% { opacity: 1; }
}
177 changes: 101 additions & 76 deletions src/loading-bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,104 +168,75 @@ angular.module('cfp.loadingBar', [])
this.parentSelector = 'body';
this.spinnerTemplate = '<div id="loading-bar-spinner"><div class="spinner-icon"></div></div>';
this.loadingBarTemplate = '<div id="loading-bar"><div class="bar"><div class="peg"></div></div></div>';
this.fadeOutDuration = 350;

this.$get = ['$injector', '$document', '$timeout', '$rootScope', function ($injector, $document, $timeout, $rootScope) {
var $animate;
var $parentSelector = this.parentSelector,
loadingBarContainer = angular.element(this.loadingBarTemplate),
loadingBar = loadingBarContainer.find('div').eq(0),
spinner = angular.element(this.spinnerTemplate);

function LoadingBar($document, $timeout, options) {
var _this = this;
var loadingBarContainer = angular.element(options.loadingBarTemplate);
var loadingBar = loadingBarContainer.find('div').eq(0);
var spinner = angular.element(options.spinnerTemplate);
var incTimeout,
completeTimeout,
started = false,
status = 0;

var autoIncrement = this.autoIncrement;
var includeSpinner = this.includeSpinner;
var includeBar = this.includeBar;
var startSize = this.startSize;

/**
* Inserts the loading bar element into the dom, and sets it to 2%
*/
function _start() {
if (!$animate) {
$animate = $injector.get('$animate');
}

$timeout.cancel(completeTimeout);

// do not continually broadcast the started event:
if (started) {
return;
}

var init = function() {
var document = $document[0];
var parent = document.querySelector ?
document.querySelector($parentSelector)
: $document.find($parentSelector)[0]
;
document.querySelector(options.parentSelector)
: $document.find(options.parentSelector)[0]
;

if (! parent) {
if(!parent) {
parent = document.getElementsByTagName('body')[0];
}

var $parent = angular.element(parent);
var $after = parent.lastChild && angular.element(parent.lastChild);

$rootScope.$broadcast('cfpLoadingBar:started');
started = true;

if (includeBar) {
$animate.enter(loadingBarContainer, $parent, $after);
if(options.includeBar) {
$parent.append(loadingBarContainer);
}

if (includeSpinner) {
$animate.enter(spinner, $parent, loadingBarContainer);
if(options.includeSpinner) {
$parent.append(spinner);
}
};

_set(startSize);
}
init();

/**
* Set the loading bar's width to a certain percent.
*
* @param n any value between 0 and 1
*/
function _set(n) {
if (!started) {
return;
}
this.activate = function() {
_this.set(options.startSize);
};

this.status = function() {
return status;
};

this.set = function(n) {
var pct = (n * 100) + '%';
loadingBar.css('width', pct);
status = n;

// increment loadingbar to give the illusion that there is always
// progress but make sure to cancel the previous timeouts so we don't
// have multiple incs running at the same time.
if (autoIncrement) {
if (options.autoIncrement) {
$timeout.cancel(incTimeout);

incTimeout = $timeout(function() {
_inc();
_this.inc();
}, 250);
}
}
};

/**
* Increments the loading bar by a random amount
* but slows down as it progresses
*/
function _inc() {
if (_status() >= 1) {
this.inc = function() {
if (_this.status() >= 1) {
return;
}

var rnd = 0;

// TODO: do this mathmatically instead of through conditions

var stat = _status();
var stat = _this.status();
if (stat >= 0 && stat < 0.25) {
// Start out between 3 - 6% increments
rnd = (Math.random() * (5 - 3 + 1) + 3) / 100;
Expand All @@ -283,34 +254,88 @@ angular.module('cfp.loadingBar', [])
rnd = 0;
}

var pct = _status() + rnd;
_set(pct);
var pct = _this.status() + rnd;
_this.set(pct);
};

this.cleanup = function() {
spinner.addClass('out');
loadingBarContainer.addClass('out');

$timeout(function() {
spinner.remove();
loadingBarContainer.remove();
}, options.fadeOutDuration);
};
}

this.$get = ['$injector', '$document', '$timeout', '$rootScope', function ($injector, $document, $timeout, $rootScope) {
var _this = this;

var options = {
parentSelector: _this.parentSelector,
loadingBarTemplate: _this.loadingBarTemplate,
spinnerTemplate: _this.spinnerTemplate,
autoIncrement: _this.autoIncrement,
includeSpinner: _this.includeSpinner,
includeBar: _this.includeBar,
startSize: _this.startSize,
fadeOutDuration: _this.fadeOutDuration
};

var completeTimeout,
currentBar = null;

/**
* Inserts the loading bar element into the dom, and sets it to 2%
*/
function _start() {
$timeout.cancel(completeTimeout);

// do not continually broadcast the started event:
if (currentBar) {
return;
}

currentBar = new LoadingBar($document, $timeout, options);
currentBar.activate();

$rootScope.$broadcast('cfpLoadingBar:started');
}

function _status() {
return status;
/**
* Set the loading bar's width to a certain percent.
*
* @param n any value between 0 and 1
*/
function _set(n) {
if (currentBar) currentBar.set(n);
}

function _completeAnimation() {
status = 0;
started = false;
/**
* Increments the loading bar by a random amount
* but slows down as it progresses
*/
function _inc() {
if (currentBar) currentBar.inc();
}

function _status() {
return currentBar ? currentBar.status() : 0;
}

function _complete() {
if (!$animate) {
$animate = $injector.get('$animate');
if (!currentBar) {
return;
}

_set(1);
$timeout.cancel(completeTimeout);

// Attempt to aggregate any start/complete calls within 500ms:
completeTimeout = $timeout(function() {
var promise = $animate.leave(loadingBarContainer, _completeAnimation);
if (promise && promise.then) {
promise.then(_completeAnimation);
}
$animate.leave(spinner);
currentBar.cleanup();
currentBar = null;
$rootScope.$broadcast('cfpLoadingBar:completed');
}, 500);
}
Expand Down
8 changes: 8 additions & 0 deletions test/loading-bar-interceptor-config.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe 'loadingBarInterceptor Service - config options', ->
expect(spinner).toBeNull
cfpLoadingBar.complete()
$timeout.flush()
$timeout.flush()

it 'should show the spinner if configured', ->
module 'chieffancypants.loadingBar', (cfpLoadingBarProvider) ->
Expand All @@ -21,6 +22,7 @@ describe 'loadingBarInterceptor Service - config options', ->
expect(spinner).not.toBeNull
cfpLoadingBar.complete()
$timeout.flush()
$timeout.flush()

it 'should hide the loadingBar if configured', ->
module 'chieffancypants.loadingBar', (cfpLoadingBarProvider) ->
Expand All @@ -32,6 +34,7 @@ describe 'loadingBarInterceptor Service - config options', ->
expect(spinner).toBeNull
cfpLoadingBar.complete()
$timeout.flush()
$timeout.flush()

it 'should show the loadingBar if configured', ->
module 'chieffancypants.loadingBar', (cfpLoadingBarProvider) ->
Expand All @@ -43,6 +46,7 @@ describe 'loadingBarInterceptor Service - config options', ->
expect(spinner).not.toBeNull
cfpLoadingBar.complete()
$timeout.flush()
$timeout.flush()

it 'should not auto increment loadingBar if configured', (done) ->
module 'chieffancypants.loadingBar', (cfpLoadingBarProvider) ->
Expand All @@ -66,6 +70,7 @@ describe 'loadingBarInterceptor Service - config options', ->
expect(cfpLoadingBar.status()).toBe .5;
cfpLoadingBar.complete()
$timeout.flush()
$timeout.flush()

it 'should auto increment loadingBar if configured', ->
module 'chieffancypants.loadingBar', (cfpLoadingBarProvider) ->
Expand All @@ -79,6 +84,7 @@ describe 'loadingBarInterceptor Service - config options', ->
expect(cfpLoadingBar.status()).toBeGreaterThan .5
cfpLoadingBar.complete()
$timeout.flush()
$timeout.flush()

it 'should append the loadingbar as the first child of the parent container if empty', ->
emptyEl = angular.element '<div id="empty"></div>'
Expand All @@ -96,6 +102,7 @@ describe 'loadingBarInterceptor Service - config options', ->
expect(children[1].id).toBe 'loading-bar-spinner'
cfpLoadingBar.complete()
$timeout.flush()
$timeout.flush()

it 'should append the loading bar to the body if parentSelector is empty', ->
module 'chieffancypants.loadingBar', (cfpLoadingBarProvider) ->
Expand All @@ -112,3 +119,4 @@ describe 'loadingBarInterceptor Service - config options', ->
expect(spinner.length).toBe 1
cfpLoadingBar.complete()
$timeout.flush()
$timeout.flush()
Loading