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

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