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

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