PluginProbe
Boxzilla – WordPress Popup Builder / 3.1.4
Boxzilla – WordPress Popup Builder v3.1.4
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.1.4, at assets/js/script.js

1,396 lines 44.3 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":4}],2:[function(require,module,exports){
83 var duration = 320;
84
85 function css(element, styles) {
86 for(var property in styles) {
87 element.style[property] = styles[property];
88 }
89 }
90
91 function initObjectProperties(properties, value) {
92 var newObject = {};
93 for(var i=0; i<properties.length; i++) {
94 newObject[properties[i]] = value;
95 }
96 return newObject;
97 }
98
99 function copyObjectProperties(properties, object) {
100 var newObject = {}
101 for(var i=0; i<properties.length; i++) {
102 newObject[properties[i]] = object[properties[i]];
103 }
104 return newObject;
105 }
106
107 /**
108 * Checks if the given element is currently being animated.
109 *
110 * @param element
111 * @returns {boolean}
112 */
113 function animated(element) {
114 return !! element.getAttribute('data-animated');
115 }
116
117 /**
118 * Toggles the element using the given animation.
119 *
120 * @param element
121 * @param animation Either "fade" or "slide"
122 */
123 function toggle(element, animation) {
124 var nowVisible = element.style.display != 'none' || element.offsetLeft > 0;
125
126 // create clone for reference
127 var clone = element.cloneNode(true);
128 var cleanup = function() {
129 element.removeAttribute('data-animated');
130 element.setAttribute('style', clone.getAttribute('style'));
131 element.style.display = nowVisible ? 'none' : '';
132 };
133
134 // store attribute so everyone knows we're animating this element
135 element.setAttribute('data-animated', "true");
136
137 // toggle element visiblity right away if we're making something visible
138 if( ! nowVisible ) {
139 element.style.display = '';
140 }
141
142 var hiddenStyles, visibleStyles;
143
144 // animate properties
145 if( animation === 'slide' ) {
146 hiddenStyles = initObjectProperties(["height", "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"], 0);
147 visibleStyles = {};
148
149 if( ! nowVisible ) {
150 var computedStyles = window.getComputedStyle(element);
151 visibleStyles = copyObjectProperties(["height", "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"], computedStyles);
152 css(element, hiddenStyles);
153 }
154
155 // don't show a scrollbar during animation
156 element.style.overflowY = 'hidden';
157 animate(element, nowVisible ? hiddenStyles : visibleStyles, cleanup);
158 } else {
159 hiddenStyles = { opacity: 0 };
160 visibleStyles = { opacity: 1 };
161 if( ! nowVisible ) {
162 css(element, hiddenStyles);
163 }
164
165 animate(element, nowVisible ? hiddenStyles : visibleStyles, cleanup);
166 }
167 }
168
169 function animate(element, targetStyles, fn) {
170 var last = +new Date();
171 var initialStyles = window.getComputedStyle(element);
172 var currentStyles = {};
173 var propSteps = {};
174
175 for(var property in targetStyles) {
176 // make sure we have an object filled with floats
177 targetStyles[property] = parseFloat(targetStyles[property]);
178
179 // calculate step size & current value
180 var to = targetStyles[property];
181 var current = parseFloat(initialStyles[property]);
182
183 // is there something to do?
184 if( current == to ) {
185 delete targetStyles[property];
186 continue;
187 }
188
189 propSteps[property] = ( to - current ) / duration; // points per second
190 currentStyles[property] = current;
191 }
192
193 var tick = function() {
194 var now = +new Date();
195 var timeSinceLastTick = now - last;
196 var done = true;
197
198 var step, to, increment, newValue;
199 for(var property in targetStyles ) {
200 step = propSteps[property];
201 to = targetStyles[property];
202 increment = step * timeSinceLastTick;
203 newValue = currentStyles[property] + increment;
204
205 if( step > 0 && newValue >= to || step < 0 && newValue <= to ) {
206 newValue = to;
207 } else {
208 done = false;
209 }
210
211 // store new value
212 currentStyles[property] = newValue;
213
214 var suffix = property !== "opacity" ? "px" : "";
215 element.style[property] = newValue + suffix;
216 }
217
218 last = +new Date();
219
220 // keep going until we're done for all props
221 if(!done) {
222 (window.requestAnimationFrame && requestAnimationFrame(tick)) || setTimeout(tick, 32);
223 } else {
224 // call callback
225 fn && fn();
226 }
227 };
228
229 tick();
230 }
231
232
233 module.exports = {
234 'toggle': toggle,
235 'animate': animate,
236 'animated': animated
237 };
238 },{}],3:[function(require,module,exports){
239 'use strict';
240
241 var defaults = {
242 'animation': 'fade',
243 'rehide': false,
244 'content': '',
245 'cookie': null,
246 'icon': '&times',
247 'minimumScreenWidth': 0,
248 'position': 'center',
249 'testMode': false,
250 'trigger': false,
251 'closable': true
252 },
253 Boxzilla,
254 Animator = require('./animator.js');
255
256 /**
257 * Merge 2 objects, values of the latter overwriting the former.
258 *
259 * @param obj1
260 * @param obj2
261 * @returns {*}
262 */
263 function merge( obj1, obj2 ) {
264 var obj3 = {};
265 for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; }
266 for (var attrname in obj2) { obj3[attrname] = obj2[attrname]; }
267 return obj3;
268 }
269
270 /**
271 * Get the real height of entire document.
272 * @returns {number}
273 */
274 function getDocumentHeight() {
275 var body = document.body,
276 html = document.documentElement;
277
278 var height = Math.max( body.scrollHeight, body.offsetHeight,
279 html.clientHeight, html.scrollHeight, html.offsetHeight );
280
281 return height;
282 }
283
284 // Box Object
285 var Box = function( id, config ) {
286 this.id = id;
287
288 // store config values
289 this.config = merge(defaults, config);
290
291 // store ref to overlay
292 this.overlay = document.getElementById('boxzilla-overlay');
293
294 // state
295 this.visible = false;
296 this.dismissed = false;
297 this.triggered = false;
298 this.triggerHeight = 0;
299 this.cookieSet = false;
300 this.element = null;
301 this.closeIcon = null;
302
303 // if a trigger was given, calculate values once and store
304 if( this.config.trigger ) {
305 if( this.config.trigger.method === 'percentage' || this.config.trigger.method === 'element' ) {
306 this.triggerHeight = this.calculateTriggerHeight();
307 }
308
309 this.cookieSet = this.isCookieSet();
310 }
311
312 // create dom elements for this box
313 this.dom();
314
315 // further initialise the box
316 this.events();
317 };
318
319 // initialise the box
320 Box.prototype.events = function() {
321 var box = this;
322
323 // attach event to "close" icon inside box
324 this.closeIcon && this.closeIcon.addEventListener('click', box.dismiss.bind(this));
325
326 this.element.addEventListener('click', function(e) {
327 if( e.target.tagName === 'A' ) {
328 Boxzilla.trigger('box.interactions.link', [ box, e.target ] );
329 }
330 }, false);
331
332 this.element.addEventListener('submit', function(e) {
333 box.setCookie();
334 Boxzilla.trigger('box.interactions.form', [ box, e.target ]);
335 }, false);
336
337 window.addEventListener("hashchange", function() {
338 var needle = "#boxzilla-" + box.id;
339 if( location.hash === needle ) {
340 box.toggle();
341 }
342 });
343
344 // maybe show box right away
345 if( this.fits() && this.locationHashRefersBox() ) {
346 window.addEventListener('load', this.show.bind(this));
347 }
348
349 };
350
351 // generate dom elements for this box
352 Box.prototype.dom = function() {
353 var wrapper = document.createElement('div');
354 wrapper.className = 'boxzilla-container boxzilla-' + this.config.position + '-container';
355
356 var box = document.createElement('div');
357 box.setAttribute('id', 'boxzilla-' + this.id);
358 box.className = 'boxzilla boxzilla-' + this.id + ' boxzilla-' + this.config.position;
359 box.style.display = 'none';
360 wrapper.appendChild(box);
361
362 var content = document.createElement('div');
363 content.className = 'boxzilla-content';
364 content.innerHTML = this.config.content;
365 box.appendChild(content);
366
367 // remove <script> from box content and append them to the document body
368 var scripts = content.querySelectorAll('script');
369 if(scripts.length) {
370 var script = document.createElement('script');
371 for( var i=0; i<scripts.length; i++ ) {
372 script.appendChild(document.createTextNode(scripts[i].text));
373 scripts[i].parentNode.removeChild(scripts[i]);
374 }
375 document.body.appendChild(script);
376 }
377
378 if( this.config.closable && this.config.icon ) {
379 var closeIcon = document.createElement('span');
380 closeIcon.className = "boxzilla-close-icon";
381 closeIcon.innerHTML = this.config.icon;
382 box.appendChild(closeIcon);
383 this.closeIcon = closeIcon;
384 }
385
386 document.body.appendChild(wrapper);
387 this.element = box;
388 };
389
390 // set (calculate) custom box styling depending on box options
391 Box.prototype.setCustomBoxStyling = function() {
392
393 // reset element to its initial state
394 var origDisplay = this.element.style.display;
395 this.element.style.display = '';
396 this.element.style.overflowY = 'auto';
397 this.element.style.maxHeight = 'none';
398
399 // get new dimensions
400 var windowHeight = window.innerHeight;
401 var boxHeight = this.element.clientHeight;
402
403 // add scrollbar to box and limit height
404 if( boxHeight > windowHeight ) {
405 this.element.style.maxHeight = windowHeight + "px";
406 this.element.style.overflowY = 'scroll';
407 }
408
409 // set new top margin for boxes which are centered
410 if( this.config.position === 'center' ) {
411 var newTopMargin = ( ( windowHeight - boxHeight ) / 2 );
412 newTopMargin = newTopMargin >= 0 ? newTopMargin : 0;
413 this.element.style.marginTop = newTopMargin + "px";
414 }
415
416 this.element.style.display = origDisplay;
417 };
418
419 // toggle visibility of the box
420 Box.prototype.toggle = function(show) {
421
422 // revert visibility if no explicit argument is given
423 if( typeof( show ) === "undefined" ) {
424 show = ! this.visible;
425 }
426
427 // is box already at desired visibility?
428 if( show === this.visible ) {
429 return false;
430 }
431
432 // is box being animated?
433 if( Animator.animated(this.element) ) {
434 return false;
435 }
436
437 // if box should be hidden but is not closable, bail.
438 if( ! show && ! this.config.closable ) {
439 return false;
440 }
441
442 // set new visibility status
443 this.visible = show;
444
445 // calculate new styling rules
446 this.setCustomBoxStyling();
447
448 // trigger event
449 Boxzilla.trigger('box.' + ( show ? 'show' : 'hide' ), [ this ] );
450
451 // show or hide box using selected animation
452 if( this.config.position === 'center' ) {
453 Animator.toggle(this.overlay, "fade");
454 }
455
456 Animator.toggle(this.element, this.config.animation);
457
458 // focus on first input field in box
459 var firstInput = this.element.querySelector('input, textarea');
460 if(firstInput) {
461 firstInput.focus();
462 }
463
464 return true;
465 };
466
467 // show the box
468 Box.prototype.show = function() {
469 return this.toggle(true);
470 };
471
472 // hide the box
473 Box.prototype.hide = function() {
474 return this.toggle(false);
475 };
476
477 // calculate trigger height
478 Box.prototype.calculateTriggerHeight = function() {
479 var triggerHeight = 0;
480
481 if( this.config.trigger.method === 'element' ) {
482 var triggerElement = document.body.querySelector(this.config.trigger.value);
483 if( triggerElement ) {
484 var offset = triggerElement.getBoundingClientRect();
485 triggerHeight = offset.top;
486 }
487 } else if( this.config.trigger.method === 'percentage' ) {
488 triggerHeight = ( this.config.trigger.value / 100 * getDocumentHeight() );
489 }
490
491 return triggerHeight;
492 };
493
494 // checks whether window.location.hash equals the box element ID or that of any element inside the box
495 Box.prototype.locationHashRefersBox = function() {
496
497 if( ! window.location.hash || 0 === window.location.hash.length ) {
498 return false;
499 }
500
501 var elementId = window.location.hash.substring(1);
502 if( elementId === this.element.id ) {
503 return true;
504 } else if( this.element.querySelector('#' + elementId) ) {
505 return true;
506 }
507
508 return false;
509 };
510
511 Box.prototype.fits = function() {
512 if( this.config.minimumScreenWidth <= 0 ) {
513 return true;
514 }
515
516 return window.innerWidth > this.config.minimumScreenWidth
517 };
518
519 // is this box enabled?
520 Box.prototype.mayAutoShow = function() {
521
522 if( this.dismissed ) {
523 return false;
524 }
525
526 // check if box fits on given minimum screen width
527 if( ! this.fits() ) {
528 return false;
529 }
530
531 // if trigger empty or error in calculating triggerHeight, return false
532 if( ! this.config.trigger ) {
533 return false;
534 }
535
536 // rely on cookie value (show if not set, don't show if set)
537 return ! this.cookieSet;
538 };
539
540 Box.prototype.mayRehide = function() {
541 return this.config.rehide && this.triggered;
542 };
543
544 Box.prototype.isCookieSet = function() {
545 // always show on test mode
546 if(this.config.testMode) {
547 return false;
548 }
549
550 // if either cookie is null or trigger & dismiss are both falsey, don't bother checking.
551 if(!this.config.cookie || ( ! this.config.cookie.triggered && ! this.config.cookie.dismissed ) ) {
552 return false;
553 }
554
555 var cookieSet = document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + 'boxzilla_box_' + this.id + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1") === "true";
556 return cookieSet;
557 };
558
559 // set cookie that disables automatically showing the box
560 Box.prototype.setCookie = function(hours) {
561 var expiryDate = new Date();
562 expiryDate.setHours( expiryDate.getHours() + hours);
563 document.cookie = 'boxzilla_box_'+ this.id + '=true; expires='+ expiryDate.toUTCString() +'; path=/';
564 };
565
566 Box.prototype.trigger = function() {
567 var shown = this.show();
568 if( ! shown ) {
569 return;
570 }
571
572 this.triggered = true;
573 if(this.config.cookie && this.config.cookie.triggered) {
574 this.setCookie(this.config.cookie.triggered);
575 }
576 };
577
578 Box.prototype.dismiss = function() {
579 this.hide();
580
581 if(this.config.cookie && this.config.cookie.dismissed) {
582 this.setCookie(this.config.cookie.dismissed);
583 }
584
585 this.dismissed = true;
586 Boxzilla.trigger('box.dismiss', [ this ]);
587 };
588
589 module.exports = function(_Boxzilla) {
590 Boxzilla = _Boxzilla;
591 return Box;
592 };
593 },{"./animator.js":2}],4:[function(require,module,exports){
594 'use strict';
595
596 var EventEmitter = require('wolfy87-eventemitter'),
597 Boxzilla = Object.create(EventEmitter.prototype),
598 Box = require('./box.js')(Boxzilla),
599 Timer = require('./timer.js'),
600 boxes = [],
601 overlay,
602 exitIntentDelayTimer, exitIntentTriggered,
603 siteTimer, pageTimer, pageViews;
604
605 function throttle(fn, threshhold, scope) {
606 threshhold || (threshhold = 250);
607 var last,
608 deferTimer;
609 return function () {
610 var context = scope || this;
611
612 var now = +new Date,
613 args = arguments;
614 if (last && now < last + threshhold) {
615 // hold on to it
616 clearTimeout(deferTimer);
617 deferTimer = setTimeout(function () {
618 last = now;
619 fn.apply(context, args);
620 }, threshhold);
621 } else {
622 last = now;
623 fn.apply(context, args);
624 }
625 };
626 }
627
628 // "keyup" listener
629 function onKeyUp(e) {
630 if (e.keyCode == 27) {
631 Boxzilla.dismiss();
632 }
633 }
634
635 // check "pageviews" criteria for each box
636 function checkPageViewsCriteria() {
637
638 // don't bother if another box is currently open
639 if( isAnyBoxVisible() ) {
640 return;
641 }
642
643 boxes.forEach(function(box) {
644 if( ! box.mayAutoShow() ) {
645 return;
646 }
647
648 if( box.config.trigger.method === 'pageviews' && pageViews >= box.config.trigger.value ) {
649 box.trigger();
650 }
651 });
652 }
653
654 // check time trigger criteria for each box
655 function checkTimeCriteria() {
656 // don't bother if another box is currently open
657 if( isAnyBoxVisible() ) {
658 return;
659 }
660
661 boxes.forEach(function(box) {
662 if( ! box.mayAutoShow() ) {
663 return;
664 }
665
666 // check "time on site" trigger
667 if (box.config.trigger.method === 'time_on_site' && siteTimer.time >= box.config.trigger.value) {
668 box.trigger();
669 }
670
671 // check "time on page" trigger
672 if (box.config.trigger.method === 'time_on_page' && pageTimer.time >= box.config.trigger.value) {
673 box.trigger();
674 }
675 });
676 }
677
678 // check triggerHeight criteria for all boxes
679 function checkHeightCriteria() {
680 var scrollY = ( window.scrollY || window.pageYOffset ) + window.innerHeight * 0.75;
681
682 // don't bother if another box is currently open
683 if( isAnyBoxVisible() ) {
684 return;
685 }
686
687 boxes.forEach(function(box) {
688
689 if( ! box.mayAutoShow() || box.triggerHeight <= 0 ) {
690 return;
691 }
692
693 if( scrollY > box.triggerHeight ) {
694 box.trigger();
695 } else if( box.mayRehide() ) {
696 box.hide();
697 }
698 });
699 }
700
701 // recalculate heights and variables based on height
702 function recalculateHeights() {
703 boxes.forEach(function(box) {
704 box.setCustomBoxStyling();
705 });
706 }
707
708 function onOverlayClick(e) {
709 var x = e.offsetX;
710 var y = e.offsetY;
711
712 // calculate if click was near a box to avoid closing it (click error margin)
713 boxes.forEach(function(box) {
714 var rect = box.element.getBoundingClientRect();
715 var margin = 100 + ( window.innerWidth * 0.05 );
716
717 // if click was not anywhere near box, dismiss it.
718 if( x < ( rect.left - margin ) || x > ( rect.right + margin ) || y < ( rect.top - margin ) || y > ( rect.bottom + margin ) ) {
719 box.dismiss();
720 }
721 });
722 }
723
724 function triggerExitIntent() {
725 // do nothing if already triggered OR another box is visible.
726 if(exitIntentTriggered || isAnyBoxVisible() ) {
727 return;
728 }
729
730 boxes.forEach(function(box) {
731 if(box.mayAutoShow() && box.config.trigger.method === 'exit_intent' ) {
732 box.trigger();
733 }
734 });
735
736 exitIntentTriggered = true;
737 }
738
739 function onMouseLeave(e) {
740 var delay = 400;
741
742 // did mouse leave at top of window?
743 if( e.clientY <= 0 ) {
744 exitIntentDelayTimer = window.setTimeout(triggerExitIntent, delay);
745 }
746 }
747
748 function isAnyBoxVisible() {
749
750 for( var i=0; i<boxes.length; i++ ) {
751 var box = boxes[i];
752
753 if( box.visible ) {
754 return true;
755 }
756 }
757
758 return false;
759 }
760
761 function onMouseEnter() {
762 if( exitIntentDelayTimer ) {
763 window.clearInterval(exitIntentDelayTimer);
764 exitIntentDelayTimer = null;
765 }
766 }
767
768 var timers = {
769 start: function() {
770 var sessionTime = sessionStorage.getItem('boxzilla_timer');
771 if( sessionTime ) siteTimer.time = sessionTime;
772 siteTimer.start();
773 pageTimer.start();
774 },
775 stop: function() {
776 sessionStorage.setItem('boxzilla_timer', siteTimer.time);
777 siteTimer.stop();
778 pageTimer.stop();
779 }
780 };
781
782 // initialise & add event listeners
783 Boxzilla.init = function() {
784 siteTimer = new Timer(sessionStorage.getItem('boxzilla_timer') || 0);
785 pageTimer = new Timer(0);
786 pageViews = sessionStorage.getItem('boxzilla_pageviews') || 0;
787
788 // insert styles into DOM
789 var styles = require('./styles.js');
790 var styleElement = document.createElement('style');
791 styleElement.setAttribute("type", "text/css");
792 styleElement.innerHTML = styles;
793 document.head.appendChild(styleElement);
794
795 // add overlay element to dom
796 overlay = document.createElement('div');
797 overlay.style.display = 'none';
798 overlay.id = 'boxzilla-overlay';
799 document.body.appendChild(overlay);
800
801 // event binds
802 window.addEventListener('scroll', throttle(checkHeightCriteria));
803 window.addEventListener('resize', throttle(recalculateHeights));
804 window.addEventListener('load', recalculateHeights );
805 overlay.addEventListener('click', onOverlayClick);
806 window.setInterval(checkTimeCriteria, 1000);
807 window.setTimeout(checkPageViewsCriteria, 1000 );
808 document.documentElement.addEventListener('mouseleave', onMouseLeave);
809 document.documentElement.addEventListener('mouseenter', onMouseEnter);
810 document.addEventListener('keyup', onKeyUp);
811
812 timers.start();
813 window.addEventListener('focus', timers.start);
814 window.addEventListener('beforeunload', function() {
815 timers.stop();
816 sessionStorage.setItem('boxzilla_pageviews', ++pageViews);
817 });
818 window.addEventListener('blur', timers.stop);
819
820 Boxzilla.trigger('ready');
821 };
822
823 /**
824 * Create a new Box
825 *
826 * @param string id
827 * @param object opts
828 *
829 * @returns Box
830 */
831 Boxzilla.create = function(id, opts) {
832 var box = new Box(id, opts);
833 boxes.push(box);
834 return box;
835 };
836
837 Boxzilla.get = function(id) {
838 for( var i=0; i<boxes.length; i++) {
839 var box = boxes[i];
840 if( box.id == id ) {
841 return box;
842 }
843 }
844
845 throw new Error("No box exists with ID " + id);
846 }
847
848 // dismiss a single box (or all by omitting id param)
849 Boxzilla.dismiss = function(id) {
850 // if no id given, dismiss all current open boxes
851 if( typeof(id) === "undefined" ) {
852 boxes.forEach(function(box) { box.dismiss(); });
853 } else if( typeof( boxes[id] ) === "object" ) {
854 Boxzilla.get(id).dismiss();
855 }
856 };
857
858 Boxzilla.hide = function(id) {
859 if( typeof(id) === "undefined" ) {
860 boxes.forEach(function(box) { box.hide(); });
861 } else {
862 Boxzilla.get(id).hide();
863 }
864 };
865
866 Boxzilla.show = function(id) {
867 if( typeof(id) === "undefined" ) {
868 boxes.forEach(function(box) { box.show(); });
869 } else {
870 Boxzilla.get(id).show();
871 }
872 };
873
874 Boxzilla.toggle = function(id) {
875 if( typeof(id) === "undefined" ) {
876 boxes.forEach(function(box) { box.toggle(); });
877 } else {
878 Boxzilla.get(id).toggle();
879 }
880 };
881
882 // expose each individual box.
883 Boxzilla.boxes = boxes;
884
885 window.Boxzilla = Boxzilla;
886
887 if ( typeof module !== 'undefined' && module.exports ) {
888 module.exports = Boxzilla;
889 }
890 },{"./box.js":3,"./styles.js":5,"./timer.js":6,"wolfy87-eventemitter":7}],5:[function(require,module,exports){
891 const styles = `#boxzilla-overlay{position:fixed;background:rgba(0,0,0,.65);width:100%;height:100%;left:0;top:0;z-index:99999}.boxzilla-center-container{position:fixed;top:0;left:0;right:0;height:0;text-align:center;z-index:999999;line-height:0}.boxzilla-center-container .boxzilla{display:inline-block;text-align:left;position:relative;line-height:normal}.boxzilla{position:fixed;z-index:999999;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:#fff;padding:25px}.boxzilla.boxzilla-top-left{top:0;left:0}.boxzilla.boxzilla-top-right{top:0;right:0}.boxzilla.boxzilla-bottom-left{bottom:0;left:0}.boxzilla.boxzilla-bottom-right{bottom:0;right:0}.boxzilla-content>:first-child{margin-top:0;padding-top:0}.boxzilla-content>:last-child{margin-bottom:0;padding-bottom:0}.boxzilla-close-icon{position:absolute;right:0;top:0;text-align:center;padding:6px;cursor:pointer;-webkit-appearance:none;font-size:28px;font-weight:700;line-height:20px;color:#000;opacity:.5}.boxzilla-close-icon:focus,.boxzilla-close-icon:hover{opacity:.8}`;
892 module.exports = styles;
893 },{}],6:[function(require,module,exports){
894 'use strict';
895
896 var Timer = function(start) {
897 this.time = start;
898 this.interval = 0;
899 };
900
901 Timer.prototype.tick = function() {
902 this.time++;
903 };
904
905 Timer.prototype.start = function() {
906 if( ! this.interval ) {
907 this.interval = window.setInterval(this.tick.bind(this), 1000);
908 }
909 };
910
911 Timer.prototype.stop = function() {
912 if( this.interval ) {
913 window.clearInterval(this.interval);
914 this.interval = 0;
915 }
916 };
917
918 module.exports = Timer;
919 },{}],7:[function(require,module,exports){
920 /*!
921 * EventEmitter v4.2.11 - git.io/ee
922 * Unlicense - http://unlicense.org/
923 * Oliver Caldwell - http://oli.me.uk/
924 * @preserve
925 */
926
927 ;(function () {
928 'use strict';
929
930 /**
931 * Class for managing events.
932 * Can be extended to provide event functionality in other classes.
933 *
934 * @class EventEmitter Manages event registering and emitting.
935 */
936 function EventEmitter() {}
937
938 // Shortcuts to improve speed and size
939 var proto = EventEmitter.prototype;
940 var exports = this;
941 var originalGlobalValue = exports.EventEmitter;
942
943 /**
944 * Finds the index of the listener for the event in its storage array.
945 *
946 * @param {Function[]} listeners Array of listeners to search through.
947 * @param {Function} listener Method to look for.
948 * @return {Number} Index of the specified listener, -1 if not found
949 * @api private
950 */
951 function indexOfListener(listeners, listener) {
952 var i = listeners.length;
953 while (i--) {
954 if (listeners[i].listener === listener) {
955 return i;
956 }
957 }
958
959 return -1;
960 }
961
962 /**
963 * Alias a method while keeping the context correct, to allow for overwriting of target method.
964 *
965 * @param {String} name The name of the target method.
966 * @return {Function} The aliased method
967 * @api private
968 */
969 function alias(name) {
970 return function aliasClosure() {
971 return this[name].apply(this, arguments);
972 };
973 }
974
975 /**
976 * Returns the listener array for the specified event.
977 * Will initialise the event object and listener arrays if required.
978 * 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.
979 * Each property in the object response is an array of listener functions.
980 *
981 * @param {String|RegExp} evt Name of the event to return the listeners from.
982 * @return {Function[]|Object} All listener functions for the event.
983 */
984 proto.getListeners = function getListeners(evt) {
985 var events = this._getEvents();
986 var response;
987 var key;
988
989 // Return a concatenated array of all matching events if
990 // the selector is a regular expression.
991 if (evt instanceof RegExp) {
992 response = {};
993 for (key in events) {
994 if (events.hasOwnProperty(key) && evt.test(key)) {
995 response[key] = events[key];
996 }
997 }
998 }
999 else {
1000 response = events[evt] || (events[evt] = []);
1001 }
1002
1003 return response;
1004 };
1005
1006 /**
1007 * Takes a list of listener objects and flattens it into a list of listener functions.
1008 *
1009 * @param {Object[]} listeners Raw listener objects.
1010 * @return {Function[]} Just the listener functions.
1011 */
1012 proto.flattenListeners = function flattenListeners(listeners) {
1013 var flatListeners = [];
1014 var i;
1015
1016 for (i = 0; i < listeners.length; i += 1) {
1017 flatListeners.push(listeners[i].listener);
1018 }
1019
1020 return flatListeners;
1021 };
1022
1023 /**
1024 * 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.
1025 *
1026 * @param {String|RegExp} evt Name of the event to return the listeners from.
1027 * @return {Object} All listener functions for an event in an object.
1028 */
1029 proto.getListenersAsObject = function getListenersAsObject(evt) {
1030 var listeners = this.getListeners(evt);
1031 var response;
1032
1033 if (listeners instanceof Array) {
1034 response = {};
1035 response[evt] = listeners;
1036 }
1037
1038 return response || listeners;
1039 };
1040
1041 /**
1042 * Adds a listener function to the specified event.
1043 * The listener will not be added if it is a duplicate.
1044 * If the listener returns true then it will be removed after it is called.
1045 * If you pass a regular expression as the event name then the listener will be added to all events that match it.
1046 *
1047 * @param {String|RegExp} evt Name of the event to attach the listener to.
1048 * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
1049 * @return {Object} Current instance of EventEmitter for chaining.
1050 */
1051 proto.addListener = function addListener(evt, listener) {
1052 var listeners = this.getListenersAsObject(evt);
1053 var listenerIsWrapped = typeof listener === 'object';
1054 var key;
1055
1056 for (key in listeners) {
1057 if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) {
1058 listeners[key].push(listenerIsWrapped ? listener : {
1059 listener: listener,
1060 once: false
1061 });
1062 }
1063 }
1064
1065 return this;
1066 };
1067
1068 /**
1069 * Alias of addListener
1070 */
1071 proto.on = alias('addListener');
1072
1073 /**
1074 * Semi-alias of addListener. It will add a listener that will be
1075 * automatically removed after its first execution.
1076 *
1077 * @param {String|RegExp} evt Name of the event to attach the listener to.
1078 * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
1079 * @return {Object} Current instance of EventEmitter for chaining.
1080 */
1081 proto.addOnceListener = function addOnceListener(evt, listener) {
1082 return this.addListener(evt, {
1083 listener: listener,
1084 once: true
1085 });
1086 };
1087
1088 /**
1089 * Alias of addOnceListener.
1090 */
1091 proto.once = alias('addOnceListener');
1092
1093 /**
1094 * 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.
1095 * You need to tell it what event names should be matched by a regex.
1096 *
1097 * @param {String} evt Name of the event to create.
1098 * @return {Object} Current instance of EventEmitter for chaining.
1099 */
1100 proto.defineEvent = function defineEvent(evt) {
1101 this.getListeners(evt);
1102 return this;
1103 };
1104
1105 /**
1106 * Uses defineEvent to define multiple events.
1107 *
1108 * @param {String[]} evts An array of event names to define.
1109 * @return {Object} Current instance of EventEmitter for chaining.
1110 */
1111 proto.defineEvents = function defineEvents(evts) {
1112 for (var i = 0; i < evts.length; i += 1) {
1113 this.defineEvent(evts[i]);
1114 }
1115 return this;
1116 };
1117
1118 /**
1119 * Removes a listener function from the specified event.
1120 * When passed a regular expression as the event name, it will remove the listener from all events that match it.
1121 *
1122 * @param {String|RegExp} evt Name of the event to remove the listener from.
1123 * @param {Function} listener Method to remove from the event.
1124 * @return {Object} Current instance of EventEmitter for chaining.
1125 */
1126 proto.removeListener = function removeListener(evt, listener) {
1127 var listeners = this.getListenersAsObject(evt);
1128 var index;
1129 var key;
1130
1131 for (key in listeners) {
1132 if (listeners.hasOwnProperty(key)) {
1133 index = indexOfListener(listeners[key], listener);
1134
1135 if (index !== -1) {
1136 listeners[key].splice(index, 1);
1137 }
1138 }
1139 }
1140
1141 return this;
1142 };
1143
1144 /**
1145 * Alias of removeListener
1146 */
1147 proto.off = alias('removeListener');
1148
1149 /**
1150 * Adds listeners in bulk using the manipulateListeners method.
1151 * 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.
1152 * You can also pass it a regular expression to add the array of listeners to all events that match it.
1153 * Yeah, this function does quite a bit. That's probably a bad thing.
1154 *
1155 * @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.
1156 * @param {Function[]} [listeners] An optional array of listener functions to add.
1157 * @return {Object} Current instance of EventEmitter for chaining.
1158 */
1159 proto.addListeners = function addListeners(evt, listeners) {
1160 // Pass through to manipulateListeners
1161 return this.manipulateListeners(false, evt, listeners);
1162 };
1163
1164 /**
1165 * Removes listeners in bulk using the manipulateListeners method.
1166 * 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.
1167 * You can also pass it an event name and an array of listeners to be removed.
1168 * You can also pass it a regular expression to remove the listeners from all events that match it.
1169 *
1170 * @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.
1171 * @param {Function[]} [listeners] An optional array of listener functions to remove.
1172 * @return {Object} Current instance of EventEmitter for chaining.
1173 */
1174 proto.removeListeners = function removeListeners(evt, listeners) {
1175 // Pass through to manipulateListeners
1176 return this.manipulateListeners(true, evt, listeners);
1177 };
1178
1179 /**
1180 * 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.
1181 * The first argument will determine if the listeners are removed (true) or added (false).
1182 * 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.
1183 * You can also pass it an event name and an array of listeners to be added/removed.
1184 * You can also pass it a regular expression to manipulate the listeners of all events that match it.
1185 *
1186 * @param {Boolean} remove True if you want to remove listeners, false if you want to add.
1187 * @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.
1188 * @param {Function[]} [listeners] An optional array of listener functions to add/remove.
1189 * @return {Object} Current instance of EventEmitter for chaining.
1190 */
1191 proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) {
1192 var i;
1193 var value;
1194 var single = remove ? this.removeListener : this.addListener;
1195 var multiple = remove ? this.removeListeners : this.addListeners;
1196
1197 // If evt is an object then pass each of its properties to this method
1198 if (typeof evt === 'object' && !(evt instanceof RegExp)) {
1199 for (i in evt) {
1200 if (evt.hasOwnProperty(i) && (value = evt[i])) {
1201 // Pass the single listener straight through to the singular method
1202 if (typeof value === 'function') {
1203 single.call(this, i, value);
1204 }
1205 else {
1206 // Otherwise pass back to the multiple function
1207 multiple.call(this, i, value);
1208 }
1209 }
1210 }
1211 }
1212 else {
1213 // So evt must be a string
1214 // And listeners must be an array of listeners
1215 // Loop over it and pass each one to the multiple method
1216 i = listeners.length;
1217 while (i--) {
1218 single.call(this, evt, listeners[i]);
1219 }
1220 }
1221
1222 return this;
1223 };
1224
1225 /**
1226 * Removes all listeners from a specified event.
1227 * If you do not specify an event then all listeners will be removed.
1228 * That means every event will be emptied.
1229 * You can also pass a regex to remove all events that match it.
1230 *
1231 * @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed.
1232 * @return {Object} Current instance of EventEmitter for chaining.
1233 */
1234 proto.removeEvent = function removeEvent(evt) {
1235 var type = typeof evt;
1236 var events = this._getEvents();
1237 var key;
1238
1239 // Remove different things depending on the state of evt
1240 if (type === 'string') {
1241 // Remove all listeners for the specified event
1242 delete events[evt];
1243 }
1244 else if (evt instanceof RegExp) {
1245 // Remove all events matching the regex.
1246 for (key in events) {
1247 if (events.hasOwnProperty(key) && evt.test(key)) {
1248 delete events[key];
1249 }
1250 }
1251 }
1252 else {
1253 // Remove all listeners in all events
1254 delete this._events;
1255 }
1256
1257 return this;
1258 };
1259
1260 /**
1261 * Alias of removeEvent.
1262 *
1263 * Added to mirror the node API.
1264 */
1265 proto.removeAllListeners = alias('removeEvent');
1266
1267 /**
1268 * Emits an event of your choice.
1269 * When emitted, every listener attached to that event will be executed.
1270 * If you pass the optional argument array then those arguments will be passed to every listener upon execution.
1271 * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately.
1272 * So they will not arrive within the array on the other side, they will be separate.
1273 * You can also pass a regular expression to emit to all events that match it.
1274 *
1275 * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
1276 * @param {Array} [args] Optional array of arguments to be passed to each listener.
1277 * @return {Object} Current instance of EventEmitter for chaining.
1278 */
1279 proto.emitEvent = function emitEvent(evt, args) {
1280 var listenersMap = this.getListenersAsObject(evt);
1281 var listeners;
1282 var listener;
1283 var i;
1284 var key;
1285 var response;
1286
1287 for (key in listenersMap) {
1288 if (listenersMap.hasOwnProperty(key)) {
1289 listeners = listenersMap[key].slice(0);
1290 i = listeners.length;
1291
1292 while (i--) {
1293 // If the listener returns true then it shall be removed from the event
1294 // The function is executed either with a basic call or an apply if there is an args array
1295 listener = listeners[i];
1296
1297 if (listener.once === true) {
1298 this.removeListener(evt, listener.listener);
1299 }
1300
1301 response = listener.listener.apply(this, args || []);
1302
1303 if (response === this._getOnceReturnValue()) {
1304 this.removeListener(evt, listener.listener);
1305 }
1306 }
1307 }
1308 }
1309
1310 return this;
1311 };
1312
1313 /**
1314 * Alias of emitEvent
1315 */
1316 proto.trigger = alias('emitEvent');
1317
1318 /**
1319 * 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.
1320 * As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it.
1321 *
1322 * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
1323 * @param {...*} Optional additional arguments to be passed to each listener.
1324 * @return {Object} Current instance of EventEmitter for chaining.
1325 */
1326 proto.emit = function emit(evt) {
1327 var args = Array.prototype.slice.call(arguments, 1);
1328 return this.emitEvent(evt, args);
1329 };
1330
1331 /**
1332 * Sets the current value to check against when executing listeners. If a
1333 * listeners return value matches the one set here then it will be removed
1334 * after execution. This value defaults to true.
1335 *
1336 * @param {*} value The new value to check for when executing listeners.
1337 * @return {Object} Current instance of EventEmitter for chaining.
1338 */
1339 proto.setOnceReturnValue = function setOnceReturnValue(value) {
1340 this._onceReturnValue = value;
1341 return this;
1342 };
1343
1344 /**
1345 * Fetches the current value to check against when executing listeners. If
1346 * the listeners return value matches this one then it should be removed
1347 * automatically. It will return true by default.
1348 *
1349 * @return {*|Boolean} The current value to check for or the default, true.
1350 * @api private
1351 */
1352 proto._getOnceReturnValue = function _getOnceReturnValue() {
1353 if (this.hasOwnProperty('_onceReturnValue')) {
1354 return this._onceReturnValue;
1355 }
1356 else {
1357 return true;
1358 }
1359 };
1360
1361 /**
1362 * Fetches the events object and creates one if required.
1363 *
1364 * @return {Object} The events storage object.
1365 * @api private
1366 */
1367 proto._getEvents = function _getEvents() {
1368 return this._events || (this._events = {});
1369 };
1370
1371 /**
1372 * Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version.
1373 *
1374 * @return {Function} Non conflicting EventEmitter class.
1375 */
1376 EventEmitter.noConflict = function noConflict() {
1377 exports.EventEmitter = originalGlobalValue;
1378 return EventEmitter;
1379 };
1380
1381 // Expose the class either via AMD, CommonJS or the global object
1382 if (typeof define === 'function' && define.amd) {
1383 define(function () {
1384 return EventEmitter;
1385 });
1386 }
1387 else if (typeof module === 'object' && module.exports){
1388 module.exports = EventEmitter;
1389 }
1390 else {
1391 exports.EventEmitter = EventEmitter;
1392 }
1393 }.call(this));
1394
1395 },{}]},{},[1]);
1396 ; })();