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

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