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

1,394 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
846 // dismiss a single box (or all by omitting id param)
847 Boxzilla.dismiss = function(id) {
848 // if no id given, dismiss all current open boxes
849 if( typeof(id) === "undefined" ) {
850 boxes.forEach(function(box) { box.dismiss(); });
851 } else if( typeof( boxes[id] ) === "object" ) {
852 Boxzilla.get(id).dismiss();
853 }
854 };
855
856 Boxzilla.hide = function(id) {
857 if( typeof(id) === "undefined" ) {
858 boxes.forEach(function(box) { box.hide(); });
859 } else {
860 Boxzilla.get(id).hide();
861 }
862 };
863
864 Boxzilla.show = function(id) {
865 if( typeof(id) === "undefined" ) {
866 boxes.forEach(function(box) { box.show(); });
867 } else if( typeof( boxes[id] ) === "object" ) {
868 Boxzilla.get(id).show();
869 }
870 };
871
872 Boxzilla.toggle = function(id) {
873 if( typeof(id) === "undefined" ) {
874 boxes.forEach(function(box) { box.toggle(); });
875 } else if( typeof( boxes[id] ) === "object" ) {
876 Boxzilla.get(id).toggle();
877 }
878 };
879
880 // expose each individual box.
881 Boxzilla.boxes = boxes;
882
883 window.Boxzilla = Boxzilla;
884
885 if ( typeof module !== 'undefined' && module.exports ) {
886 module.exports = Boxzilla;
887 }
888 },{"./box.js":3,"./styles.js":5,"./timer.js":6,"wolfy87-eventemitter":7}],5:[function(require,module,exports){
889 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}`;
890 module.exports = styles;
891 },{}],6:[function(require,module,exports){
892 'use strict';
893
894 var Timer = function(start) {
895 this.time = start;
896 this.interval = 0;
897 };
898
899 Timer.prototype.tick = function() {
900 this.time++;
901 };
902
903 Timer.prototype.start = function() {
904 if( ! this.interval ) {
905 this.interval = window.setInterval(this.tick.bind(this), 1000);
906 }
907 };
908
909 Timer.prototype.stop = function() {
910 if( this.interval ) {
911 window.clearInterval(this.interval);
912 this.interval = 0;
913 }
914 };
915
916 module.exports = Timer;
917 },{}],7:[function(require,module,exports){
918 /*!
919 * EventEmitter v4.2.11 - git.io/ee
920 * Unlicense - http://unlicense.org/
921 * Oliver Caldwell - http://oli.me.uk/
922 * @preserve
923 */
924
925 ;(function () {
926 'use strict';
927
928 /**
929 * Class for managing events.
930 * Can be extended to provide event functionality in other classes.
931 *
932 * @class EventEmitter Manages event registering and emitting.
933 */
934 function EventEmitter() {}
935
936 // Shortcuts to improve speed and size
937 var proto = EventEmitter.prototype;
938 var exports = this;
939 var originalGlobalValue = exports.EventEmitter;
940
941 /**
942 * Finds the index of the listener for the event in its storage array.
943 *
944 * @param {Function[]} listeners Array of listeners to search through.
945 * @param {Function} listener Method to look for.
946 * @return {Number} Index of the specified listener, -1 if not found
947 * @api private
948 */
949 function indexOfListener(listeners, listener) {
950 var i = listeners.length;
951 while (i--) {
952 if (listeners[i].listener === listener) {
953 return i;
954 }
955 }
956
957 return -1;
958 }
959
960 /**
961 * Alias a method while keeping the context correct, to allow for overwriting of target method.
962 *
963 * @param {String} name The name of the target method.
964 * @return {Function} The aliased method
965 * @api private
966 */
967 function alias(name) {
968 return function aliasClosure() {
969 return this[name].apply(this, arguments);
970 };
971 }
972
973 /**
974 * Returns the listener array for the specified event.
975 * Will initialise the event object and listener arrays if required.
976 * 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.
977 * Each property in the object response is an array of listener functions.
978 *
979 * @param {String|RegExp} evt Name of the event to return the listeners from.
980 * @return {Function[]|Object} All listener functions for the event.
981 */
982 proto.getListeners = function getListeners(evt) {
983 var events = this._getEvents();
984 var response;
985 var key;
986
987 // Return a concatenated array of all matching events if
988 // the selector is a regular expression.
989 if (evt instanceof RegExp) {
990 response = {};
991 for (key in events) {
992 if (events.hasOwnProperty(key) && evt.test(key)) {
993 response[key] = events[key];
994 }
995 }
996 }
997 else {
998 response = events[evt] || (events[evt] = []);
999 }
1000
1001 return response;
1002 };
1003
1004 /**
1005 * Takes a list of listener objects and flattens it into a list of listener functions.
1006 *
1007 * @param {Object[]} listeners Raw listener objects.
1008 * @return {Function[]} Just the listener functions.
1009 */
1010 proto.flattenListeners = function flattenListeners(listeners) {
1011 var flatListeners = [];
1012 var i;
1013
1014 for (i = 0; i < listeners.length; i += 1) {
1015 flatListeners.push(listeners[i].listener);
1016 }
1017
1018 return flatListeners;
1019 };
1020
1021 /**
1022 * 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.
1023 *
1024 * @param {String|RegExp} evt Name of the event to return the listeners from.
1025 * @return {Object} All listener functions for an event in an object.
1026 */
1027 proto.getListenersAsObject = function getListenersAsObject(evt) {
1028 var listeners = this.getListeners(evt);
1029 var response;
1030
1031 if (listeners instanceof Array) {
1032 response = {};
1033 response[evt] = listeners;
1034 }
1035
1036 return response || listeners;
1037 };
1038
1039 /**
1040 * Adds a listener function to the specified event.
1041 * The listener will not be added if it is a duplicate.
1042 * If the listener returns true then it will be removed after it is called.
1043 * If you pass a regular expression as the event name then the listener will be added to all events that match it.
1044 *
1045 * @param {String|RegExp} evt Name of the event to attach the listener to.
1046 * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
1047 * @return {Object} Current instance of EventEmitter for chaining.
1048 */
1049 proto.addListener = function addListener(evt, listener) {
1050 var listeners = this.getListenersAsObject(evt);
1051 var listenerIsWrapped = typeof listener === 'object';
1052 var key;
1053
1054 for (key in listeners) {
1055 if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) {
1056 listeners[key].push(listenerIsWrapped ? listener : {
1057 listener: listener,
1058 once: false
1059 });
1060 }
1061 }
1062
1063 return this;
1064 };
1065
1066 /**
1067 * Alias of addListener
1068 */
1069 proto.on = alias('addListener');
1070
1071 /**
1072 * Semi-alias of addListener. It will add a listener that will be
1073 * automatically removed after its first execution.
1074 *
1075 * @param {String|RegExp} evt Name of the event to attach the listener to.
1076 * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
1077 * @return {Object} Current instance of EventEmitter for chaining.
1078 */
1079 proto.addOnceListener = function addOnceListener(evt, listener) {
1080 return this.addListener(evt, {
1081 listener: listener,
1082 once: true
1083 });
1084 };
1085
1086 /**
1087 * Alias of addOnceListener.
1088 */
1089 proto.once = alias('addOnceListener');
1090
1091 /**
1092 * 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.
1093 * You need to tell it what event names should be matched by a regex.
1094 *
1095 * @param {String} evt Name of the event to create.
1096 * @return {Object} Current instance of EventEmitter for chaining.
1097 */
1098 proto.defineEvent = function defineEvent(evt) {
1099 this.getListeners(evt);
1100 return this;
1101 };
1102
1103 /**
1104 * Uses defineEvent to define multiple events.
1105 *
1106 * @param {String[]} evts An array of event names to define.
1107 * @return {Object} Current instance of EventEmitter for chaining.
1108 */
1109 proto.defineEvents = function defineEvents(evts) {
1110 for (var i = 0; i < evts.length; i += 1) {
1111 this.defineEvent(evts[i]);
1112 }
1113 return this;
1114 };
1115
1116 /**
1117 * Removes a listener function from the specified event.
1118 * When passed a regular expression as the event name, it will remove the listener from all events that match it.
1119 *
1120 * @param {String|RegExp} evt Name of the event to remove the listener from.
1121 * @param {Function} listener Method to remove from the event.
1122 * @return {Object} Current instance of EventEmitter for chaining.
1123 */
1124 proto.removeListener = function removeListener(evt, listener) {
1125 var listeners = this.getListenersAsObject(evt);
1126 var index;
1127 var key;
1128
1129 for (key in listeners) {
1130 if (listeners.hasOwnProperty(key)) {
1131 index = indexOfListener(listeners[key], listener);
1132
1133 if (index !== -1) {
1134 listeners[key].splice(index, 1);
1135 }
1136 }
1137 }
1138
1139 return this;
1140 };
1141
1142 /**
1143 * Alias of removeListener
1144 */
1145 proto.off = alias('removeListener');
1146
1147 /**
1148 * Adds listeners in bulk using the manipulateListeners method.
1149 * 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.
1150 * You can also pass it a regular expression to add the array of listeners to all events that match it.
1151 * Yeah, this function does quite a bit. That's probably a bad thing.
1152 *
1153 * @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.
1154 * @param {Function[]} [listeners] An optional array of listener functions to add.
1155 * @return {Object} Current instance of EventEmitter for chaining.
1156 */
1157 proto.addListeners = function addListeners(evt, listeners) {
1158 // Pass through to manipulateListeners
1159 return this.manipulateListeners(false, evt, listeners);
1160 };
1161
1162 /**
1163 * Removes listeners in bulk using the manipulateListeners method.
1164 * 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.
1165 * You can also pass it an event name and an array of listeners to be removed.
1166 * You can also pass it a regular expression to remove the listeners from all events that match it.
1167 *
1168 * @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.
1169 * @param {Function[]} [listeners] An optional array of listener functions to remove.
1170 * @return {Object} Current instance of EventEmitter for chaining.
1171 */
1172 proto.removeListeners = function removeListeners(evt, listeners) {
1173 // Pass through to manipulateListeners
1174 return this.manipulateListeners(true, evt, listeners);
1175 };
1176
1177 /**
1178 * 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.
1179 * The first argument will determine if the listeners are removed (true) or added (false).
1180 * 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.
1181 * You can also pass it an event name and an array of listeners to be added/removed.
1182 * You can also pass it a regular expression to manipulate the listeners of all events that match it.
1183 *
1184 * @param {Boolean} remove True if you want to remove listeners, false if you want to add.
1185 * @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.
1186 * @param {Function[]} [listeners] An optional array of listener functions to add/remove.
1187 * @return {Object} Current instance of EventEmitter for chaining.
1188 */
1189 proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) {
1190 var i;
1191 var value;
1192 var single = remove ? this.removeListener : this.addListener;
1193 var multiple = remove ? this.removeListeners : this.addListeners;
1194
1195 // If evt is an object then pass each of its properties to this method
1196 if (typeof evt === 'object' && !(evt instanceof RegExp)) {
1197 for (i in evt) {
1198 if (evt.hasOwnProperty(i) && (value = evt[i])) {
1199 // Pass the single listener straight through to the singular method
1200 if (typeof value === 'function') {
1201 single.call(this, i, value);
1202 }
1203 else {
1204 // Otherwise pass back to the multiple function
1205 multiple.call(this, i, value);
1206 }
1207 }
1208 }
1209 }
1210 else {
1211 // So evt must be a string
1212 // And listeners must be an array of listeners
1213 // Loop over it and pass each one to the multiple method
1214 i = listeners.length;
1215 while (i--) {
1216 single.call(this, evt, listeners[i]);
1217 }
1218 }
1219
1220 return this;
1221 };
1222
1223 /**
1224 * Removes all listeners from a specified event.
1225 * If you do not specify an event then all listeners will be removed.
1226 * That means every event will be emptied.
1227 * You can also pass a regex to remove all events that match it.
1228 *
1229 * @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed.
1230 * @return {Object} Current instance of EventEmitter for chaining.
1231 */
1232 proto.removeEvent = function removeEvent(evt) {
1233 var type = typeof evt;
1234 var events = this._getEvents();
1235 var key;
1236
1237 // Remove different things depending on the state of evt
1238 if (type === 'string') {
1239 // Remove all listeners for the specified event
1240 delete events[evt];
1241 }
1242 else if (evt instanceof RegExp) {
1243 // Remove all events matching the regex.
1244 for (key in events) {
1245 if (events.hasOwnProperty(key) && evt.test(key)) {
1246 delete events[key];
1247 }
1248 }
1249 }
1250 else {
1251 // Remove all listeners in all events
1252 delete this._events;
1253 }
1254
1255 return this;
1256 };
1257
1258 /**
1259 * Alias of removeEvent.
1260 *
1261 * Added to mirror the node API.
1262 */
1263 proto.removeAllListeners = alias('removeEvent');
1264
1265 /**
1266 * Emits an event of your choice.
1267 * When emitted, every listener attached to that event will be executed.
1268 * If you pass the optional argument array then those arguments will be passed to every listener upon execution.
1269 * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately.
1270 * So they will not arrive within the array on the other side, they will be separate.
1271 * You can also pass a regular expression to emit to all events that match it.
1272 *
1273 * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
1274 * @param {Array} [args] Optional array of arguments to be passed to each listener.
1275 * @return {Object} Current instance of EventEmitter for chaining.
1276 */
1277 proto.emitEvent = function emitEvent(evt, args) {
1278 var listenersMap = this.getListenersAsObject(evt);
1279 var listeners;
1280 var listener;
1281 var i;
1282 var key;
1283 var response;
1284
1285 for (key in listenersMap) {
1286 if (listenersMap.hasOwnProperty(key)) {
1287 listeners = listenersMap[key].slice(0);
1288 i = listeners.length;
1289
1290 while (i--) {
1291 // If the listener returns true then it shall be removed from the event
1292 // The function is executed either with a basic call or an apply if there is an args array
1293 listener = listeners[i];
1294
1295 if (listener.once === true) {
1296 this.removeListener(evt, listener.listener);
1297 }
1298
1299 response = listener.listener.apply(this, args || []);
1300
1301 if (response === this._getOnceReturnValue()) {
1302 this.removeListener(evt, listener.listener);
1303 }
1304 }
1305 }
1306 }
1307
1308 return this;
1309 };
1310
1311 /**
1312 * Alias of emitEvent
1313 */
1314 proto.trigger = alias('emitEvent');
1315
1316 /**
1317 * 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.
1318 * As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it.
1319 *
1320 * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
1321 * @param {...*} Optional additional arguments to be passed to each listener.
1322 * @return {Object} Current instance of EventEmitter for chaining.
1323 */
1324 proto.emit = function emit(evt) {
1325 var args = Array.prototype.slice.call(arguments, 1);
1326 return this.emitEvent(evt, args);
1327 };
1328
1329 /**
1330 * Sets the current value to check against when executing listeners. If a
1331 * listeners return value matches the one set here then it will be removed
1332 * after execution. This value defaults to true.
1333 *
1334 * @param {*} value The new value to check for when executing listeners.
1335 * @return {Object} Current instance of EventEmitter for chaining.
1336 */
1337 proto.setOnceReturnValue = function setOnceReturnValue(value) {
1338 this._onceReturnValue = value;
1339 return this;
1340 };
1341
1342 /**
1343 * Fetches the current value to check against when executing listeners. If
1344 * the listeners return value matches this one then it should be removed
1345 * automatically. It will return true by default.
1346 *
1347 * @return {*|Boolean} The current value to check for or the default, true.
1348 * @api private
1349 */
1350 proto._getOnceReturnValue = function _getOnceReturnValue() {
1351 if (this.hasOwnProperty('_onceReturnValue')) {
1352 return this._onceReturnValue;
1353 }
1354 else {
1355 return true;
1356 }
1357 };
1358
1359 /**
1360 * Fetches the events object and creates one if required.
1361 *
1362 * @return {Object} The events storage object.
1363 * @api private
1364 */
1365 proto._getEvents = function _getEvents() {
1366 return this._events || (this._events = {});
1367 };
1368
1369 /**
1370 * Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version.
1371 *
1372 * @return {Function} Non conflicting EventEmitter class.
1373 */
1374 EventEmitter.noConflict = function noConflict() {
1375 exports.EventEmitter = originalGlobalValue;
1376 return EventEmitter;
1377 };
1378
1379 // Expose the class either via AMD, CommonJS or the global object
1380 if (typeof define === 'function' && define.amd) {
1381 define(function () {
1382 return EventEmitter;
1383 });
1384 }
1385 else if (typeof module === 'object' && module.exports){
1386 module.exports = EventEmitter;
1387 }
1388 else {
1389 exports.EventEmitter = EventEmitter;
1390 }
1391 }.call(this));
1392
1393 },{}]},{},[1]);
1394 ; })();