angularjs-loading
Version:
Angular directive that lets you to prevent user interaction with part of the page and display loading/busy indicator (spinner based on spin.js)
251 lines (219 loc) • 7.68 kB
JavaScript
/**
* Angular Loading
* @homepage https://github.com/darthwade/angular-loading
* @author Vadym Petrychenko https://github.com/darthwade
* @license The MIT License (http://www.opensource.org/licenses/mit-license.php)
* @copyright 2014 Vadym Petrychenko
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['angular', 'spin.js'], factory);
} else if (typeof exports === 'object') {
// CommonJS
factory(require('angular'), require('spin.js'));
} else {
// Browser globals
factory(window.angular, window.Spinner)
}
}(function (angular, Spinner) {
'use strict';
angular.module('darthwade.loading', [])
.value('loadingOptions', {
active: false, // Defines current loading state
text: 'Loading...', // Display text
className: '', // Custom class, added to directive
overlay: true, // Display overlay
spinner: true, // Display spinner
spinnerOptions: {
lines: 12, // The number of lines to draw
length: 7, // The length of each line
width: 4, // The line thickness
radius: 10, // The radius of the inner circle
rotate: 0, // Rotation offset
corners: 1, // Roundness (0..1)
color: '#000', // #rgb or #rrggbb
direction: 1, // 1: clockwise, -1: counterclockwise
speed: 2, // Rounds per second
trail: 100, // Afterglow percentage
opacity: 1 / 4, // Opacity of the lines
fps: 20, // Frames per second when using setTimeout()
zIndex: 2e9, // Use a high z-index by default
className: 'dw-spinner', // CSS class to assign to the element
top: 'auto', // Center vertically
left: 'auto', // Center horizontally
position: 'relative' // Element position
}
})
.service('$loading', ['$rootScope', 'loadingOptions', function ($rootScope, loadingOptions) {
var self = this;
/**
* Overrides default options
* @param {object} options
*/
self.setDefaultOptions = function (options) {
extend(true, loadingOptions, options);
};
/**
* Activates loading state by key
* @param {string} key
*/
self.start = function (key) {
$rootScope.$evalAsync(function() {
$rootScope.$broadcast('$loadingStart', key);
});
};
/**
* Update loading state by key with loadingOptions object
* @param {string} key
* @param {object} options
*/
self.update = function (key, options) {
$rootScope.$evalAsync(function() {
$rootScope.$broadcast('$loadingUpdate', key, options);
});
};
/**
* Deactivates loading state by key
* @param {string} key
*/
self.finish = function (key) {
$rootScope.$evalAsync(function() {
$rootScope.$broadcast('$loadingFinish', key);
});
};
}])
.directive('dwLoading', ['$rootScope', 'loadingOptions', function ($rootScope, loadingOptions) {
return {
link: function (scope, element, attrs) {
var spinner = null,
key = attrs.dwLoading || false,
options,
container,
body,
spinnerContainer,
text;
/**
* Starts spinner
*/
var start = function () {
if (container) {
container.addClass('dw-loading-active');
}
if (spinner) {
spinner.spin(spinnerContainer[0]);
}
};
/**
* Update spinner, use force to update when loader is already started
*/
var update = function (newOptions, force) {
finish();
options = extend(true, {}, loadingOptions, newOptions);
// Build template
body = angular.element('<div></div>')
.addClass('dw-loading-body');
container = angular.element('<div></div>')
.addClass('dw-loading')
.append(body);
if (options.overlay) {
container.addClass('dw-loading-overlay');
}
if (options.className) {
container.addClass(options.className);
}
if (options.spinner) {
spinnerContainer = angular.element('<div></div>')
.addClass('dw-loading-spinner');
body.append(spinnerContainer);
spinner = new Spinner(options.spinnerOptions);
}
if (options.text) {
text = angular.element('<div></div>')
.addClass('dw-loading-text')
.text(options.text);
body.append(text);
}
element.append(container);
if ( options.active || !key || force) {
start();
}
};
/**
* Stops spinner
*/
var finish = function () {
if (container) {
container.removeClass('dw-loading-active');
}
if (spinner) {
spinner.stop();
}
};
scope.$watch(attrs.dwLoadingOptions, function (newOptions) {
update(newOptions);
}, true);
$rootScope.$on('$loadingStart', function (event, loadKey) {
if (loadKey === key) {
start();
}
});
$rootScope.$on('$loadingUpdate', function (event, loadKey, options) {
if (loadKey === key) {
update(options, true);
}
});
$rootScope.$on('$loadingFinish', function (event, loadKey) {
if (loadKey === key) {
finish();
}
});
scope.$on('$destroy', function () {
finish();
spinner = null;
});
}
};
}]);
/**
* Extends the destination object `dst` by copying all of the properties from the `src` object(s)
* to `dst`. You can specify multiple `src` objects.
*
* @param {Boolean} deep If true, the merge becomes recursive (optional)
* @param {Object} dst Destination object.
* @param {Object} src Source object(s).
* @returns {Object} Reference to `dst`.
*/
function extend(dst) {
var deep = false,
i = 1;
if (typeof dst === 'boolean') {
deep = dst;
dst = arguments[1] || {};
i++;
}
angular.forEach([].slice.call(arguments, i), function (obj) {
var array, clone, copy, key, src;
for (key in obj) {
src = dst[key];
copy = obj[key];
if (dst === copy) {
continue;
}
if (deep && copy && (angular.isObject(copy) ||
(array = angular.isArray(copy)))) {
if (array) {
clone = (src && angular.isArray(src)) ? src : [];
} else {
clone = (src && angular.isObject(src)) ? src : {};
}
dst[key] = extend(deep, clone, copy);
}
else if (copy !== undefined) {
dst[key] = copy;
}
}
});
return dst;
}
}));