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

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