PluginProbe
Boxzilla – WordPress Popup Builder / 3.0.3
Boxzilla – WordPress Popup Builder v3.0.3
3.4.11 3.4.10 3.4.9 3.4.3 3.4.4 3.4.5 3.4.6 3.4.7 3.4.8 trunk 3.0 3.0.1 3.0.2 3.0.3 3.1 3.1.1 3.1.10 3.1.11 3.1.12 3.1.13 3.1.14 3.1.15 3.1.16 3.1.17 3.1.18 All 72 releases
boxzilla / assets / js / script.js

script.js in Boxzilla – WordPress Popup Builder 3.0.3, at assets/js/script.js

1,170 lines 36.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function () { var require = undefined; var module = undefined; var exports = undefined; var define = undefined; (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
2 'use strict';
3
4 var Boxzilla = require('boxzilla');
5 var options = window.boxzilla_options;
6 var isLoggedIn = document.body.className.indexOf('logged-in') > -1;
7
8 // print message when test mode is enabled
9 if( isLoggedIn && options.testMode ) {
10 console.log( 'Boxzilla: Test mode is enabled. Please disable test mode if you\'re done testing.' );
11 }
12
13 // init boxzilla
14 Boxzilla.init();
15
16 // create boxes from options
17 for( var i=0; i < options.boxes.length; i++ ) {
18 // get opts
19 var boxOpts = options.boxes[i];
20 boxOpts.testMode = isLoggedIn && options.testMode;
21
22 // fix http:// links in box content....
23 if( window.location.origin.substring(0, 5) === "https" ) {
24 boxOpts.content = boxOpts.content.replace(window.location.origin.replace("https", "http"), window.location.origin);
25 }
26
27 // create box
28 var box = Boxzilla.create( boxOpts.id, boxOpts);
29
30 // add custom css to box
31 css(box.element, boxOpts.css);
32 }
33
34 // helper function for setting CSS styles
35 function css(element, styles) {
36 if( styles.background_color ) {
37 element.style.background = styles.background_color;
38 }
39
40 if( styles.color ) {
41 element.style.color = styles.color;
42 }
43
44 if( styles.border_color ) {
45 element.style.borderColor = styles.border_color;
46 }
47
48 if( styles.border_width ) {
49 element.style.borderWidth = parseInt(styles.border_width) + "px";
50 }
51
52 if( styles.border_style ) {
53 element.style.borderStyle = styles.border_style;
54 }
55
56 if( styles.width ) {
57 element.style.maxWidth = parseInt(styles.width) + "px";
58 }
59 }
60
61 /**
62 * If a MailChimp for WordPress form was submitted, open the box containing that form (if any)
63 *
64 * TODO: Just set location hash from MailChimp for WP?
65 */
66 window.addEventListener('load', function() {
67 if( typeof(window.mc4wp_forms_config) === "object" && window.mc4wp_forms_config.submitted_form ) {
68 var selector = '#' + window.mc4wp_forms_config.submitted_form.element_id;
69 var boxes = Boxzilla.boxes;
70 for( var boxId in boxes ) {
71 if(!boxes.hasOwnProperty(boxId)) { continue; }
72 var box = boxes[boxId];
73 if( box.element.querySelector(selector)) {
74 box.show();
75 return;
76 }
77 }
78 }
79 });
80
81 window.Boxzilla = Boxzilla;
82 },{"boxzilla":3}],2:[function(require,module,exports){
83 'use strict';
84
85 var $ = window.jQuery,
86 defaults = {
87 'animation': 'fade',
88 'rehide': false,
89 'content': '',
90 'cookieTime': 0,
91 'icon': '&times',
92 'minimumScreenWidth': 0,
93 'position': 'center',
94 'testMode': false,
95 'trigger': false,
96 'closable': true
97 },
98 Boxzilla;
99
100 /**
101 * Merge 2 objects, values of the latter overwriting the former.
102 *
103 * @param obj1
104 * @param obj2
105 * @returns {*}
106 */
107 function merge( obj1, obj2 ) {
108 var obj3 = {};
109 for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; }
110 for (var attrname in obj2) { obj3[attrname] = obj2[attrname]; }
111 return obj3;
112 }
113
114 // Box Object
115 var Box = function( id, config ) {
116 this.id = id;
117
118 // store config values
119 this.config = merge(defaults, config);
120
121 // store ref to overlay
122 this.overlay = document.getElementById('boxzilla-overlay');
123
124 // state
125 this.visible = false;
126 this.closed = false;
127 this.triggered = false;
128 this.triggerHeight = 0;
129 this.cookieSet = false;
130
131 // if a trigger was given, calculate values once and store
132 if( this.config.trigger ) {
133 if( this.config.trigger.method === 'percentage' || this.config.trigger.method === 'element' ) {
134 this.triggerHeight = this.calculateTriggerHeight();
135 }
136
137 this.cookieSet = this.isCookieSet();
138 }
139
140 // create dom element for this box
141 this.element = this.dom();
142 this.$element = $(this.element);
143
144 // further initialise the box
145 this.events();
146 };
147
148 // initialise the box
149 Box.prototype.events = function() {
150 var box = this;
151
152 // attach event to "close" icon inside box
153 this.$element.find('.boxzilla-close-icon').click(box.dismiss.bind(this));
154
155 this.$element.on('click', 'a', function(e) {
156 Boxzilla.trigger('box.interactions.link', [ box, e.target ] );
157 });
158
159 this.$element.on('submit', 'form', function(e) {
160 box.setCookie();
161 Boxzilla.trigger('box.interactions.form', [ box, e.target ]);
162 });
163
164 // attach event to all links referring #boxzilla-{box_id}
165 $(document.body).on('click', 'a[href="#boxzilla-' + box.id + '"]', function() {
166 box.toggle();
167 return false;
168 });
169
170 // maybe show box right away
171 if( this.fits() && this.locationHashRefersBox() ) {
172 $(window).load(this.show.bind(this));
173 }
174
175 };
176
177 // generate dom elements for this box
178 Box.prototype.dom = function() {
179 var wrapper = document.createElement('div');
180 wrapper.className = 'boxzilla-container boxzilla-' + this.config.position + '-container';
181
182 var box = document.createElement('div');
183 box.setAttribute('id', 'boxzilla-' + this.id);
184 box.className = 'boxzilla boxzilla-' + this.id + ' boxzilla-' + this.config.position;
185 box.style.display = 'none';
186 wrapper.appendChild(box);
187
188 var content = document.createElement('div');
189 content.className = 'boxzilla-content';
190 content.innerHTML = this.config.content;
191 box.appendChild(content);
192
193 // remove <script> from box content and append them to the document body
194 var scripts = content.querySelectorAll('script');
195 if(scripts.length) {
196 var script = document.createElement('script');
197 for( var i=0; i<scripts.length; i++ ) {
198 script.appendChild(document.createTextNode(scripts[i].text));
199 scripts[i].parentNode.removeChild(scripts[i]);
200 }
201 document.body.appendChild(script);
202 }
203
204 // for safety measure, restore jQuery
205 window.jQuery = $;
206
207 if( this.config.closable && this.config.icon ) {
208 var icon = document.createElement('span');
209 icon.className = "boxzilla-close-icon";
210 icon.innerHTML = this.config.icon;
211 box.appendChild(icon);
212 }
213
214 document.body.appendChild(wrapper);
215
216 return box;
217 };
218
219 // set (calculate) custom box styling depending on box options
220 Box.prototype.setCustomBoxStyling = function() {
221
222 // reset element to its initial state
223 this.element.style.overflowY = 'auto';
224 this.element.style.maxHeight = 'none';
225
226 // get new dimensions
227 var windowHeight = window.innerHeight;
228 var boxHeight = this.$element.outerHeight();
229
230 // add scrollbar to box and limit height
231 if( boxHeight > windowHeight ) {
232 this.element.style.maxHeight = windowHeight + "px";
233 this.element.style.overflowY = 'scroll';
234 }
235
236 // set new top margin for boxes which are centered
237 if( this.config.position === 'center' ) {
238 var newTopMargin = ( ( windowHeight - boxHeight ) / 2 );
239 newTopMargin = newTopMargin >= 0 ? newTopMargin : 0;
240 this.element.style.marginTop = newTopMargin + "px";
241 }
242
243 };
244
245 // toggle visibility of the box
246 Box.prototype.toggle = function(show) {
247
248 // revert visibility if no explicit argument is given
249 if( typeof( show ) === "undefined" ) {
250 show = ! this.visible;
251 }
252
253 // do nothing if element is being animated
254 if( this.$element.is(':animated') ) {
255 return false;
256 }
257
258 // is box already at desired visibility?
259 if( show === this.visible ) {
260 return false;
261 }
262
263 // if box should be hidden but is not closable, bail.
264 if( ! show && ! this.config.closable ) {
265 return false;
266 }
267
268 // set new visibility status
269 this.visible = show;
270
271 // calculate custom styling for which CSS is "too stupid"
272 this.setCustomBoxStyling();
273
274 // fadein / fadeout the overlay if position is "center"
275 if( this.config.position === 'center' ) {
276 $(this.overlay).fadeToggle('slow');
277 }
278
279 // trigger event
280 Boxzilla.trigger('box.' + ( show ? 'show' : 'hide' ), [ this ] );
281
282 // show or hide box using selected animation
283 if( this.config.animation === 'fade' ) {
284 this.$element.fadeToggle( 'slow' );
285 } else {
286 this.$element.slideToggle( 'slow' );
287 }
288
289 // // focus on first input field in box
290 // this.$element.find('input').first().focus();
291
292 return true;
293 };
294
295 // show the box
296 Box.prototype.show = function() {
297 return this.toggle(true);
298 };
299
300 // hide the box
301 Box.prototype.hide = function() {
302 return this.toggle(false);
303 };
304
305 // calculate trigger height
306 Box.prototype.calculateTriggerHeight = function() {
307 var triggerHeight = 0;
308
309 if( this.config.trigger.method === 'element' ) {
310 var $triggerElement = $(this.config.trigger.value).first();
311 triggerHeight = ( $triggerElement.length > 0 ) ? $triggerElement.offset().top : 0;
312 } else if( this.config.trigger.method === 'percentage' ) {
313 triggerHeight = ( this.config.trigger.value / 100 * $(document).height() );
314 }
315
316 return triggerHeight;
317 };
318
319 // set cookie that disables automatically showing the box
320 Box.prototype.setCookie = function() {
321 // do nothing if cookieTime evaluates to false
322 if(! this.config.cookieTime) {
323 return;
324 }
325
326 var expiryDate = new Date();
327 expiryDate.setDate( expiryDate.getDate() + this.config.cookieTime );
328 document.cookie = 'boxzilla_box_'+ this.id + '=true; expires='+ expiryDate.toUTCString() +'; path=/';
329 };
330
331 // checks whether window.location.hash equals the box element ID or that of any element inside the box
332 Box.prototype.locationHashRefersBox = function() {
333
334 if( ! window.location.hash || 0 === window.location.hash.length ) {
335 return false;
336 }
337
338 var elementId = window.location.hash.substring(1);
339 if( elementId === this.element.id ) {
340 return true;
341 } else if( this.element.querySelector('#' + elementId) ) {
342 return true;
343 }
344
345 return false;
346 };
347
348 Box.prototype.fits = function() {
349 if( this.config.minimumScreenWidth <= 0 ) {
350 return true;
351 }
352
353 return window.innerWidth > this.config.minimumScreenWidth
354 };
355
356 // is this box enabled?
357 Box.prototype.mayAutoShow = function() {
358
359 // don't show if box was closed (dismissed) before
360 if( this.closed ) {
361 return false;
362 }
363
364 // check if box fits on given minimum screen width
365 if( ! this.fits() ) {
366 return false;
367 }
368
369 // if trigger empty or error in calculating triggerHeight, return false
370 if( ! this.config.trigger ) {
371 return false;
372 }
373
374 // rely on cookie value (show if not set, don't show if set)
375 return ! this.cookieSet;
376 };
377
378 Box.prototype.mayRehide = function() {
379 return this.config.rehide && this.triggered;
380 };
381
382 Box.prototype.isCookieSet = function() {
383 // always show on test mode
384 if( this.config.testMode ) {
385 return false;
386 }
387
388 // check for cookie
389 if( ! this.config.cookieTime ) {
390 return false;
391 }
392
393 var cookieSet = document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + 'boxzilla_box_' + this.id + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1") === "true";
394 return cookieSet;
395
396 };
397
398 Box.prototype.trigger = function() {
399 var shown = this.show();
400 if( shown ) {
401 this.triggered = true;
402 }
403 };
404
405 Box.prototype.dismiss = function() {
406 this.hide();
407 this.setCookie();
408 this.closed = true;
409 Boxzilla.trigger('box.dismiss', [ this ]);
410 };
411
412 module.exports = function(_Boxzilla) {
413 Boxzilla = _Boxzilla;
414 return Box;
415 };
416 },{}],3:[function(require,module,exports){
417 'use strict';
418
419 var $ = window.jQuery,
420 EventEmitter = require('wolfy87-eventemitter'),
421 Boxzilla = Object.create(EventEmitter.prototype),
422 Box = require('./Box.js')(Boxzilla),
423 Timer = require('./Timer.js'),
424 boxes = {},
425 windowHeight = window.innerHeight,
426 overlay = document.createElement('div'),
427 exitIntentDelayTimer,
428 exitIntentTriggered,
429 siteTimer = new Timer(sessionStorage.getItem('boxzilla_timer') || 0),
430 pageTimer = new Timer(0),
431 pageViews = sessionStorage.getItem('boxzilla_pageviews') || 0;
432
433 function each( obj, callback ) {
434 for( var key in obj ) {
435 if(! obj.hasOwnProperty(key)) continue;
436 callback(obj[key]);
437 }
438 }
439
440 function throttle(fn, threshhold, scope) {
441 threshhold || (threshhold = 250);
442 var last,
443 deferTimer;
444 return function () {
445 var context = scope || this;
446
447 var now = +new Date,
448 args = arguments;
449 if (last && now < last + threshhold) {
450 // hold on to it
451 clearTimeout(deferTimer);
452 deferTimer = setTimeout(function () {
453 last = now;
454 fn.apply(context, args);
455 }, threshhold);
456 } else {
457 last = now;
458 fn.apply(context, args);
459 }
460 };
461 }
462
463 // "keyup" listener
464 function onKeyUp(e) {
465 if (e.keyCode == 27) {
466 Boxzilla.dismiss();
467 }
468 }
469
470 // check "pageviews" criteria for each box
471 function checkPageViewsCriteria() {
472 each(boxes, function(box) {
473 if( ! box.mayAutoShow() ) {
474 return;
475 }
476
477 if( box.config.trigger.method === 'pageviews' && pageViews >= box.config.trigger.value ) {
478 box.trigger();
479 }
480 });
481 }
482
483 // check time trigger criteria for each box
484 function checkTimeCriteria() {
485 each(boxes, function(box) {
486 if( ! box.mayAutoShow() ) {
487 return;
488 }
489
490 // check "time on site" trigger
491 if (box.config.trigger.method === 'time_on_site' && siteTimer.time >= box.config.trigger.value) {
492 box.trigger();
493 }
494
495 // check "time on page" trigger
496 if (box.config.trigger.method === 'time_on_page' && pageTimer.time >= box.config.trigger.value) {
497 box.trigger();
498 }
499 });
500 }
501
502 // check triggerHeight criteria for all boxes
503 function checkHeightCriteria() {
504 var scrollY = window.scrollY;
505 var scrollHeight = scrollY + ( windowHeight * 0.667 );
506
507 each(boxes, function(box) {
508 if( ! box.mayAutoShow() || box.triggerHeight <= 0 ) {
509 return;
510 }
511
512 if( scrollHeight > box.triggerHeight ) {
513 box.trigger();
514 } else if( box.mayRehide() ) {
515 box.hide();
516 }
517 });
518 }
519
520 // recalculate heights and variables based on height
521 function recalculateHeights() {
522 windowHeight = window.innerHeight;
523
524 each(boxes, function(box) {
525 box.setCustomBoxStyling();
526 });
527 }
528
529 function onOverlayClick(e) {
530 var x = e.offsetX;
531 var y = e.offsetY;
532
533 // calculate if click was near a box to avoid closing it (click error margin)
534 each(boxes, function(box) {
535 var rect = box.element.getBoundingClientRect();
536 var margin = 100 + ( window.innerWidth * 0.05 );
537
538 // if click was not anywhere near box, dismiss it.
539 if( x < ( rect.left - margin ) || x > ( rect.right + margin ) || y < ( rect.top - margin ) || y > ( rect.bottom + margin ) ) {
540 box.dismiss();
541 }
542 });
543 }
544
545 function triggerExitIntent() {
546 if(exitIntentTriggered) return;
547
548 each(boxes, function(box) {
549 if(box.mayAutoShow() && box.config.trigger.method === 'exit_intent' ) {
550 box.trigger();
551 }
552 });
553
554 exitIntentTriggered = true;
555 }
556
557 function onMouseLeave(e) {
558 var delay = 400;
559
560 // did mouse leave at top of window?
561 if( e.clientY <= 0 ) {
562 exitIntentDelayTimer = window.setTimeout(triggerExitIntent, delay);
563 }
564 }
565
566 function onMouseEnter() {
567 if( exitIntentDelayTimer ) {
568 window.clearInterval(exitIntentDelayTimer);
569 exitIntentDelayTimer = null;
570 }
571 }
572
573 var timers = {
574 start: function() {
575 var sessionTime = sessionStorage.getItem('boxzilla_timer');
576 if( sessionTime ) siteTimer.time = sessionTime;
577 siteTimer.start();
578 pageTimer.start();
579 },
580 stop: function() {
581 sessionStorage.setItem('boxzilla_timer', siteTimer.time);
582 siteTimer.stop();
583 pageTimer.stop();
584 }
585 };
586
587 // initialise & add event listeners
588 Boxzilla.init = function() {
589 var html = document.documentElement;
590
591 // add overlay element to dom
592 overlay.id = 'boxzilla-overlay';
593 document.body.appendChild(overlay);
594
595 // event binds
596 $(window).on('scroll', throttle(checkHeightCriteria));
597 $(window).on('resize', throttle(recalculateHeights));
598 $(window).on('load', recalculateHeights );
599 $(html).on('mouseleave', onMouseLeave);
600 $(html).on('mouseenter', onMouseEnter);
601 $(html).on('keyup', onKeyUp);
602 $(overlay).click(onOverlayClick);
603 window.setInterval(checkTimeCriteria, 1000);
604 window.setTimeout(checkPageViewsCriteria, 1000 );
605
606 timers.start();
607 $(window).on('focus', timers.start);
608 $(window).on('beforeunload', function() {
609 timers.stop();
610 sessionStorage.setItem('boxzilla_pageviews', ++pageViews);
611 });
612 $(window).on('blur', timers.stop);
613
614 Boxzilla.trigger('ready');
615 };
616
617 /**
618 * Create a new Box
619 *
620 * @param string id
621 * @param object opts
622 *
623 * @returns Box
624 */
625 Boxzilla.create = function(id, opts) {
626 boxes[id] = new Box(id, opts);
627 return boxes[id];
628 };
629
630 // dismiss a single box (or all by omitting id param)
631 Boxzilla.dismiss = function(id) {
632 // if no id given, dismiss all current open boxes
633 if( typeof(id) === "undefined" ) {
634 each(boxes, function(box) { box.dismiss(); });
635 } else if( typeof( boxes[id] ) === "object" ) {
636 boxes[id].dismiss();
637 }
638 };
639
640 Boxzilla.hide = function(id) {
641 if( typeof(id) === "undefined" ) {
642 each(boxes, function(box) { box.hide(); });
643 } else if( typeof( boxes[id] ) === "object" ) {
644 boxes[id].hide();
645 }
646 };
647
648 Boxzilla.show = function(id) {
649 if( typeof(id) === "undefined" ) {
650 each(boxes, function(box) { box.show(); });
651 } else if( typeof( boxes[id] ) === "object" ) {
652 boxes[id].show();
653 }
654 };
655
656 Boxzilla.toggle = function(id) {
657 if( typeof(id) === "undefined" ) {
658 each(boxes, function(box) { box.toggle(); });
659 } else if( typeof( boxes[id] ) === "object" ) {
660 boxes[id].toggle();
661 }
662 };
663
664 window.Boxzilla = Boxzilla;
665
666 if ( typeof module !== 'undefined' && module.exports ) {
667 module.exports = Boxzilla;
668 }
669 },{"./Box.js":2,"./Timer.js":4,"wolfy87-eventemitter":5}],4:[function(require,module,exports){
670 'use strict';
671
672 var Timer = function(start) {
673 this.time = start;
674 this.interval = 0;
675 };
676
677 Timer.prototype.tick = function() {
678 this.time++;
679 };
680
681 Timer.prototype.start = function() {
682 if( ! this.interval ) {
683 this.interval = window.setInterval(this.tick.bind(this), 1000);
684 }
685 };
686
687 Timer.prototype.stop = function() {
688 window.clearInterval(this.interval);
689 this.interval = 0;
690 };
691
692 module.exports = Timer;
693 },{}],5:[function(require,module,exports){
694 /*!
695 * EventEmitter v4.2.11 - git.io/ee
696 * Unlicense - http://unlicense.org/
697 * Oliver Caldwell - http://oli.me.uk/
698 * @preserve
699 */
700
701 ;(function () {
702 'use strict';
703
704 /**
705 * Class for managing events.
706 * Can be extended to provide event functionality in other classes.
707 *
708 * @class EventEmitter Manages event registering and emitting.
709 */
710 function EventEmitter() {}
711
712 // Shortcuts to improve speed and size
713 var proto = EventEmitter.prototype;
714 var exports = this;
715 var originalGlobalValue = exports.EventEmitter;
716
717 /**
718 * Finds the index of the listener for the event in its storage array.
719 *
720 * @param {Function[]} listeners Array of listeners to search through.
721 * @param {Function} listener Method to look for.
722 * @return {Number} Index of the specified listener, -1 if not found
723 * @api private
724 */
725 function indexOfListener(listeners, listener) {
726 var i = listeners.length;
727 while (i--) {
728 if (listeners[i].listener === listener) {
729 return i;
730 }
731 }
732
733 return -1;
734 }
735
736 /**
737 * Alias a method while keeping the context correct, to allow for overwriting of target method.
738 *
739 * @param {String} name The name of the target method.
740 * @return {Function} The aliased method
741 * @api private
742 */
743 function alias(name) {
744 return function aliasClosure() {
745 return this[name].apply(this, arguments);
746 };
747 }
748
749 /**
750 * Returns the listener array for the specified event.
751 * Will initialise the event object and listener arrays if required.
752 * Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object containing bar and baz. But only if you have either defined them with defineEvent or added some listeners to them.
753 * Each property in the object response is an array of listener functions.
754 *
755 * @param {String|RegExp} evt Name of the event to return the listeners from.
756 * @return {Function[]|Object} All listener functions for the event.
757 */
758 proto.getListeners = function getListeners(evt) {
759 var events = this._getEvents();
760 var response;
761 var key;
762
763 // Return a concatenated array of all matching events if
764 // the selector is a regular expression.
765 if (evt instanceof RegExp) {
766 response = {};
767 for (key in events) {
768 if (events.hasOwnProperty(key) && evt.test(key)) {
769 response[key] = events[key];
770 }
771 }
772 }
773 else {
774 response = events[evt] || (events[evt] = []);
775 }
776
777 return response;
778 };
779
780 /**
781 * Takes a list of listener objects and flattens it into a list of listener functions.
782 *
783 * @param {Object[]} listeners Raw listener objects.
784 * @return {Function[]} Just the listener functions.
785 */
786 proto.flattenListeners = function flattenListeners(listeners) {
787 var flatListeners = [];
788 var i;
789
790 for (i = 0; i < listeners.length; i += 1) {
791 flatListeners.push(listeners[i].listener);
792 }
793
794 return flatListeners;
795 };
796
797 /**
798 * Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful.
799 *
800 * @param {String|RegExp} evt Name of the event to return the listeners from.
801 * @return {Object} All listener functions for an event in an object.
802 */
803 proto.getListenersAsObject = function getListenersAsObject(evt) {
804 var listeners = this.getListeners(evt);
805 var response;
806
807 if (listeners instanceof Array) {
808 response = {};
809 response[evt] = listeners;
810 }
811
812 return response || listeners;
813 };
814
815 /**
816 * Adds a listener function to the specified event.
817 * The listener will not be added if it is a duplicate.
818 * If the listener returns true then it will be removed after it is called.
819 * If you pass a regular expression as the event name then the listener will be added to all events that match it.
820 *
821 * @param {String|RegExp} evt Name of the event to attach the listener to.
822 * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
823 * @return {Object} Current instance of EventEmitter for chaining.
824 */
825 proto.addListener = function addListener(evt, listener) {
826 var listeners = this.getListenersAsObject(evt);
827 var listenerIsWrapped = typeof listener === 'object';
828 var key;
829
830 for (key in listeners) {
831 if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) {
832 listeners[key].push(listenerIsWrapped ? listener : {
833 listener: listener,
834 once: false
835 });
836 }
837 }
838
839 return this;
840 };
841
842 /**
843 * Alias of addListener
844 */
845 proto.on = alias('addListener');
846
847 /**
848 * Semi-alias of addListener. It will add a listener that will be
849 * automatically removed after its first execution.
850 *
851 * @param {String|RegExp} evt Name of the event to attach the listener to.
852 * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
853 * @return {Object} Current instance of EventEmitter for chaining.
854 */
855 proto.addOnceListener = function addOnceListener(evt, listener) {
856 return this.addListener(evt, {
857 listener: listener,
858 once: true
859 });
860 };
861
862 /**
863 * Alias of addOnceListener.
864 */
865 proto.once = alias('addOnceListener');
866
867 /**
868 * Defines an event name. This is required if you want to use a regex to add a listener to multiple events at once. If you don't do this then how do you expect it to know what event to add to? Should it just add to every possible match for a regex? No. That is scary and bad.
869 * You need to tell it what event names should be matched by a regex.
870 *
871 * @param {String} evt Name of the event to create.
872 * @return {Object} Current instance of EventEmitter for chaining.
873 */
874 proto.defineEvent = function defineEvent(evt) {
875 this.getListeners(evt);
876 return this;
877 };
878
879 /**
880 * Uses defineEvent to define multiple events.
881 *
882 * @param {String[]} evts An array of event names to define.
883 * @return {Object} Current instance of EventEmitter for chaining.
884 */
885 proto.defineEvents = function defineEvents(evts) {
886 for (var i = 0; i < evts.length; i += 1) {
887 this.defineEvent(evts[i]);
888 }
889 return this;
890 };
891
892 /**
893 * Removes a listener function from the specified event.
894 * When passed a regular expression as the event name, it will remove the listener from all events that match it.
895 *
896 * @param {String|RegExp} evt Name of the event to remove the listener from.
897 * @param {Function} listener Method to remove from the event.
898 * @return {Object} Current instance of EventEmitter for chaining.
899 */
900 proto.removeListener = function removeListener(evt, listener) {
901 var listeners = this.getListenersAsObject(evt);
902 var index;
903 var key;
904
905 for (key in listeners) {
906 if (listeners.hasOwnProperty(key)) {
907 index = indexOfListener(listeners[key], listener);
908
909 if (index !== -1) {
910 listeners[key].splice(index, 1);
911 }
912 }
913 }
914
915 return this;
916 };
917
918 /**
919 * Alias of removeListener
920 */
921 proto.off = alias('removeListener');
922
923 /**
924 * Adds listeners in bulk using the manipulateListeners method.
925 * If you pass an object as the second argument you can add to multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. You can also pass it an event name and an array of listeners to be added.
926 * You can also pass it a regular expression to add the array of listeners to all events that match it.
927 * Yeah, this function does quite a bit. That's probably a bad thing.
928 *
929 * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add to multiple events at once.
930 * @param {Function[]} [listeners] An optional array of listener functions to add.
931 * @return {Object} Current instance of EventEmitter for chaining.
932 */
933 proto.addListeners = function addListeners(evt, listeners) {
934 // Pass through to manipulateListeners
935 return this.manipulateListeners(false, evt, listeners);
936 };
937
938 /**
939 * Removes listeners in bulk using the manipulateListeners method.
940 * If you pass an object as the second argument you can remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
941 * You can also pass it an event name and an array of listeners to be removed.
942 * You can also pass it a regular expression to remove the listeners from all events that match it.
943 *
944 * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to remove from multiple events at once.
945 * @param {Function[]} [listeners] An optional array of listener functions to remove.
946 * @return {Object} Current instance of EventEmitter for chaining.
947 */
948 proto.removeListeners = function removeListeners(evt, listeners) {
949 // Pass through to manipulateListeners
950 return this.manipulateListeners(true, evt, listeners);
951 };
952
953 /**
954 * Edits listeners in bulk. The addListeners and removeListeners methods both use this to do their job. You should really use those instead, this is a little lower level.
955 * The first argument will determine if the listeners are removed (true) or added (false).
956 * If you pass an object as the second argument you can add/remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
957 * You can also pass it an event name and an array of listeners to be added/removed.
958 * You can also pass it a regular expression to manipulate the listeners of all events that match it.
959 *
960 * @param {Boolean} remove True if you want to remove listeners, false if you want to add.
961 * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add/remove from multiple events at once.
962 * @param {Function[]} [listeners] An optional array of listener functions to add/remove.
963 * @return {Object} Current instance of EventEmitter for chaining.
964 */
965 proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) {
966 var i;
967 var value;
968 var single = remove ? this.removeListener : this.addListener;
969 var multiple = remove ? this.removeListeners : this.addListeners;
970
971 // If evt is an object then pass each of its properties to this method
972 if (typeof evt === 'object' && !(evt instanceof RegExp)) {
973 for (i in evt) {
974 if (evt.hasOwnProperty(i) && (value = evt[i])) {
975 // Pass the single listener straight through to the singular method
976 if (typeof value === 'function') {
977 single.call(this, i, value);
978 }
979 else {
980 // Otherwise pass back to the multiple function
981 multiple.call(this, i, value);
982 }
983 }
984 }
985 }
986 else {
987 // So evt must be a string
988 // And listeners must be an array of listeners
989 // Loop over it and pass each one to the multiple method
990 i = listeners.length;
991 while (i--) {
992 single.call(this, evt, listeners[i]);
993 }
994 }
995
996 return this;
997 };
998
999 /**
1000 * Removes all listeners from a specified event.
1001 * If you do not specify an event then all listeners will be removed.
1002 * That means every event will be emptied.
1003 * You can also pass a regex to remove all events that match it.
1004 *
1005 * @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed.
1006 * @return {Object} Current instance of EventEmitter for chaining.
1007 */
1008 proto.removeEvent = function removeEvent(evt) {
1009 var type = typeof evt;
1010 var events = this._getEvents();
1011 var key;
1012
1013 // Remove different things depending on the state of evt
1014 if (type === 'string') {
1015 // Remove all listeners for the specified event
1016 delete events[evt];
1017 }
1018 else if (evt instanceof RegExp) {
1019 // Remove all events matching the regex.
1020 for (key in events) {
1021 if (events.hasOwnProperty(key) && evt.test(key)) {
1022 delete events[key];
1023 }
1024 }
1025 }
1026 else {
1027 // Remove all listeners in all events
1028 delete this._events;
1029 }
1030
1031 return this;
1032 };
1033
1034 /**
1035 * Alias of removeEvent.
1036 *
1037 * Added to mirror the node API.
1038 */
1039 proto.removeAllListeners = alias('removeEvent');
1040
1041 /**
1042 * Emits an event of your choice.
1043 * When emitted, every listener attached to that event will be executed.
1044 * If you pass the optional argument array then those arguments will be passed to every listener upon execution.
1045 * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately.
1046 * So they will not arrive within the array on the other side, they will be separate.
1047 * You can also pass a regular expression to emit to all events that match it.
1048 *
1049 * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
1050 * @param {Array} [args] Optional array of arguments to be passed to each listener.
1051 * @return {Object} Current instance of EventEmitter for chaining.
1052 */
1053 proto.emitEvent = function emitEvent(evt, args) {
1054 var listenersMap = this.getListenersAsObject(evt);
1055 var listeners;
1056 var listener;
1057 var i;
1058 var key;
1059 var response;
1060
1061 for (key in listenersMap) {
1062 if (listenersMap.hasOwnProperty(key)) {
1063 listeners = listenersMap[key].slice(0);
1064 i = listeners.length;
1065
1066 while (i--) {
1067 // If the listener returns true then it shall be removed from the event
1068 // The function is executed either with a basic call or an apply if there is an args array
1069 listener = listeners[i];
1070
1071 if (listener.once === true) {
1072 this.removeListener(evt, listener.listener);
1073 }
1074
1075 response = listener.listener.apply(this, args || []);
1076
1077 if (response === this._getOnceReturnValue()) {
1078 this.removeListener(evt, listener.listener);
1079 }
1080 }
1081 }
1082 }
1083
1084 return this;
1085 };
1086
1087 /**
1088 * Alias of emitEvent
1089 */
1090 proto.trigger = alias('emitEvent');
1091
1092 /**
1093 * Subtly different from emitEvent in that it will pass its arguments on to the listeners, as opposed to taking a single array of arguments to pass on.
1094 * As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it.
1095 *
1096 * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
1097 * @param {...*} Optional additional arguments to be passed to each listener.
1098 * @return {Object} Current instance of EventEmitter for chaining.
1099 */
1100 proto.emit = function emit(evt) {
1101 var args = Array.prototype.slice.call(arguments, 1);
1102 return this.emitEvent(evt, args);
1103 };
1104
1105 /**
1106 * Sets the current value to check against when executing listeners. If a
1107 * listeners return value matches the one set here then it will be removed
1108 * after execution. This value defaults to true.
1109 *
1110 * @param {*} value The new value to check for when executing listeners.
1111 * @return {Object} Current instance of EventEmitter for chaining.
1112 */
1113 proto.setOnceReturnValue = function setOnceReturnValue(value) {
1114 this._onceReturnValue = value;
1115 return this;
1116 };
1117
1118 /**
1119 * Fetches the current value to check against when executing listeners. If
1120 * the listeners return value matches this one then it should be removed
1121 * automatically. It will return true by default.
1122 *
1123 * @return {*|Boolean} The current value to check for or the default, true.
1124 * @api private
1125 */
1126 proto._getOnceReturnValue = function _getOnceReturnValue() {
1127 if (this.hasOwnProperty('_onceReturnValue')) {
1128 return this._onceReturnValue;
1129 }
1130 else {
1131 return true;
1132 }
1133 };
1134
1135 /**
1136 * Fetches the events object and creates one if required.
1137 *
1138 * @return {Object} The events storage object.
1139 * @api private
1140 */
1141 proto._getEvents = function _getEvents() {
1142 return this._events || (this._events = {});
1143 };
1144
1145 /**
1146 * Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version.
1147 *
1148 * @return {Function} Non conflicting EventEmitter class.
1149 */
1150 EventEmitter.noConflict = function noConflict() {
1151 exports.EventEmitter = originalGlobalValue;
1152 return EventEmitter;
1153 };
1154
1155 // Expose the class either via AMD, CommonJS or the global object
1156 if (typeof define === 'function' && define.amd) {
1157 define(function () {
1158 return EventEmitter;
1159 });
1160 }
1161 else if (typeof module === 'object' && module.exports){
1162 module.exports = EventEmitter;
1163 }
1164 else {
1165 exports.EventEmitter = EventEmitter;
1166 }
1167 }.call(this));
1168
1169 },{}]},{},[1]);
1170 ; })();