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

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