<!DOCTYPE html>
<html>

  <head>
    <title>Angular Block Spinner</title>
    
    <link rel="stylesheet" type="text/css" href="angular-loading.css" />
    <link rel="stylesheet" type="text/css" href="style.css" />
    
    <script data-require="angular.js@1.2.13" data-semver="1.2.13" src="http://code.angularjs.org/1.2.13/angular.js"></script>
    <script data-require="spin.js@1.2.7" data-semver="1.2.7" src="//cdnjs.cloudflare.com/ajax/libs/spin.js/1.2.7/spin.min.js"></script>
    <script type="text/javascript" src="angular-loading.js"></script>
    <script>
    angular.module('demoApp', [
          'darthwade.dwLoading'
        ])

        .controller('MainCtrl', function ($scope, $loading) {
          $scope.startLoading = function (name) {
            $loading.start(name);
          };

          $scope.finishLoading = function (name) {
            $loading.finish(name);
          };
        })
  </script>
  </head>

  <body ng-app="demoApp">
    <div ng-controller="MainCtrl">
      <div class="sample sample-1">
        <h2>Basic</h2>
        <input type="button" value="Start Loading" ng-click="startLoading('sample-1')" />
        <input type="button" value="Finish Loading" ng-click="finishLoading('sample-1')" />
        <div class="box small" dw-loading="sample-1">
          <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.</p>
          <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.</p>
        </div>
      </div>
      <div class="sample sample-2">
        <h2>Autoload</h2>
        <input type="button" value="Start Loading" ng-click="startLoading('sample-2')" />
        <input type="button" value="Finish Loading" ng-click="finishLoading('sample-2')" />
        <div class="box small" dw-loading="sample-2" dw-loading-options="{active: true}">
          <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.</p>
          <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.</p>
        </div>
      </div>
      <div class="sample sample-3">
        <h2>No text, no overlay</h2>
        <input type="button" value="Start Loading" ng-click="startLoading('sample-3')" />
        <input type="button" value="Finish Loading" ng-click="finishLoading('sample-3')" />
        <div class="box small" dw-loading="sample-3" dw-loading-options="{active: true, text: false, overlay: false}">
          <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.</p>
          <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.</p>
        </div>
      </div>
      <div class="sample sample-4">
        <h2>Custom styling, custom spinner, custom text</h2>
        <input type="button" value="Start Loading" ng-click="startLoading('sample-4')" />
        <input type="button" value="Finish Loading" ng-click="finishLoading('sample-4')" />
        <div class="box small" dw-loading="sample-4" dw-loading-options="{active: true, text: 'Custom text', className: 'custom-loading', spinnerOptions: {lines: 12, length: 20, width: 6, radius: 20, color: '#f0f', direction: -1, speed: 3}}">
          <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.</p>
          <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.</p>
        </div>
      </div>
    </div>
  </body>

</html>
/* for testing purposes */
body {
  margin:80px 10px;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 13px;
  line-height: 18px;
  color: #333333;
  background-color: #ffffff;
}

.box {
  padding:20px;
  border:1px solid #000;
  position:relative;
}

.small {
  width:200px;
  height:200px;
}

.medium {
  width:500px;
  height:350px;
}

.large {
  width:600px;
  height:500px;
}

.sample {
  float: left;
  width: 270px;
}

/* custom loader styles */
.custom-loading.dw-loading {
  text-align: center;
}

.custom-loading.dw-loading.dw-loading-overlay {
  background-color: rgba(0, 255, 255, .7);
  z-index: 9999;
}

