1/* global angular */ 2(function (window, document) { 3 'use strict'; 4 5 /* 6 * AngularJS Toaster 7 * Version: 0.4.15 8 * 9 * Copyright 2013-2015 Jiri Kavulak. 10 * All Rights Reserved. 11 * Use, reproduction, distribution, and modification of this code is subject to the terms and 12 * conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php 13 * 14 * Author: Jiri Kavulak 15 * Related to project of John Papa, Hans Fjällemark and Nguyá» n Thiá»n Hùng (thienhung1989) 16 */ 17 18 angular.module('toaster', []).constant( 19 'toasterConfig', { 20 'limit': 0, // limits max number of toasts 21 'tap-to-dismiss': true, 22 23 /* Options: 24 - Boolean false/true 25 'close-button': true 26 - object if not a boolean that allows you to 27 override showing the close button for each 28 icon-class value 29 'close-button': { 'toast-error': true, 'toast-info': false } 30 */ 31 'close-button': false, 32 'newest-on-top': true, 33 //'fade-in': 1000, // done in css 34 //'on-fade-in': undefined, // not implemented 35 //'fade-out': 1000, // done in css 36 //'on-fade-out': undefined, // not implemented 37 //'extended-time-out': 1000, // not implemented 38 'time-out': 15000, //5000 Set timeOut and extendedTimeout to 0 to make it sticky 39 'icon-classes': { 40 error: 'toast-error', 41 info: 'toast-info', 42 wait: 'toast-wait', 43 success: 'toast-success', 44 warning: 'toast-warning' 45 }, 46 'body-output-type': '', // Options: '', 'trustedHtml', 'template', 'templateWithData', 'directive' 47 'body-template': 'toasterBodyTmpl.html', 48 'icon-class': 'toast-info', 49 'position-class': 'toast-top-right', // Options (see CSS): 50 // 'toast-top-full-width', 'toast-bottom-full-width', 'toast-center', 51 // 'toast-top-left', 'toast-top-center', 'toast-top-right', 52 // 'toast-bottom-left', 'toast-bottom-center', 'toast-bottom-right', 53 'title-class': 'toast-title', 54 'message-class': 'toast-message', 55 'prevent-duplicates': false, 56 'mouseover-timer-stop': true // stop timeout on mouseover and restart timer on mouseout 57 } 58 ).service( 59 'toaster', [ 60 '$rootScope', 'toasterConfig', function ($rootScope, toasterConfig) { 61 this.pop = function (type, title, body, timeout, bodyOutputType, clickHandler, toasterId, showCloseButton, toastId, onHideCallback) { 62 if (angular.isObject(type)) { 63 var params = type; // Enable named parameters as pop argument 64 this.toast = { 65 type: params.type, 66 title: params.title, 67 body: params.body, 68 timeout: params.timeout, 69 bodyOutputType: params.bodyOutputType, 70 clickHandler: params.clickHandler, 71 showCloseButton: params.showCloseButton, 72 uid: params.toastId, 73 onHideCallback: params.onHideCallback 74 }; 75 toastId = params.toastId; 76 toasterId = params.toasterId; 77 } else { 78 this.toast = { 79 type: type, 80 title: title, 81 body: body, 82 timeout: timeout, 83 bodyOutputType: bodyOutputType, 84 clickHandler: clickHandler, 85 showCloseButton: showCloseButton, 86 uid: toastId, 87 onHideCallback: onHideCallback 88 }; 89 } 90 $rootScope.$emit('toaster-newToast', toasterId, toastId); 91 }; 92 93 this.clear = function (toasterId, toastId) { 94 $rootScope.$emit('toaster-clearToasts', toasterId, toastId); 95 }; 96 97 // Create one method per icon class, to allow to call toaster.info() and similar 98 for (var type in toasterConfig['icon-classes']) { 99 this[type] = createTypeMethod(type); 100 } 101 102 function createTypeMethod(toasterType) { 103 return function (title, body, timeout, bodyOutputType, clickHandler, toasterId, showCloseButton, toastId, onHideCallback) { 104 if (angular.isString(title)) { 105 this.pop( 106 toasterType, 107 title, 108 body, 109 timeout, 110 bodyOutputType, 111 clickHandler, 112 toasterId, 113 showCloseButton, 114 toastId, 115 onHideCallback 116 ); 117 } else { // 'title' is actually an object with options 118 this.pop(angular.extend(title, { type: toasterType })); 119 } 120 }; 121 } 122 }] 123 ).factory( 124 'toasterEventRegistry', [ 125 '$rootScope', function ($rootScope) { 126 var deregisterNewToast = null, deregisterClearToasts = null, newToastEventSubscribers = [], clearToastsEventSubscribers = [], toasterFactory; 127 128 toasterFactory = { 129 setup: function () { 130 if (!deregisterNewToast) { 131 deregisterNewToast = $rootScope.$on( 132 'toaster-newToast', function (event, toasterId, toastId) { 133 for (var i = 0, len = newToastEventSubscribers.length; i < len; i++) { 134 newToastEventSubscribers[i](event, toasterId, toastId); 135 } 136 } 137 ); 138 } 139 140 if (!deregisterClearToasts) { 141 deregisterClearToasts = $rootScope.$on( 142 'toaster-clearToasts', function (event, toasterId, toastId) { 143 for (var i = 0, len = clearToastsEventSubscribers.length; i < len; i++) { 144 clearToastsEventSubscribers[i](event, toasterId, toastId); 145 } 146 } 147 ); 148 } 149 }, 150 151 subscribeToNewToastEvent: function (onNewToast) { 152 newToastEventSubscribers.push(onNewToast); 153 }, 154 subscribeToClearToastsEvent: function (onClearToasts) { 155 clearToastsEventSubscribers.push(onClearToasts); 156 }, 157 unsubscribeToNewToastEvent: function (onNewToast) { 158 var index = newToastEventSubscribers.indexOf(onNewToast); 159 if (index >= 0) { 160 newToastEventSubscribers.splice(index, 1); 161 } 162 163 if (newToastEventSubscribers.length === 0) { 164 deregisterNewToast(); 165 deregisterNewToast = null; 166 } 167 }, 168 unsubscribeToClearToastsEvent: function (onClearToasts) { 169 var index = clearToastsEventSubscribers.indexOf(onClearToasts); 170 if (index >= 0) { 171 clearToastsEventSubscribers.splice(index, 1); 172 } 173 174 if (clearToastsEventSubscribers.length === 0) { 175 deregisterClearToasts(); 176 deregisterClearToasts = null; 177 } 178 } 179 }; 180 return { 181 setup: toasterFactory.setup, 182 subscribeToNewToastEvent: toasterFactory.subscribeToNewToastEvent, 183 subscribeToClearToastsEvent: toasterFactory.subscribeToClearToastsEvent, 184 unsubscribeToNewToastEvent: toasterFactory.unsubscribeToNewToastEvent, 185 unsubscribeToClearToastsEvent: toasterFactory.unsubscribeToClearToastsEvent 186 }; 187 }] 188 ) 189 .directive('directiveTemplate', ['$compile', function($compile) { 190 return { 191 restrict: 'A', 192 scope: { 193 directiveName: '@directiveName' 194 }, 195 replace: true, 196 link: function (scope, elm, attrs) { 197 scope.$watch('directiveName', function (directiveName) { 198 var template = $compile('<div ' + directiveName + '></div>')(scope); 199 elm.append(template); 200 }); 201 } 202 } 203 }]) 204 .directive( 205 'toasterContainer', [ 206 '$parse', '$rootScope', '$interval', '$sce', 'toasterConfig', 'toaster', 'toasterEventRegistry', 207 function ($parse, $rootScope, $interval, $sce, toasterConfig, toaster, toasterEventRegistry) { 208 return { 209 replace: true, 210 restrict: 'EA', 211 scope: true, // creates an internal scope for this directive (one per directive instance) 212 link: function (scope, elm, attrs) { 213 var id = 0, mergedConfig; 214 215 // Merges configuration set in directive with default one 216 mergedConfig = angular.extend({}, toasterConfig, scope.$eval(attrs.toasterOptions)); 217 218 scope.config = { 219 toasterId: mergedConfig['toaster-id'], 220 position: mergedConfig['position-class'], 221 title: mergedConfig['title-class'], 222 message: mergedConfig['message-class'], 223 tap: mergedConfig['tap-to-dismiss'], 224 closeButton: mergedConfig['close-button'], 225 animation: mergedConfig['animation-class'], 226 mouseoverTimer: mergedConfig['mouseover-timer-stop'] 227 }; 228 229 scope.$on( 230 "$destroy", function () { 231 toasterEventRegistry.unsubscribeToNewToastEvent(scope._onNewToast); 232 toasterEventRegistry.unsubscribeToClearToastsEvent(scope._onClearToasts); 233 } 234 ); 235 236 function setTimeout(toast, time) { 237 toast.timeoutPromise = $interval( 238 function () { 239 scope.removeToast(toast.id); 240 }, time, 1 241 ); 242 } 243 244 scope.configureTimer = function (toast) { 245 var timeout = angular.isNumber(toast.timeout) ? toast.timeout : mergedConfig['time-out']; 246 if (typeof timeout === "object") timeout = timeout[toast.type]; 247 if (timeout > 0) { 248 setTimeout(toast, timeout); 249 } 250 }; 251 252 function addToast(toast, toastId) { 253 toast.type = mergedConfig['icon-classes'][toast.type]; 254 if (!toast.type) { 255 toast.type = mergedConfig['icon-class']; 256 } 257 258 if (mergedConfig['prevent-duplicates'] === true) { 259 // Prevent adding duplicate toasts if it's set 260 if (isUndefinedOrNull(toastId)) { 261 if (scope.toasters.length > 0 && scope.toasters[scope.toasters.length - 1].body === toast.body) { 262 return; 263 } 264 } else { 265 var i, len; 266 for (i = 0, len = scope.toasters.length; i < len; i++) { 267 if (scope.toasters[i].uid === toastId) { 268 removeToast(i); 269 // update loop 270 i--; 271 len = scope.toasters.length; 272 } 273 } 274 } 275 } 276 277 toast.id = ++id; 278 // Sure uid defined 279 if (!isUndefinedOrNull(toastId)) { 280 toast.uid = toastId; 281 } 282 283 // set the showCloseButton property on the toast so that 284 // each template can bind directly to the property to show/hide 285 // the close button 286 var closeButton = mergedConfig['close-button']; 287 288 // if toast.showCloseButton is a boolean value, 289 // it was specifically overriden in the pop arguments 290 if (typeof toast.showCloseButton === "boolean") { 291 292 } else if (typeof closeButton === "boolean") { 293 toast.showCloseButton = closeButton; 294 } else if (typeof closeButton === "object") {
vendor: 6,722 bytes, lines 295-416
295 var closeButtonForType = closeButton[toast.type]; 296 297 if (typeof closeButtonForType !== "undefined" && closeButtonForType !== null) { 298 toast.showCloseButton = closeButtonForType; 299 } 300 } else { 301 // if an option was not set, default to false. 302 toast.showCloseButton = false; 303 } 304 305 // Set the toast.bodyOutputType to the default if it isn't set 306 toast.bodyOutputType = toast.bodyOutputType || mergedConfig['body-output-type']; 307 switch (toast.bodyOutputType) { 308 case 'trustedHtml': 309 toast.html = $sce.trustAsHtml(toast.body); 310 break; 311 case 'template': 312 toast.bodyTemplate = toast.body || mergedConfig['body-template']; 313 break; 314 case 'templateWithData': 315 var fcGet = $parse(toast.body || mergedConfig['body-template']); 316 var templateWithData = fcGet(scope); 317 toast.bodyTemplate = templateWithData.template; 318 toast.data = templateWithData.data; 319 break; 320 case 'directive': 321 toast.html = toast.body; 322 break; 323 } 324 325 scope.configureTimer(toast); 326 if (mergedConfig['newest-on-top'] === true) { 327 scope.toasters.unshift(toast); 328 if (mergedConfig['limit'] > 0 && scope.toasters.length > mergedConfig['limit']) { 329 scope.toasters.pop(); 330 } 331 } else { 332 scope.toasters.push(toast); 333 if (mergedConfig['limit'] > 0 && scope.toasters.length > mergedConfig['limit']) { 334 scope.toasters.shift(); 335 } 336 } 337 } 338 339 scope.removeToast = function (id) { 340 var i, len; 341 for (i = 0, len = scope.toasters.length; i < len; i++) { 342 if (scope.toasters[i].id === id) { 343 removeToast(i); 344 break; 345 } 346 } 347 }; 348 349 function removeToast(toastIndex) { 350 var toast = scope.toasters[toastIndex]; 351 if (toast) { 352 if (toast.timeoutPromise) { 353 $interval.cancel(toast.timeoutPromise); 354 } 355 scope.toasters.splice(toastIndex, 1); 356 357 if (angular.isFunction(toast.onHideCallback)) { 358 toast.onHideCallback(); 359 } 360 } 361 } 362 363 function removeAllToasts(toastId) { 364 for (var i = scope.toasters.length - 1; i >= 0; i--) { 365 if (isUndefinedOrNull(toastId)) { 366 removeToast(i); 367 } else { 368 if (scope.toasters[i].uid == toastId) { 369 removeToast(i); 370 } 371 } 372 } 373 } 374 375 scope.toasters = []; 376 377 function isUndefinedOrNull(val) { 378 return angular.isUndefined(val) || val === null; 379 } 380 381 scope._onNewToast = function (event, toasterId, toastId) { 382 // Compatibility: if toaster has no toasterId defined, and if call to display 383 // hasn't either, then the request is for us 384 385 if ((isUndefinedOrNull(scope.config.toasterId) && isUndefinedOrNull(toasterId)) || (!isUndefinedOrNull(scope.config.toasterId) && !isUndefinedOrNull(toasterId) && scope.config.toasterId == toasterId)) { 386 addToast(toaster.toast, toastId); 387 } 388 }; 389 scope._onClearToasts = function (event, toasterId, toastId) { 390 // Compatibility: if toaster has no toasterId defined, and if call to display 391 // hasn't either, then the request is for us 392 if (toasterId == '*' || (isUndefinedOrNull(scope.config.toasterId) && isUndefinedOrNull(toasterId)) || (!isUndefinedOrNull(scope.config.toasterId) && !isUndefinedOrNull(toasterId) && scope.config.toasterId == toasterId)) { 393 removeAllToasts(toastId); 394 } 395 }; 396 397 toasterEventRegistry.setup(); 398 399 toasterEventRegistry.subscribeToNewToastEvent(scope._onNewToast); 400 toasterEventRegistry.subscribeToClearToastsEvent(scope._onClearToasts); 401 }, 402 controller: [ 403 '$scope', '$element', '$attrs', function ($scope, $element, $attrs) { 404 // Called on mouseover 405 $scope.stopTimer = function (toast) { 406 if ($scope.config.mouseoverTimer === true) { 407 if (toast.timeoutPromise) { 408 $interval.cancel(toast.timeoutPromise); 409 toast.timeoutPromise = null; 410 } 411 } 412 }; 413 414 // Called on mouseout 415 $scope.restartTimer = function (toast) { 416 if ($scope.config.mouseoverTimer === true) {
417 if (!toast.timeoutPromise) { 418 $scope.configureTimer(toast); 419 } 420 } else if (toast.timeoutPromise === null) { 421 $scope.removeToast(toast.id); 422 } 423 }; 424 425 $scope.click = function (toast, isCloseButton) { 426 if ($scope.config.tap === true || (toast.showCloseButton === true && isCloseButton === true)) { 427 var removeToast = true; 428 if (toast.clickHandler) { 429 if (angular.isFunction(toast.clickHandler)) { 430 removeToast = toast.clickHandler(toast, isCloseButton); 431 } else if (angular.isFunction($scope.$parent.$eval(toast.clickHandler))) { 432 removeToast = $scope.$parent.$eval(toast.clickHandler)(toast, isCloseButton); 433 } else { 434 console.log("TOAST-NOTE: Your click handler is not inside a parent scope of toaster-container."); 435 } 436 } 437 if (removeToast) { 438 $scope.removeToast(toast.id); 439 } 440 } 441 }; 442 }], 443 template: 444 '<div id="toast-container" ng-class="[config.position, config.animation]">' + 445 '<div ng-repeat="toaster in toasters" class="toast" ng-class="toaster.type" ng-click="click(toaster)" ng-mouseover="stopTimer(toaster)" ng-mouseout="restartTimer(toaster)">' + 446 '<button type="button" class="toast-close-button" ng-show="toaster.showCloseButton" ng-click="click(toaster, true)">×</button>' + 447 '<div ng-class="config.title">{{toaster.title}}</div>' + 448 '<div ng-class="config.message" ng-switch on="toaster.bodyOutputType">' + 449 '<div ng-switch-when="trustedHtml" ng-bind-html="toaster.html"></div>' + 450 '<div ng-switch-when="template"><div ng-include="toaster.bodyTemplate"></div></div>' + 451 '<div ng-switch-when="templateWithData"><div ng-include="toaster.bodyTemplate"></div></div>' + 452 '<div ng-switch-when="directive"><div directive-template directive-name="{{toaster.html}}"></div></div>' + 453 '<div ng-switch-default >{{toaster.body}}</div>' + 454 '</div>' + 455 '</div>' + 456 '</div>' 457 }; 458 }] 459 ); 460})(window, document);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.