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

1,528 lines 48.0 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('scrollY') ? scrollElement.scrollY : 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').toLowerCase().indexOf('#boxzilla-') === 0) {
862 var boxId = el.getAttribute('href').toLowerCase().substring("#boxzilla-".length);
863 Boxzilla.toggle(boxId);
864 }
865 }
866
867 var timers = {
868 start: function start() {
869 try {
870 var sessionTime = sessionStorage.getItem('boxzilla_timer');
871 if (sessionTime) siteTimer.time = sessionTime;
872 } catch (e) {}
873 siteTimer.start();
874 pageTimer.start();
875 },
876 stop: function stop() {
877 sessionStorage.setItem('boxzilla_timer', siteTimer.time);
878 siteTimer.stop();
879 pageTimer.stop();
880 }
881 };
882
883 // initialise & add event listeners
884 Boxzilla.init = function () {
885 document.body.addEventListener('click', onElementClick, false);
886
887 try {
888 pageViews = sessionStorage.getItem('boxzilla_pageviews') || 0;
889 } catch (e) {
890 pageViews = 0;
891 }
892
893 siteTimer = new Timer(0);
894 pageTimer = new Timer(0);
895
896 // insert styles into DOM
897 var styles = require('./styles.js');
898 var styleElement = document.createElement('style');
899 styleElement.setAttribute("type", "text/css");
900 styleElement.innerHTML = styles;
901 document.head.appendChild(styleElement);
902
903 // add overlay element to dom
904 overlay = document.createElement('div');
905 overlay.style.display = 'none';
906 overlay.id = 'boxzilla-overlay';
907 document.body.appendChild(overlay);
908
909 // event binds
910 scrollElement.addEventListener('touchstart', throttle(checkHeightCriteria), true);
911 scrollElement.addEventListener('scroll', throttle(checkHeightCriteria), true);
912 window.addEventListener('resize', throttle(recalculateHeights));
913 window.addEventListener('load', recalculateHeights);
914 overlay.addEventListener('click', onOverlayClick);
915 window.setInterval(checkTimeCriteria, 1000);
916 window.setTimeout(checkPageViewsCriteria, 1000);
917 document.documentElement.addEventListener('mouseleave', onMouseLeave);
918 document.documentElement.addEventListener('mouseenter', onMouseEnter);
919 document.addEventListener('keyup', onKeyUp);
920
921 timers.start();
922 window.addEventListener('focus', timers.start);
923 window.addEventListener('beforeunload', function () {
924 timers.stop();
925 sessionStorage.setItem('boxzilla_pageviews', ++pageViews);
926 });
927 window.addEventListener('blur', timers.stop);
928
929 Boxzilla.trigger('ready');
930 };
931
932 /**
933 * Create a new Box
934 *
935 * @param string id
936 * @param object opts
937 *
938 * @returns Box
939 */
940 Boxzilla.create = function (id, opts) {
941
942 // preserve backwards compat for minimumScreenWidth option
943 if (typeof opts.minimumScreenWidth !== "undefined") {
944 opts.screenWidthCondition = {
945 condition: "larger",
946 value: opts.minimumScreenWidth
947 };
948 }
949
950 var box = new Box(id, opts);
951 boxes.push(box);
952 return box;
953 };
954
955 Boxzilla.get = function (id) {
956 for (var i = 0; i < boxes.length; i++) {
957 var box = boxes[i];
958 if (box.id == id) {
959 return box;
960 }
961 }
962
963 throw new Error("No box exists with ID " + id);
964 };
965
966 // dismiss a single box (or all by omitting id param)
967 Boxzilla.dismiss = function (id) {
968 // if no id given, dismiss all current open boxes
969 if (typeof id === "undefined") {
970 boxes.forEach(function (box) {
971 box.dismiss();
972 });
973 } else {
974 Boxzilla.get(id).dismiss();
975 }
976 };
977
978 Boxzilla.hide = function (id) {
979 if (typeof id === "undefined") {
980 boxes.forEach(function (box) {
981 box.hide();
982 });
983 } else {
984 Boxzilla.get(id).hide();
985 }
986 };
987
988 Boxzilla.show = function (id) {
989 if (typeof id === "undefined") {
990 boxes.forEach(function (box) {
991 box.show();
992 });
993 } else {
994 Boxzilla.get(id).show();
995 }
996 };
997
998 Boxzilla.toggle = function (id) {
999 if (typeof id === "undefined") {
1000 boxes.forEach(function (box) {
1001 box.toggle();
1002 });
1003 } else {
1004 Boxzilla.get(id).toggle();
1005 }
1006 };
1007
1008 // expose each individual box.
1009 Boxzilla.boxes = boxes;
1010
1011 // expose boxzilla object
1012 window.Boxzilla = Boxzilla;
1013
1014 if (typeof module !== 'undefined' && module.exports) {
1015 module.exports = Boxzilla;
1016 }
1017
1018 },{"./box.js":3,"./styles.js":5,"./timer.js":6,"wolfy87-eventemitter":7}],5:[function(require,module,exports){
1019 "use strict";
1020
1021 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}";
1022 module.exports = styles;
1023
1024 },{}],6:[function(require,module,exports){
1025 'use strict';
1026
1027 var Timer = function Timer(start) {
1028 this.time = start;
1029 this.interval = 0;
1030 };
1031
1032 Timer.prototype.tick = function () {
1033 this.time++;
1034 };
1035
1036 Timer.prototype.start = function () {
1037 if (!this.interval) {
1038 this.interval = window.setInterval(this.tick.bind(this), 1000);
1039 }
1040 };
1041
1042 Timer.prototype.stop = function () {
1043 if (this.interval) {
1044 window.clearInterval(this.interval);
1045 this.interval = 0;
1046 }
1047 };
1048
1049 module.exports = Timer;
1050
1051 },{}],7:[function(require,module,exports){
1052 /*!
1053 * EventEmitter v4.2.11 - git.io/ee
1054 * Unlicense - http://unlicense.org/
1055 * Oliver Caldwell - http://oli.me.uk/
1056 * @preserve
1057 */
1058
1059 ;(function () {
1060 'use strict';
1061
1062 /**
1063 * Class for managing events.
1064 * Can be extended to provide event functionality in other classes.
1065 *
1066 * @class EventEmitter Manages event registering and emitting.
1067 */
1068 function EventEmitter() {}
1069
1070 // Shortcuts to improve speed and size
1071 var proto = EventEmitter.prototype;
1072 var exports = this;
1073 var originalGlobalValue = exports.EventEmitter;
1074
1075 /**
1076 * Finds the index of the listener for the event in its storage array.
1077 *
1078 * @param {Function[]} listeners Array of listeners to search through.
1079 * @param {Function} listener Method to look for.
1080 * @return {Number} Index of the specified listener, -1 if not found
1081 * @api private
1082 */
1083 function indexOfListener(listeners, listener) {
1084 var i = listeners.length;
1085 while (i--) {
1086 if (listeners[i].listener === listener) {
1087 return i;
1088 }
1089 }
1090
1091 return -1;
1092 }
1093
1094 /**
1095 * Alias a method while keeping the context correct, to allow for overwriting of target method.
1096 *
1097 * @param {String} name The name of the target method.
1098 * @return {Function} The aliased method
1099 * @api private
1100 */
1101 function alias(name) {
1102 return function aliasClosure() {
1103 return this[name].apply(this, arguments);
1104 };
1105 }
1106
1107 /**
1108 * Returns the listener array for the specified event.
1109 * Will initialise the event object and listener arrays if required.
1110 * 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.
1111 * Each property in the object response is an array of listener functions.
1112 *
1113 * @param {String|RegExp} evt Name of the event to return the listeners from.
1114 * @return {Function[]|Object} All listener functions for the event.
1115 */
1116 proto.getListeners = function getListeners(evt) {
1117 var events = this._getEvents();
1118 var response;
1119 var key;
1120
1121 // Return a concatenated array of all matching events if
1122 // the selector is a regular expression.
1123 if (evt instanceof RegExp) {
1124 response = {};
1125 for (key in events) {
1126 if (events.hasOwnProperty(key) && evt.test(key)) {
1127 response[key] = events[key];
1128 }
1129 }
1130 }
1131 else {
1132 response = events[evt] || (events[evt] = []);
1133 }
1134
1135 return response;
1136 };
1137
1138 /**
1139 * Takes a list of listener objects and flattens it into a list of listener functions.
1140 *
1141 * @param {Object[]} listeners Raw listener objects.
1142 * @return {Function[]} Just the listener functions.
1143 */
1144 proto.flattenListeners = function flattenListeners(listeners) {
1145 var flatListeners = [];
1146 var i;
1147
1148 for (i = 0; i < listeners.length; i += 1) {
1149 flatListeners.push(listeners[i].listener);
1150 }
1151
1152 return flatListeners;
1153 };
1154
1155 /**
1156 * 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.
1157 *
1158 * @param {String|RegExp} evt Name of the event to return the listeners from.
1159 * @return {Object} All listener functions for an event in an object.
1160 */
1161 proto.getListenersAsObject = function getListenersAsObject(evt) {
1162 var listeners = this.getListeners(evt);
1163 var response;
1164
1165 if (listeners instanceof Array) {
1166 response = {};
1167 response[evt] = listeners;
1168 }
1169
1170 return response || listeners;
1171 };
1172
1173 /**
1174 * Adds a listener function to the specified event.
1175 * The listener will not be added if it is a duplicate.
1176 * If the listener returns true then it will be removed after it is called.
1177 * If you pass a regular expression as the event name then the listener will be added to all events that match it.
1178 *
1179 * @param {String|RegExp} evt Name of the event to attach the listener to.
1180 * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
1181 * @return {Object} Current instance of EventEmitter for chaining.
1182 */
1183 proto.addListener = function addListener(evt, listener) {
1184 var listeners = this.getListenersAsObject(evt);
1185 var listenerIsWrapped = typeof listener === 'object';
1186 var key;
1187
1188 for (key in listeners) {
1189 if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) {
1190 listeners[key].push(listenerIsWrapped ? listener : {
1191 listener: listener,
1192 once: false
1193 });
1194 }
1195 }
1196
1197 return this;
1198 };
1199
1200 /**
1201 * Alias of addListener
1202 */
1203 proto.on = alias('addListener');
1204
1205 /**
1206 * Semi-alias of addListener. It will add a listener that will be
1207 * automatically removed after its first execution.
1208 *
1209 * @param {String|RegExp} evt Name of the event to attach the listener to.
1210 * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
1211 * @return {Object} Current instance of EventEmitter for chaining.
1212 */
1213 proto.addOnceListener = function addOnceListener(evt, listener) {
1214 return this.addListener(evt, {
1215 listener: listener,
1216 once: true
1217 });
1218 };
1219
1220 /**
1221 * Alias of addOnceListener.
1222 */
1223 proto.once = alias('addOnceListener');
1224
1225 /**
1226 * 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.
1227 * You need to tell it what event names should be matched by a regex.
1228 *
1229 * @param {String} evt Name of the event to create.
1230 * @return {Object} Current instance of EventEmitter for chaining.
1231 */
1232 proto.defineEvent = function defineEvent(evt) {
1233 this.getListeners(evt);
1234 return this;
1235 };
1236
1237 /**
1238 * Uses defineEvent to define multiple events.
1239 *
1240 * @param {String[]} evts An array of event names to define.
1241 * @return {Object} Current instance of EventEmitter for chaining.
1242 */
1243 proto.defineEvents = function defineEvents(evts) {
1244 for (var i = 0; i < evts.length; i += 1) {
1245 this.defineEvent(evts[i]);
1246 }
1247 return this;
1248 };
1249
1250 /**
1251 * Removes a listener function from the specified event.
1252 * When passed a regular expression as the event name, it will remove the listener from all events that match it.
1253 *
1254 * @param {String|RegExp} evt Name of the event to remove the listener from.
1255 * @param {Function} listener Method to remove from the event.
1256 * @return {Object} Current instance of EventEmitter for chaining.
1257 */
1258 proto.removeListener = function removeListener(evt, listener) {
1259 var listeners = this.getListenersAsObject(evt);
1260 var index;
1261 var key;
1262
1263 for (key in listeners) {
1264 if (listeners.hasOwnProperty(key)) {
1265 index = indexOfListener(listeners[key], listener);
1266
1267 if (index !== -1) {
1268 listeners[key].splice(index, 1);
1269 }
1270 }
1271 }
1272
1273 return this;
1274 };
1275
1276 /**
1277 * Alias of removeListener
1278 */
1279 proto.off = alias('removeListener');
1280
1281 /**
1282 * Adds listeners in bulk using the manipulateListeners method.
1283 * 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.
1284 * You can also pass it a regular expression to add the array of listeners to all events that match it.
1285 * Yeah, this function does quite a bit. That's probably a bad thing.
1286 *
1287 * @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.
1288 * @param {Function[]} [listeners] An optional array of listener functions to add.
1289 * @return {Object} Current instance of EventEmitter for chaining.
1290 */
1291 proto.addListeners = function addListeners(evt, listeners) {
1292 // Pass through to manipulateListeners
1293 return this.manipulateListeners(false, evt, listeners);
1294 };
1295
1296 /**
1297 * Removes listeners in bulk using the manipulateListeners method.
1298 * 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.
1299 * You can also pass it an event name and an array of listeners to be removed.
1300 * You can also pass it a regular expression to remove the listeners from all events that match it.
1301 *
1302 * @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.
1303 * @param {Function[]} [listeners] An optional array of listener functions to remove.
1304 * @return {Object} Current instance of EventEmitter for chaining.
1305 */
1306 proto.removeListeners = function removeListeners(evt, listeners) {
1307 // Pass through to manipulateListeners
1308 return this.manipulateListeners(true, evt, listeners);
1309 };
1310
1311 /**
1312 * 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.
1313 * The first argument will determine if the listeners are removed (true) or added (false).
1314 * 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.
1315 * You can also pass it an event name and an array of listeners to be added/removed.
1316 * You can also pass it a regular expression to manipulate the listeners of all events that match it.
1317 *
1318 * @param {Boolean} remove True if you want to remove listeners, false if you want to add.
1319 * @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.
1320 * @param {Function[]} [listeners] An optional array of listener functions to add/remove.
1321 * @return {Object} Current instance of EventEmitter for chaining.
1322 */
1323 proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) {
1324 var i;
1325 var value;
1326 var single = remove ? this.removeListener : this.addListener;
1327 var multiple = remove ? this.removeListeners : this.addListeners;
1328
1329 // If evt is an object then pass each of its properties to this method
1330 if (typeof evt === 'object' && !(evt instanceof RegExp)) {
1331 for (i in evt) {
1332 if (evt.hasOwnProperty(i) && (value = evt[i])) {
1333 // Pass the single listener straight through to the singular method
1334 if (typeof value === 'function') {
1335 single.call(this, i, value);
1336 }
1337 else {
1338 // Otherwise pass back to the multiple function
1339 multiple.call(this, i, value);
1340 }
1341 }
1342 }
1343 }
1344 else {
1345 // So evt must be a string
1346 // And listeners must be an array of listeners
1347 // Loop over it and pass each one to the multiple method
1348 i = listeners.length;
1349 while (i--) {
1350 single.call(this, evt, listeners[i]);
1351 }
1352 }
1353
1354 return this;
1355 };
1356
1357 /**
1358 * Removes all listeners from a specified event.
1359 * If you do not specify an event then all listeners will be removed.
1360 * That means every event will be emptied.
1361 * You can also pass a regex to remove all events that match it.
1362 *
1363 * @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed.
1364 * @return {Object} Current instance of EventEmitter for chaining.
1365 */
1366 proto.removeEvent = function removeEvent(evt) {
1367 var type = typeof evt;
1368 var events = this._getEvents();
1369 var key;
1370
1371 // Remove different things depending on the state of evt
1372 if (type === 'string') {
1373 // Remove all listeners for the specified event
1374 delete events[evt];
1375 }
1376 else if (evt instanceof RegExp) {
1377 // Remove all events matching the regex.
1378 for (key in events) {
1379 if (events.hasOwnProperty(key) && evt.test(key)) {
1380 delete events[key];
1381 }
1382 }
1383 }
1384 else {
1385 // Remove all listeners in all events
1386 delete this._events;
1387 }
1388
1389 return this;
1390 };
1391
1392 /**
1393 * Alias of removeEvent.
1394 *
1395 * Added to mirror the node API.
1396 */
1397 proto.removeAllListeners = alias('removeEvent');
1398
1399 /**
1400 * Emits an event of your choice.
1401 * When emitted, every listener attached to that event will be executed.
1402 * If you pass the optional argument array then those arguments will be passed to every listener upon execution.
1403 * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately.
1404 * So they will not arrive within the array on the other side, they will be separate.
1405 * You can also pass a regular expression to emit to all events that match it.
1406 *
1407 * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
1408 * @param {Array} [args] Optional array of arguments to be passed to each listener.
1409 * @return {Object} Current instance of EventEmitter for chaining.
1410 */
1411 proto.emitEvent = function emitEvent(evt, args) {
1412 var listenersMap = this.getListenersAsObject(evt);
1413 var listeners;
1414 var listener;
1415 var i;
1416 var key;
1417 var response;
1418
1419 for (key in listenersMap) {
1420 if (listenersMap.hasOwnProperty(key)) {
1421 listeners = listenersMap[key].slice(0);
1422 i = listeners.length;
1423
1424 while (i--) {
1425 // If the listener returns true then it shall be removed from the event
1426 // The function is executed either with a basic call or an apply if there is an args array
1427 listener = listeners[i];
1428
1429 if (listener.once === true) {
1430 this.removeListener(evt, listener.listener);
1431 }
1432
1433 response = listener.listener.apply(this, args || []);
1434
1435 if (response === this._getOnceReturnValue()) {
1436 this.removeListener(evt, listener.listener);
1437 }
1438 }
1439 }
1440 }
1441
1442 return this;
1443 };
1444
1445 /**
1446 * Alias of emitEvent
1447 */
1448 proto.trigger = alias('emitEvent');
1449
1450 /**
1451 * 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.
1452 * As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it.
1453 *
1454 * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
1455 * @param {...*} Optional additional arguments to be passed to each listener.
1456 * @return {Object} Current instance of EventEmitter for chaining.
1457 */
1458 proto.emit = function emit(evt) {
1459 var args = Array.prototype.slice.call(arguments, 1);
1460 return this.emitEvent(evt, args);
1461 };
1462
1463 /**
1464 * Sets the current value to check against when executing listeners. If a
1465 * listeners return value matches the one set here then it will be removed
1466 * after execution. This value defaults to true.
1467 *
1468 * @param {*} value The new value to check for when executing listeners.
1469 * @return {Object} Current instance of EventEmitter for chaining.
1470 */
1471 proto.setOnceReturnValue = function setOnceReturnValue(value) {
1472 this._onceReturnValue = value;
1473 return this;
1474 };
1475
1476 /**
1477 * Fetches the current value to check against when executing listeners. If
1478 * the listeners return value matches this one then it should be removed
1479 * automatically. It will return true by default.
1480 *
1481 * @return {*|Boolean} The current value to check for or the default, true.
1482 * @api private
1483 */
1484 proto._getOnceReturnValue = function _getOnceReturnValue() {
1485 if (this.hasOwnProperty('_onceReturnValue')) {
1486 return this._onceReturnValue;
1487 }
1488 else {
1489 return true;
1490 }
1491 };
1492
1493 /**
1494 * Fetches the events object and creates one if required.
1495 *
1496 * @return {Object} The events storage object.
1497 * @api private
1498 */
1499 proto._getEvents = function _getEvents() {
1500 return this._events || (this._events = {});
1501 };
1502
1503 /**
1504 * Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version.
1505 *
1506 * @return {Function} Non conflicting EventEmitter class.
1507 */
1508 EventEmitter.noConflict = function noConflict() {
1509 exports.EventEmitter = originalGlobalValue;
1510 return EventEmitter;
1511 };
1512
1513 // Expose the class either via AMD, CommonJS or the global object
1514 if (typeof define === 'function' && define.amd) {
1515 define(function () {
1516 return EventEmitter;
1517 });
1518 }
1519 else if (typeof module === 'object' && module.exports){
1520 module.exports = EventEmitter;
1521 }
1522 else {
1523 exports.EventEmitter = EventEmitter;
1524 }
1525 }.call(this));
1526
1527 },{}]},{},[1]);
1528 ; })();