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

1,357 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 /**
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 each( obj, callback ) {
606 for( var key in obj ) {
607 if(! obj.hasOwnProperty(key)) continue;
608 callback(obj[key]);
609 }
610 }
611
612 function throttle(fn, threshhold, scope) {
613 threshhold || (threshhold = 250);
614 var last,
615 deferTimer;
616 return function () {
617 var context = scope || this;
618
619 var now = +new Date,
620 args = arguments;
621 if (last && now < last + threshhold) {
622 // hold on to it
623 clearTimeout(deferTimer);
624 deferTimer = setTimeout(function () {
625 last = now;
626 fn.apply(context, args);
627 }, threshhold);
628 } else {
629 last = now;
630 fn.apply(context, args);
631 }
632 };
633 }
634
635 // "keyup" listener
636 function onKeyUp(e) {
637 if (e.keyCode == 27) {
638 Boxzilla.dismiss();
639 }
640 }
641
642 // check "pageviews" criteria for each box
643 function checkPageViewsCriteria() {
644 each(boxes, function(box) {
645 if( ! box.mayAutoShow() ) {
646 return;
647 }
648
649 if( box.config.trigger.method === 'pageviews' && pageViews >= box.config.trigger.value ) {
650 box.trigger();
651 }
652 });
653 }
654
655 // check time trigger criteria for each box
656 function checkTimeCriteria() {
657 each(boxes, function(box) {
658 if( ! box.mayAutoShow() ) {
659 return;
660 }
661
662 // check "time on site" trigger
663 if (box.config.trigger.method === 'time_on_site' && siteTimer.time >= box.config.trigger.value) {
664 box.trigger();
665 }
666
667 // check "time on page" trigger
668 if (box.config.trigger.method === 'time_on_page' && pageTimer.time >= box.config.trigger.value) {
669 box.trigger();
670 }
671 });
672 }
673
674 // check triggerHeight criteria for all boxes
675 function checkHeightCriteria() {
676 var scrollY = ( window.scrollY || window.pageYOffset ) + window.innerHeight * 0.75;
677
678 each(boxes, function(box) {
679
680 if( ! box.mayAutoShow() || box.triggerHeight <= 0 ) {
681 return;
682 }
683
684 if( scrollY > box.triggerHeight ) {
685 box.trigger();
686 } else if( box.mayRehide() ) {
687 box.hide();
688 }
689 });
690 }
691
692 // recalculate heights and variables based on height
693 function recalculateHeights() {
694 each(boxes, function(box) {
695 box.setCustomBoxStyling();
696 });
697 }
698
699 function onOverlayClick(e) {
700 var x = e.offsetX;
701 var y = e.offsetY;
702
703 // calculate if click was near a box to avoid closing it (click error margin)
704 each(boxes, function(box) {
705 var rect = box.element.getBoundingClientRect();
706 var margin = 100 + ( window.innerWidth * 0.05 );
707
708 // if click was not anywhere near box, dismiss it.
709 if( x < ( rect.left - margin ) || x > ( rect.right + margin ) || y < ( rect.top - margin ) || y > ( rect.bottom + margin ) ) {
710 box.dismiss();
711 }
712 });
713 }
714
715 function triggerExitIntent() {
716 if(exitIntentTriggered) return;
717
718 each(boxes, function(box) {
719 if(box.mayAutoShow() && box.config.trigger.method === 'exit_intent' ) {
720 box.trigger();
721 }
722 });
723
724 exitIntentTriggered = true;
725 }
726
727 function onMouseLeave(e) {
728 var delay = 400;
729
730 // did mouse leave at top of window?
731 if( e.clientY <= 0 ) {
732 exitIntentDelayTimer = window.setTimeout(triggerExitIntent, delay);
733 }
734 }
735
736 function onMouseEnter() {
737 if( exitIntentDelayTimer ) {
738 window.clearInterval(exitIntentDelayTimer);
739 exitIntentDelayTimer = null;
740 }
741 }
742
743 var timers = {
744 start: function() {
745 var sessionTime = sessionStorage.getItem('boxzilla_timer');
746 if( sessionTime ) siteTimer.time = sessionTime;
747 siteTimer.start();
748 pageTimer.start();
749 },
750 stop: function() {
751 sessionStorage.setItem('boxzilla_timer', siteTimer.time);
752 siteTimer.stop();
753 pageTimer.stop();
754 }
755 };
756
757 // initialise & add event listeners
758 Boxzilla.init = function() {
759 siteTimer = new Timer(sessionStorage.getItem('boxzilla_timer') || 0);
760 pageTimer = new Timer(0);
761 pageViews = sessionStorage.getItem('boxzilla_pageviews') || 0;
762
763 // insert styles into DOM
764 var styles = require('./styles.js');
765 var styleElement = document.createElement('style');
766 styleElement.setAttribute("type", "text/css");
767 styleElement.innerHTML = styles;
768 document.head.appendChild(styleElement);
769
770 // add overlay element to dom
771 overlay = document.createElement('div');
772 overlay.style.display = 'none';
773 overlay.id = 'boxzilla-overlay';
774 document.body.appendChild(overlay);
775
776 // event binds
777 window.addEventListener('scroll', throttle(checkHeightCriteria));
778 window.addEventListener('resize', throttle(recalculateHeights));
779 window.addEventListener('load', recalculateHeights );
780 overlay.addEventListener('click', onOverlayClick);
781 window.setInterval(checkTimeCriteria, 1000);
782 window.setTimeout(checkPageViewsCriteria, 1000 );
783 document.addEventListener('mouseleave', onMouseLeave);
784 document.addEventListener('mouseenter', onMouseEnter);
785 document.addEventListener('keyup', onKeyUp);
786
787 timers.start();
788 window.addEventListener('focus', timers.start);
789 window.addEventListener('beforeunload', function() {
790 timers.stop();
791 sessionStorage.setItem('boxzilla_pageviews', ++pageViews);
792 });
793 window.addEventListener('blur', timers.stop);
794
795 Boxzilla.trigger('ready');
796 };
797
798 /**
799 * Create a new Box
800 *
801 * @param string id
802 * @param object opts
803 *
804 * @returns Box
805 */
806 Boxzilla.create = function(id, opts) {
807 boxes[id] = new Box(id, opts);
808 return boxes[id];
809 };
810
811 // dismiss a single box (or all by omitting id param)
812 Boxzilla.dismiss = function(id) {
813 // if no id given, dismiss all current open boxes
814 if( typeof(id) === "undefined" ) {
815 each(boxes, function(box) { box.dismiss(); });
816 } else if( typeof( boxes[id] ) === "object" ) {
817 boxes[id].dismiss();
818 }
819 };
820
821 Boxzilla.hide = function(id) {
822 if( typeof(id) === "undefined" ) {
823 each(boxes, function(box) { box.hide(); });
824 } else if( typeof( boxes[id] ) === "object" ) {
825 boxes[id].hide();
826 }
827 };
828
829 Boxzilla.show = function(id) {
830 if( typeof(id) === "undefined" ) {
831 each(boxes, function(box) { box.show(); });
832 } else if( typeof( boxes[id] ) === "object" ) {
833 boxes[id].show();
834 }
835 };
836
837 Boxzilla.toggle = function(id) {
838 if( typeof(id) === "undefined" ) {
839 each(boxes, function(box) { box.toggle(); });
840 } else if( typeof( boxes[id] ) === "object" ) {
841 boxes[id].toggle();
842 }
843 };
844
845 // expose each individual box.
846 Boxzilla.boxes = boxes;
847
848 window.Boxzilla = Boxzilla;
849
850 if ( typeof module !== 'undefined' && module.exports ) {
851 module.exports = Boxzilla;
852 }
853 },{"./box.js":3,"./styles.js":5,"./timer.js":6,"wolfy87-eventemitter":7}],5:[function(require,module,exports){
854 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}`;
855 module.exports = styles;
856 },{}],6:[function(require,module,exports){
857 'use strict';
858
859 var Timer = function(start) {
860 this.time = start;
861 this.interval = 0;
862 };
863
864 Timer.prototype.tick = function() {
865 this.time++;
866 };
867
868 Timer.prototype.start = function() {
869 if( ! this.interval ) {
870 this.interval = window.setInterval(this.tick.bind(this), 1000);
871 }
872 };
873
874 Timer.prototype.stop = function() {
875 window.clearInterval(this.interval);
876 this.interval = 0;
877 };
878
879 module.exports = Timer;
880 },{}],7:[function(require,module,exports){
881 /*!
882 * EventEmitter v4.2.11 - git.io/ee
883 * Unlicense - http://unlicense.org/
884 * Oliver Caldwell - http://oli.me.uk/
885 * @preserve
886 */
887
888 ;(function () {
889 'use strict';
890
891 /**
892 * Class for managing events.
893 * Can be extended to provide event functionality in other classes.
894 *
895 * @class EventEmitter Manages event registering and emitting.
896 */
897 function EventEmitter() {}
898
899 // Shortcuts to improve speed and size
900 var proto = EventEmitter.prototype;
901 var exports = this;
902 var originalGlobalValue = exports.EventEmitter;
903
904 /**
905 * Finds the index of the listener for the event in its storage array.
906 *
907 * @param {Function[]} listeners Array of listeners to search through.
908 * @param {Function} listener Method to look for.
909 * @return {Number} Index of the specified listener, -1 if not found
910 * @api private
911 */
912 function indexOfListener(listeners, listener) {
913 var i = listeners.length;
914 while (i--) {
915 if (listeners[i].listener === listener) {
916 return i;
917 }
918 }
919
920 return -1;
921 }
922
923 /**
924 * Alias a method while keeping the context correct, to allow for overwriting of target method.
925 *
926 * @param {String} name The name of the target method.
927 * @return {Function} The aliased method
928 * @api private
929 */
930 function alias(name) {
931 return function aliasClosure() {
932 return this[name].apply(this, arguments);
933 };
934 }
935
936 /**
937 * Returns the listener array for the specified event.
938 * Will initialise the event object and listener arrays if required.
939 * 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.
940 * Each property in the object response is an array of listener functions.
941 *
942 * @param {String|RegExp} evt Name of the event to return the listeners from.
943 * @return {Function[]|Object} All listener functions for the event.
944 */
945 proto.getListeners = function getListeners(evt) {
946 var events = this._getEvents();
947 var response;
948 var key;
949
950 // Return a concatenated array of all matching events if
951 // the selector is a regular expression.
952 if (evt instanceof RegExp) {
953 response = {};
954 for (key in events) {
955 if (events.hasOwnProperty(key) && evt.test(key)) {
956 response[key] = events[key];
957 }
958 }
959 }
960 else {
961 response = events[evt] || (events[evt] = []);
962 }
963
964 return response;
965 };
966
967 /**
968 * Takes a list of listener objects and flattens it into a list of listener functions.
969 *
970 * @param {Object[]} listeners Raw listener objects.
971 * @return {Function[]} Just the listener functions.
972 */
973 proto.flattenListeners = function flattenListeners(listeners) {
974 var flatListeners = [];
975 var i;
976
977 for (i = 0; i < listeners.length; i += 1) {
978 flatListeners.push(listeners[i].listener);
979 }
980
981 return flatListeners;
982 };
983
984 /**
985 * 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.
986 *
987 * @param {String|RegExp} evt Name of the event to return the listeners from.
988 * @return {Object} All listener functions for an event in an object.
989 */
990 proto.getListenersAsObject = function getListenersAsObject(evt) {
991 var listeners = this.getListeners(evt);
992 var response;
993
994 if (listeners instanceof Array) {
995 response = {};
996 response[evt] = listeners;
997 }
998
999 return response || listeners;
1000 };
1001
1002 /**
1003 * Adds a listener function to the specified event.
1004 * The listener will not be added if it is a duplicate.
1005 * If the listener returns true then it will be removed after it is called.
1006 * If you pass a regular expression as the event name then the listener will be added to all events that match it.
1007 *
1008 * @param {String|RegExp} evt Name of the event to attach the listener to.
1009 * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
1010 * @return {Object} Current instance of EventEmitter for chaining.
1011 */
1012 proto.addListener = function addListener(evt, listener) {
1013 var listeners = this.getListenersAsObject(evt);
1014 var listenerIsWrapped = typeof listener === 'object';
1015 var key;
1016
1017 for (key in listeners) {
1018 if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) {
1019 listeners[key].push(listenerIsWrapped ? listener : {
1020 listener: listener,
1021 once: false
1022 });
1023 }
1024 }
1025
1026 return this;
1027 };
1028
1029 /**
1030 * Alias of addListener
1031 */
1032 proto.on = alias('addListener');
1033
1034 /**
1035 * Semi-alias of addListener. It will add a listener that will be
1036 * automatically removed after its first execution.
1037 *
1038 * @param {String|RegExp} evt Name of the event to attach the listener to.
1039 * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
1040 * @return {Object} Current instance of EventEmitter for chaining.
1041 */
1042 proto.addOnceListener = function addOnceListener(evt, listener) {
1043 return this.addListener(evt, {
1044 listener: listener,
1045 once: true
1046 });
1047 };
1048
1049 /**
1050 * Alias of addOnceListener.
1051 */
1052 proto.once = alias('addOnceListener');
1053
1054 /**
1055 * 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.
1056 * You need to tell it what event names should be matched by a regex.
1057 *
1058 * @param {String} evt Name of the event to create.
1059 * @return {Object} Current instance of EventEmitter for chaining.
1060 */
1061 proto.defineEvent = function defineEvent(evt) {
1062 this.getListeners(evt);
1063 return this;
1064 };
1065
1066 /**
1067 * Uses defineEvent to define multiple events.
1068 *
1069 * @param {String[]} evts An array of event names to define.
1070 * @return {Object} Current instance of EventEmitter for chaining.
1071 */
1072 proto.defineEvents = function defineEvents(evts) {
1073 for (var i = 0; i < evts.length; i += 1) {
1074 this.defineEvent(evts[i]);
1075 }
1076 return this;
1077 };
1078
1079 /**
1080 * Removes a listener function from the specified event.
1081 * When passed a regular expression as the event name, it will remove the listener from all events that match it.
1082 *
1083 * @param {String|RegExp} evt Name of the event to remove the listener from.
1084 * @param {Function} listener Method to remove from the event.
1085 * @return {Object} Current instance of EventEmitter for chaining.
1086 */
1087 proto.removeListener = function removeListener(evt, listener) {
1088 var listeners = this.getListenersAsObject(evt);
1089 var index;
1090 var key;
1091
1092 for (key in listeners) {
1093 if (listeners.hasOwnProperty(key)) {
1094 index = indexOfListener(listeners[key], listener);
1095
1096 if (index !== -1) {
1097 listeners[key].splice(index, 1);
1098 }
1099 }
1100 }
1101
1102 return this;
1103 };
1104
1105 /**
1106 * Alias of removeListener
1107 */
1108 proto.off = alias('removeListener');
1109
1110 /**
1111 * Adds listeners in bulk using the manipulateListeners method.
1112 * 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.
1113 * You can also pass it a regular expression to add the array of listeners to all events that match it.
1114 * Yeah, this function does quite a bit. That's probably a bad thing.
1115 *
1116 * @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.
1117 * @param {Function[]} [listeners] An optional array of listener functions to add.
1118 * @return {Object} Current instance of EventEmitter for chaining.
1119 */
1120 proto.addListeners = function addListeners(evt, listeners) {
1121 // Pass through to manipulateListeners
1122 return this.manipulateListeners(false, evt, listeners);
1123 };
1124
1125 /**
1126 * Removes listeners in bulk using the manipulateListeners method.
1127 * 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.
1128 * You can also pass it an event name and an array of listeners to be removed.
1129 * You can also pass it a regular expression to remove the listeners from all events that match it.
1130 *
1131 * @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.
1132 * @param {Function[]} [listeners] An optional array of listener functions to remove.
1133 * @return {Object} Current instance of EventEmitter for chaining.
1134 */
1135 proto.removeListeners = function removeListeners(evt, listeners) {
1136 // Pass through to manipulateListeners
1137 return this.manipulateListeners(true, evt, listeners);
1138 };
1139
1140 /**
1141 * 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.
1142 * The first argument will determine if the listeners are removed (true) or added (false).
1143 * 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.
1144 * You can also pass it an event name and an array of listeners to be added/removed.
1145 * You can also pass it a regular expression to manipulate the listeners of all events that match it.
1146 *
1147 * @param {Boolean} remove True if you want to remove listeners, false if you want to add.
1148 * @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.
1149 * @param {Function[]} [listeners] An optional array of listener functions to add/remove.
1150 * @return {Object} Current instance of EventEmitter for chaining.
1151 */
1152 proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) {
1153 var i;
1154 var value;
1155 var single = remove ? this.removeListener : this.addListener;
1156 var multiple = remove ? this.removeListeners : this.addListeners;
1157
1158 // If evt is an object then pass each of its properties to this method
1159 if (typeof evt === 'object' && !(evt instanceof RegExp)) {
1160 for (i in evt) {
1161 if (evt.hasOwnProperty(i) && (value = evt[i])) {
1162 // Pass the single listener straight through to the singular method
1163 if (typeof value === 'function') {
1164 single.call(this, i, value);
1165 }
1166 else {
1167 // Otherwise pass back to the multiple function
1168 multiple.call(this, i, value);
1169 }
1170 }
1171 }
1172 }
1173 else {
1174 // So evt must be a string
1175 // And listeners must be an array of listeners
1176 // Loop over it and pass each one to the multiple method
1177 i = listeners.length;
1178 while (i--) {
1179 single.call(this, evt, listeners[i]);
1180 }
1181 }
1182
1183 return this;
1184 };
1185
1186 /**
1187 * Removes all listeners from a specified event.
1188 * If you do not specify an event then all listeners will be removed.
1189 * That means every event will be emptied.
1190 * You can also pass a regex to remove all events that match it.
1191 *
1192 * @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed.
1193 * @return {Object} Current instance of EventEmitter for chaining.
1194 */
1195 proto.removeEvent = function removeEvent(evt) {
1196 var type = typeof evt;
1197 var events = this._getEvents();
1198 var key;
1199
1200 // Remove different things depending on the state of evt
1201 if (type === 'string') {
1202 // Remove all listeners for the specified event
1203 delete events[evt];
1204 }
1205 else if (evt instanceof RegExp) {
1206 // Remove all events matching the regex.
1207 for (key in events) {
1208 if (events.hasOwnProperty(key) && evt.test(key)) {
1209 delete events[key];
1210 }
1211 }
1212 }
1213 else {
1214 // Remove all listeners in all events
1215 delete this._events;
1216 }
1217
1218 return this;
1219 };
1220
1221 /**
1222 * Alias of removeEvent.
1223 *
1224 * Added to mirror the node API.
1225 */
1226 proto.removeAllListeners = alias('removeEvent');
1227
1228 /**
1229 * Emits an event of your choice.
1230 * When emitted, every listener attached to that event will be executed.
1231 * If you pass the optional argument array then those arguments will be passed to every listener upon execution.
1232 * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately.
1233 * So they will not arrive within the array on the other side, they will be separate.
1234 * You can also pass a regular expression to emit to all events that match it.
1235 *
1236 * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
1237 * @param {Array} [args] Optional array of arguments to be passed to each listener.
1238 * @return {Object} Current instance of EventEmitter for chaining.
1239 */
1240 proto.emitEvent = function emitEvent(evt, args) {
1241 var listenersMap = this.getListenersAsObject(evt);
1242 var listeners;
1243 var listener;
1244 var i;
1245 var key;
1246 var response;
1247
1248 for (key in listenersMap) {
1249 if (listenersMap.hasOwnProperty(key)) {
1250 listeners = listenersMap[key].slice(0);
1251 i = listeners.length;
1252
1253 while (i--) {
1254 // If the listener returns true then it shall be removed from the event
1255 // The function is executed either with a basic call or an apply if there is an args array
1256 listener = listeners[i];
1257
1258 if (listener.once === true) {
1259 this.removeListener(evt, listener.listener);
1260 }
1261
1262 response = listener.listener.apply(this, args || []);
1263
1264 if (response === this._getOnceReturnValue()) {
1265 this.removeListener(evt, listener.listener);
1266 }
1267 }
1268 }
1269 }
1270
1271 return this;
1272 };
1273
1274 /**
1275 * Alias of emitEvent
1276 */
1277 proto.trigger = alias('emitEvent');
1278
1279 /**
1280 * 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.
1281 * As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it.
1282 *
1283 * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
1284 * @param {...*} Optional additional arguments to be passed to each listener.
1285 * @return {Object} Current instance of EventEmitter for chaining.
1286 */
1287 proto.emit = function emit(evt) {
1288 var args = Array.prototype.slice.call(arguments, 1);
1289 return this.emitEvent(evt, args);
1290 };
1291
1292 /**
1293 * Sets the current value to check against when executing listeners. If a
1294 * listeners return value matches the one set here then it will be removed
1295 * after execution. This value defaults to true.
1296 *
1297 * @param {*} value The new value to check for when executing listeners.
1298 * @return {Object} Current instance of EventEmitter for chaining.
1299 */
1300 proto.setOnceReturnValue = function setOnceReturnValue(value) {
1301 this._onceReturnValue = value;
1302 return this;
1303 };
1304
1305 /**
1306 * Fetches the current value to check against when executing listeners. If
1307 * the listeners return value matches this one then it should be removed
1308 * automatically. It will return true by default.
1309 *
1310 * @return {*|Boolean} The current value to check for or the default, true.
1311 * @api private
1312 */
1313 proto._getOnceReturnValue = function _getOnceReturnValue() {
1314 if (this.hasOwnProperty('_onceReturnValue')) {
1315 return this._onceReturnValue;
1316 }
1317 else {
1318 return true;
1319 }
1320 };
1321
1322 /**
1323 * Fetches the events object and creates one if required.
1324 *
1325 * @return {Object} The events storage object.
1326 * @api private
1327 */
1328 proto._getEvents = function _getEvents() {
1329 return this._events || (this._events = {});
1330 };
1331
1332 /**
1333 * Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version.
1334 *
1335 * @return {Function} Non conflicting EventEmitter class.
1336 */
1337 EventEmitter.noConflict = function noConflict() {
1338 exports.EventEmitter = originalGlobalValue;
1339 return EventEmitter;
1340 };
1341
1342 // Expose the class either via AMD, CommonJS or the global object
1343 if (typeof define === 'function' && define.amd) {
1344 define(function () {
1345 return EventEmitter;
1346 });
1347 }
1348 else if (typeof module === 'object' && module.exports){
1349 module.exports = EventEmitter;
1350 }
1351 else {
1352 exports.EventEmitter = EventEmitter;
1353 }
1354 }.call(this));
1355
1356 },{}]},{},[1]);
1357 ; })();