.custom-loading.dw-loading > .dw-loading-body > .dw-loading-text {
  color: green;
  font-size: 13px;
  top: 45px;
}
(function (window, angular, undefined) {
  'use strict';

  angular.module('darthwade.dwLoading', [])

    .value('dwLoadingOptions', {
      active: false,
      text: 'Loading...',
      className: '',
      overlay: true,
      spinner: true,
      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('dwLoading', ['$rootScope', 'dwLoadingOptions', function ($rootScope, dwLoadingOptions) {
      var self = this;

      /**
       * Sets default options (@see `dwLoadingOptions`)
       * @param {object} options
       */
      self.setDefaultOptions = function (options) {
        extend(true, dwLoadingOptions, options);
      };

      /**
       * Activates spinner by key
       * @param {string} key
       */
      self.start = function (key) {
        $rootScope.$broadcast('$dwLoadingStart', key);
      };

      /**
       * Deactivates spinner by key
       * @param {string} key
       */
      self.finish = function (key) {
        $rootScope.$broadcast('$dwLoadingFinish', key);
      };
    }])

    // Shortcut
    .factory('$loading', ['dwLoading', function(dwLoading) {
      return dwLoading;
    }])

    .directive('dwLoading', ['$window', '$compile', 'dwLoadingOptions', function ($window, $compile, dwLoadingOptions) {
      return {
        scope: true,
        link: function (scope, element, attrs) {
          scope.spinner = null;
          scope.key = attrs.dwLoading || false;

          /**
           * Starts spinner
           */
          scope.start = function () {
            if (scope.container) {
              scope.container.addClass('dw-loading-active');
            }
            if (scope.spinner) {
              scope.spinner.spin(scope.spinnerContainer[0]);
            }
          };

          /**
           * Stops spinner
           */
          scope.finish = function () {
            if (scope.container) {
              scope.container.removeClass('dw-loading-active');
            }
            if (scope.spinner) {
              scope.spinner.stop();
            }
          };
          
          scope.$watch(attrs.dwLoadingOptions, function (options) {
            scope.finish();

            scope.options = extend(true, {}, dwLoadingOptions, options);

            // Build template
            var body = angular.element('<div></div>')
              .addClass('dw-loading-body');
            scope.container = angular.element('<div></div>')
              .addClass('dw-loading')
              .append(body);

            if (scope.options.overlay) {
              scope.container.addClass('dw-loading-overlay');
            }
            if (scope.options.className) {
              scope.container.addClass(scope.options.className);
            }
            if (scope.options.spinner) {
              scope.spinnerContainer = angular.element('<div></div>')
                .addClass('dw-loading-spinner');
              body.append(scope.spinnerContainer);
              scope.spinner = new $window.Spinner(scope.options.spinnerOptions);
            }
            if (scope.options.text) {
              var text = angular.element('<div></div>')
                .addClass('dw-loading-text')
                .text(scope.options.text);
              body.append(text);
            }

            element.append(scope.container);
//            $compile(container)(scope);

            if (scope.options.active || !scope.key) {
              scope.start();
            }
          }, true);

          scope.$on('$dwLoadingStart', function (event, key) {
            if (key === scope.key) {
              scope.start();
            }
          });

          scope.$on('$dwLoadingFinish', function (event, key) {
            if (key === scope.key) {
              scope.finish();
            }
          });

          scope.$on('$destroy', function () {
            scope.finish();
            scope.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;
  }

})(window, window.angular);
.dw-loading {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    text-align: center;
    display: none;
}

.dw-loading:before {
    content: '';
    display: inline-block;
    height: 100%;
    vertical-align: middle;
}

.dw-loading.dw-loading-active {
    display: block;
}

.dw-loading.dw-loading-overlay {
    background-color: rgba(255, 255, 255, .7);
    z-index: 9999;
}

.dw-loading > .dw-loading-body {
    display: inline-block;
    vertical-align: middle;
    position: relative;
}

.dw-loading > .dw-loading-body > .dw-loading-spinner {
    position: relative;
}

.dw-loading > .dw-loading-body > .dw-loading-text {
    position: relative;
    top: 25px;
    font-weight: bold;
    font-size: 11px;
    text-shadow: 0 0 2px rgb(255, 255, 255);
}