alexcrack/angular-ui-notification

View on GitHub
src/angular-ui-notification.js

Summary

Maintainability
D
2 days
Test Coverage
angular.module('ui-notification', []);

angular.module('ui-notification').provider('Notification', function () {

  this.options = {
    delay: 5000,
    startTop: 10,
    startRight: 10,
    verticalSpacing: 10,
    horizontalSpacing: 10,
    positionX: 'right',
    positionY: 'top',
    replaceMessage: false,
    templateUrl: 'angular-ui-notification.html',
    onClose: undefined,
    onClick: undefined,
    closeOnClick: true,
    maxCount: 0, // 0 - Infinite
    container: 'body',
    priority: 10
  };

  this.setOptions = function (options) {
    if (!angular.isObject(options)) throw new Error("Options should be an object!");
    this.options = angular.extend({}, this.options, options);
  };

  this.$get = function ($timeout, $http, $compile, $templateCache, $rootScope, $injector, $sce, $q, $window) {
    var options = this.options;

    var startTop = options.startTop;
    var startRight = options.startRight;
    var verticalSpacing = options.verticalSpacing;
    var horizontalSpacing = options.horizontalSpacing;
    var delay = options.delay;

    var messageElements = [];
    var isResizeBound = false;

    var notify = function (args, t) {
      var deferred = $q.defer();

      if (typeof args !== 'object' || args === null) {
        args = {message: args};
      }

      args.scope = args.scope ? args.scope : $rootScope;
      args.template = args.templateUrl ? args.templateUrl : options.templateUrl;
      args.delay = !angular.isUndefined(args.delay) ? args.delay : delay;
      args.type = t || args.type || options.type || '';
      args.positionY = args.positionY ? args.positionY : options.positionY;
      args.positionX = args.positionX ? args.positionX : options.positionX;
      args.replaceMessage = args.replaceMessage ? args.replaceMessage : options.replaceMessage;
      args.onClose = args.onClose ? args.onClose : options.onClose;
      args.onClick = args.onClick ? args.onClick : options.onClick;
      args.closeOnClick = (args.closeOnClick !== null && args.closeOnClick !== undefined) ? args.closeOnClick : options.closeOnClick;
      args.container = args.container ? args.container : options.container;
      args.priority = args.priority ? args.priority : options.priority;

      var template = $templateCache.get(args.template);

      if (template) {
        processNotificationTemplate(template);
      } else {
        // load it via $http only if it isn't default template and template isn't exist in template cache
        // cache:true means cache it for later access.
        $http.get(args.template, {cache: true})
          .then(function (response) {
            processNotificationTemplate(response.data);
          })
          .catch(function (data) {
            throw new Error('Template (' + args.template + ') could not be loaded. ' + data);
          });
      }


      function processNotificationTemplate(template) {

        var scope = args.scope.$new();
        scope.message = $sce.trustAsHtml(args.message);
        scope.title = $sce.trustAsHtml(args.title);
        scope.t = args.type.substr(0, 1);
        scope.delay = args.delay;
        scope.onClose = args.onClose;
        scope.onClick = args.onClick;

        var priorityCompareTop = function (a, b) {
          return a._priority - b._priority;
        };

        var priorityCompareBtm = function (a, b) {
          return b._priority - a._priority;
        };

        var reposite = function () {
          var j = 0;
          var k = 0;
          var lastTop = startTop;
          var lastRight = startRight;
          var lastPosition = [];

          if (args.positionY === 'top') {
            messageElements.sort(priorityCompareTop);
          } else if (args.positionY === 'bottom') {
            messageElements.sort(priorityCompareBtm);
          }

          for (var i = messageElements.length - 1; i >= 0; i--) {
            var element = messageElements[i];
            if (args.replaceMessage && i < messageElements.length - 1) {
              element.addClass('killed');
              continue;
            }
            var elHeight = parseInt(element[0].offsetHeight);
            var elWidth = parseInt(element[0].offsetWidth);
            var position = lastPosition[element._positionY + element._positionX];

            if ((top + elHeight) > window.innerHeight) {
              position = startTop;
              k++;
              j = 0;
            }

            var top = (lastTop = position ? (j === 0 ? position : position + verticalSpacing) : startTop);
            var right = lastRight + (k * (horizontalSpacing + elWidth));

            element.css(element._positionY, top + 'px');
            if (element._positionX === 'center') {
              element.css('left', parseInt(window.innerWidth / 2 - elWidth / 2) + 'px');
            } else {
              element.css(element._positionX, right + 'px');
            }

            lastPosition[element._positionY + element._positionX] = top + elHeight;

            if (options.maxCount > 0 && messageElements.length > options.maxCount && i === 0) {
              element.scope().kill(true);
            }

            j++;
          }
        };

        var templateElement = $compile(template)(scope);
        templateElement._positionY = args.positionY;
        templateElement._positionX = args.positionX;
        templateElement._priority = args.priority;
        templateElement.addClass(args.type);

        var closeEvent = function (e) {
          e = e.originalEvent || e;
          if (e.type === 'click' || e.propertyName === 'opacity' && e.elapsedTime >= 1) {

            if (scope.onClose) {
              scope.$apply(scope.onClose(templateElement));
            }

            if (e.type === 'click')
              if (scope.onClick) {
                scope.$apply(scope.onClick(templateElement));
              }

            templateElement.remove();
            messageElements.splice(messageElements.indexOf(templateElement), 1);
            scope.$destroy();
            reposite();
          }
        };

        if (args.closeOnClick) {
          templateElement.addClass('clickable');
          templateElement.bind('click', closeEvent);
        }

        templateElement.bind('webkitTransitionEnd oTransitionEnd otransitionend transitionend msTransitionEnd', closeEvent);

        if (angular.isNumber(args.delay)) {
          $timeout(function () {
            templateElement.addClass('killed');
          }, args.delay);
        }

        setCssTransitions('none');

        angular.element(document.querySelector(args.container)).append(templateElement);
        var offset = -(parseInt(templateElement[0].offsetHeight) + 50);
        templateElement.css(templateElement._positionY, offset + "px");
        messageElements.push(templateElement);

        if (args.positionX == 'center') {
          var elWidth = parseInt(templateElement[0].offsetWidth);
          templateElement.css('left', parseInt(window.innerWidth / 2 - elWidth / 2) + 'px');
        }

        $timeout(function () {
          setCssTransitions('');
        });

        function setCssTransitions(value) {
          ['-webkit-transition', '-o-transition', 'transition'].forEach(function (prefix) {
            templateElement.css(prefix, value);
          });
        }

        scope._templateElement = templateElement;

        scope.kill = function (isHard) {
          if (isHard) {
            if (scope.onClose) {
              scope.$apply(scope.onClose(scope._templateElement));
            }

            messageElements.splice(messageElements.indexOf(scope._templateElement), 1);
            scope._templateElement.remove();
            scope.$destroy();
            $timeout(reposite);
          } else {
            scope._templateElement.addClass('killed');
          }
        };

        $timeout(reposite);

        if (!isResizeBound) {
          angular.element($window).bind('resize', function (e) {
            $timeout(reposite);
          });
          isResizeBound = true;
        }

        deferred.resolve(scope);

      }

      return deferred.promise;
    };

    notify.primary = function (args) {
      return this(args, 'primary');
    };
    notify.error = function (args) {
      return this(args, 'error');
    };
    notify.success = function (args) {
      return this(args, 'success');
    };
    notify.info = function (args) {
      return this(args, 'info');
    };
    notify.warning = function (args) {
      return this(args, 'warning');
    };

    notify.clearAll = function () {
      angular.forEach(messageElements, function (element) {
        element.addClass('killed');
      });
    };

    return notify;
  };
});