PluginProbe
Boxzilla – WordPress Popup Builder / 3.1.12
Boxzilla – WordPress Popup Builder v3.1.12
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.12, at assets/js/script.js

1,518 lines 47.8 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 'screenWidthCondition': null,
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.screenWidthCondition || !this.config.screenWidthCondition.value) {
551 return true;
552 }
553
554 switch (this.config.screenWidthCondition.condition) {
555 case "larger":
556 return window.innerWidth > this.config.screenWidthCondition.value;
557 case "smaller":
558 return window.innerWidth < this.config.screenWidthCondition.value;
559 }
560
561 // meh.. condition should be "smaller" or "larger", just return true.
562 return true;
563 };
564
565 // is this box enabled?
566 Box.prototype.mayAutoShow = function () {
567
568 if (this.dismissed) {
569 return false;
570 }
571
572 // check if box fits on given minimum screen width
573 if (!this.fits()) {
574 return false;
575 }
576
577 // if trigger empty or error in calculating triggerHeight, return false
578 if (!this.config.trigger) {
579 return false;
580 }
581
582 // rely on cookie value (show if not set, don't show if set)
583 return !this.cookieSet;
584 };
585
586 Box.prototype.mayRehide = function () {
587 return this.config.rehide && this.triggered;
588 };
589
590 Box.prototype.isCookieSet = function () {
591 // always show on test mode
592 if (this.config.testMode) {
593 return false;
594 }
595
596 // if either cookie is null or trigger & dismiss are both falsey, don't bother checking.
597 if (!this.config.cookie || !this.config.cookie.triggered && !this.config.cookie.dismissed) {
598 return false;
599 }
600
601 var cookieSet = document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + 'boxzilla_box_' + this.id + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1") === "true";
602 return cookieSet;
603 };
604
605 // set cookie that disables automatically showing the box
606 Box.prototype.setCookie = function (hours) {
607 var expiryDate = new Date();
608 expiryDate.setHours(expiryDate.getHours() + hours);
609 document.cookie = 'boxzilla_box_' + this.id + '=true; expires=' + expiryDate.toUTCString() + '; path=/';
610 };
611
612 Box.prototype.trigger = function () {
613 var shown = this.show();
614 if (!shown) {
615 return;
616 }
617
618 this.triggered = true;
619 if (this.config.cookie && this.config.cookie.triggered) {
620 this.setCookie(this.config.cookie.triggered);
621 }
622 };
623
624 /**
625 * Dismisses the box and optionally sets a cookie.
626 *
627 * @param e The event that triggered this dismissal.
628 * @returns {boolean}
629 */
630 Box.prototype.dismiss = function (e) {
631 // prevent default action
632 e && e.preventDefault();
633
634 // only dismiss box if it's currently open.
635 if (!this.visible) {
636 return false;
637 }
638
639 // hide box element
640 this.hide();
641
642 // set cookie
643 if (this.config.cookie && this.config.cookie.dismissed) {
644 this.setCookie(this.config.cookie.dismissed);
645 }
646
647 this.dismissed = true;
648 Boxzilla.trigger('box.dismiss', [this]);
649 return true;
650 };
651
652 module.exports = function (_Boxzilla) {
653 Boxzilla = _Boxzilla;
654 return Box;
655 };
656
657 },{"./animator.js":2}],4:[function(require,module,exports){
658 'use strict';
659
660 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; };
661
662 var EventEmitter = require('wolfy87-eventemitter'),
663 Boxzilla = Object.create(EventEmitter.prototype),
664 Box = require('./box.js')(Boxzilla),
665 Timer = require('./timer.js'),
666 boxes = [],
667 overlay,
668 scrollElement = window,
669 exitIntentDelayTimer,
670 exitIntentTriggered,
671 siteTimer,
672 pageTimer,
673 pageViews;
674
675 function throttle(fn, threshhold, scope) {
676 threshhold || (threshhold = 250);
677 var last, deferTimer;
678 return function () {
679 var context = scope || this;
680
681 var now = +new Date(),
682 args = arguments;
683 if (last && now < last + threshhold) {
684 // hold on to it
685 clearTimeout(deferTimer);
686 deferTimer = setTimeout(function () {
687 last = now;
688 fn.apply(context, args);
689 }, threshhold);
690 } else {
691 last = now;
692 fn.apply(context, args);
693 }
694 };
695 }
696
697 // "keyup" listener
698 function onKeyUp(e) {
699 if (e.keyCode == 27) {
700 Boxzilla.dismiss();
701 }
702 }
703
704 // check "pageviews" criteria for each box
705 function checkPageViewsCriteria() {
706
707 // don't bother if another box is currently open
708 if (isAnyBoxVisible()) {
709 return;
710 }
711
712 boxes.forEach(function (box) {
713 if (!box.mayAutoShow()) {
714 return;
715 }
716
717 if (box.config.trigger.method === 'pageviews' && pageViews >= box.config.trigger.value) {
718 box.trigger();
719 }
720 });
721 }
722
723 // check time trigger criteria for each box
724 function checkTimeCriteria() {
725 // don't bother if another box is currently open
726 if (isAnyBoxVisible()) {
727 return;
728 }
729
730 boxes.forEach(function (box) {
731 if (!box.mayAutoShow()) {
732 return;
733 }
734
735 // check "time on site" trigger
736 if (box.config.trigger.method === 'time_on_site' && siteTimer.time >= box.config.trigger.value) {
737 box.trigger();
738 }
739
740 // check "time on page" trigger
741 if (box.config.trigger.method === 'time_on_page' && pageTimer.time >= box.config.trigger.value) {
742 box.trigger();
743 }
744 });
745 }
746
747 // check triggerHeight criteria for all boxes
748 function checkHeightCriteria() {
749
750 var scrollY = scrollElement.hasOwnProperty('scrollY') ? scrollElement.scrollY : scrollElement.scrollTop;
751 scrollY = scrollY + window.innerHeight * 0.75;
752
753 boxes.forEach(function (box) {
754 if (!box.mayAutoShow() || box.triggerHeight <= 0) {
755 return;
756 }
757
758 if (scrollY > box.triggerHeight) {
759 // don't bother if another box is currently open
760 if (isAnyBoxVisible()) {
761 return;
762 }
763
764 // trigger box
765 box.trigger();
766 } else if (box.mayRehide()) {
767 box.hide();
768 }
769 });
770 }
771
772 // recalculate heights and variables based on height
773 function recalculateHeights() {
774 boxes.forEach(function (box) {
775 box.setCustomBoxStyling();
776 });
777 }
778
779 function onOverlayClick(e) {
780 var x = e.offsetX;
781 var y = e.offsetY;
782
783 // calculate if click was less than 40px outside box to avoid closing it by accident
784 boxes.forEach(function (box) {
785 var rect = box.element.getBoundingClientRect();
786 var margin = 40;
787
788 // if click was not anywhere near box, dismiss it.
789 if (x < rect.left - margin || x > rect.right + margin || y < rect.top - margin || y > rect.bottom + margin) {
790 box.dismiss();
791 }
792 });
793 }
794
795 function triggerExitIntent() {
796 // do nothing if already triggered OR another box is visible.
797 if (exitIntentTriggered || isAnyBoxVisible()) {
798 return;
799 }
800
801 boxes.forEach(function (box) {
802 if (box.mayAutoShow() && box.config.trigger.method === 'exit_intent') {
803 box.trigger();
804 }
805 });
806
807 exitIntentTriggered = true;
808 }
809
810 function onMouseLeave(e) {
811 var delay = 400;
812
813 // did mouse leave at top of window?
814 if (e.clientY <= 0) {
815 exitIntentDelayTimer = window.setTimeout(triggerExitIntent, delay);
816 }
817 }
818
819 function isAnyBoxVisible() {
820
821 for (var i = 0; i < boxes.length; i++) {
822 var box = boxes[i];
823
824 if (box.visible) {
825 return true;
826 }
827 }
828
829 return false;
830 }
831
832 function onMouseEnter() {
833 if (exitIntentDelayTimer) {
834 window.clearInterval(exitIntentDelayTimer);
835 exitIntentDelayTimer = null;
836 }
837 }
838
839 function onElementClick(e) {
840 // find <a> element in up to 3 parent elements
841 var el = e.target || e.srcElement;
842 var depth = 3;
843 for (var i = 0; i <= depth; i++) {
844 if (!el || el.tagName === 'A') {
845 break;
846 }
847
848 el = el.parentElement;
849 }
850
851 if (el && el.tagName === 'A' && el.getAttribute('href').toLowerCase().indexOf('#boxzilla-') === 0) {
852 var boxId = el.getAttribute('href').toLowerCase().substring("#boxzilla-".length);
853 Boxzilla.toggle(boxId);
854 }
855 }
856
857 var timers = {
858 start: function start() {
859 try {
860 var sessionTime = sessionStorage.getItem('boxzilla_timer');
861 if (sessionTime) siteTimer.time = sessionTime;
862 } catch (e) {}
863 siteTimer.start();
864 pageTimer.start();
865 },
866 stop: function stop() {
867 sessionStorage.setItem('boxzilla_timer', siteTimer.time);
868 siteTimer.stop();
869 pageTimer.stop();
870 }
871 };
872
873 // initialise & add event listeners
874 Boxzilla.init = function () {
875 document.body.addEventListener('click', onElementClick, false);
876
877 try {
878 pageViews = sessionStorage.getItem('boxzilla_pageviews') || 0;
879 } catch (e) {
880 pageViews = 0;
881 }
882
883 siteTimer = new Timer(0);
884 pageTimer = new Timer(0);
885
886 // insert styles into DOM
887 var styles = require('./styles.js');
888 var styleElement = document.createElement('style');
889 styleElement.setAttribute("type", "text/css");
890 styleElement.innerHTML = styles;
891 document.head.appendChild(styleElement);
892
893 // add overlay element to dom
894 overlay = document.createElement('div');
895 overlay.style.display = 'none';
896 overlay.id = 'boxzilla-overlay';
897 document.body.appendChild(overlay);
898
899 // event binds
900 scrollElement.addEventListener('touchstart', throttle(checkHeightCriteria), true);
901 scrollElement.addEventListener('scroll', throttle(checkHeightCriteria), true);
902 window.addEventListener('resize', throttle(recalculateHeights));
903 window.addEventListener('load', recalculateHeights);
904 overlay.addEventListener('click', onOverlayClick);
905 window.setInterval(checkTimeCriteria, 1000);
906 window.setTimeout(checkPageViewsCriteria, 1000);
907 document.documentElement.addEventListener('mouseleave', onMouseLeave);
908 document.documentElement.addEventListener('mouseenter', onMouseEnter);
909 document.addEventListener('keyup', onKeyUp);
910
911 timers.start();
912 window.addEventListener('focus', timers.start);
913 window.addEventListener('beforeunload', function () {
914 timers.stop();
915 sessionStorage.setItem('boxzilla_pageviews', ++pageViews);
916 });
917 window.addEventListener('blur', timers.stop);
918
919 Boxzilla.trigger('ready');
920 };
921
922 /**
923 * Create a new Box
924 *
925 * @param string id
926 * @param object opts
927 *
928 * @returns Box
929 */
930 Boxzilla.create = function (id, opts) {
931
932 // preserve backwards compat for minimumScreenWidth option
933 if (typeof opts.minimumScreenWidth !== "undefined") {
934 opts.screenWidthCondition = {
935 condition: "larger",
936 value: opts.minimumScreenWidth
937 };
938 }
939
940 var box = new Box(id, opts);
941 boxes.push(box);
942 return box;
943 };
944
945 Boxzilla.get = function (id) {
946 for (var i = 0; i < boxes.length; i++) {
947 var box = boxes[i];
948 if (box.id == id) {
949 return box;
950 }
951 }
952
953 throw new Error("No box exists with ID " + id);
954 };
955
956 // dismiss a single box (or all by omitting id param)
957 Boxzilla.dismiss = function (id) {
958 // if no id given, dismiss all current open boxes
959 if (typeof id === "undefined") {
960 boxes.forEach(function (box) {
961 box.dismiss();
962 });
963 } else if (_typeof(boxes[id]) === "object") {
964 Boxzilla.get(id).dismiss();
965 }
966 };
967
968 Boxzilla.hide = function (id) {
969 if (typeof id === "undefined") {
970 boxes.forEach(function (box) {
971 box.hide();
972 });
973 } else {
974 Boxzilla.get(id).hide();
975 }
976 };
977
978 Boxzilla.show = function (id) {
979 if (typeof id === "undefined") {
980 boxes.forEach(function (box) {
981 box.show();
982 });
983 } else {
984 Boxzilla.get(id).show();
985 }
986 };
987
988 Boxzilla.toggle = function (id) {
989 if (typeof id === "undefined") {
990 boxes.forEach(function (box) {
991 box.toggle();
992 });
993 } else {
994 Boxzilla.get(id).toggle();
995 }
996 };
997
998 // expose each individual box.
999 Boxzilla.boxes = boxes;
1000
1001 // expose boxzilla object
1002 window.Boxzilla = Boxzilla;
1003
1004 if (typeof module !== 'undefined' && module.exports) {
1005 module.exports = Boxzilla;
1006 }
1007
1008 },{"./box.js":3,"./styles.js":5,"./timer.js":6,"wolfy87-eventemitter":7}],5:[function(require,module,exports){
1009 "use strict";
1010
1011 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}";
1012 module.exports = styles;
1013
1014 },{}],6:[function(require,module,exports){
1015 'use strict';
1016
1017 var Timer = function Timer(start) {
1018 this.time = start;
1019 this.interval = 0;
1020 };
1021
1022 Timer.prototype.tick = function () {
1023 this.time++;
1024 };
1025
1026 Timer.prototype.start = function () {
1027 if (!this.interval) {
1028 this.interval = window.setInterval(this.tick.bind(this), 1000);
1029 }
1030 };
1031
1032 Timer.prototype.stop = function () {
1033 if (this.interval) {
1034 window.clearInterval(this.interval);
1035 this.interval = 0;
1036 }
1037 };
1038
1039 module.exports = Timer;
1040
1041 },{}],7:[function(require,module,exports){
1042 /*!
1043 * EventEmitter v4.2.11 - git.io/ee
1044 * Unlicense - http://unlicense.org/
1045 * Oliver Caldwell - http://oli.me.uk/
1046 * @preserve
1047 */
1048
1049 ;(function () {
1050 'use strict';
1051
1052 /**
1053 * Class for managing events.
1054 * Can be extended to provide event functionality in other classes.
1055 *
1056 * @class EventEmitter Manages event registering and emitting.
1057 */
1058 function EventEmitter() {}
1059
1060 // Shortcuts to improve speed and size
1061 var proto = EventEmitter.prototype;
1062 var exports = this;
1063 var originalGlobalValue = exports.EventEmitter;
1064
1065 /**
1066 * Finds the index of the listener for the event in its storage array.
1067 *
1068 * @param {Function[]} listeners Array of listeners to search through.
1069 * @param {Function} listener Method to look for.
1070 * @return {Number} Index of the specified listener, -1 if not found
1071 * @api private
1072 */
1073 function indexOfListener(listeners, listener) {
1074 var i = listeners.length;
1075 while (i--) {
1076 if (listeners[i].listener === listener) {
1077 return i;
1078 }
1079 }
1080
1081 return -1;
1082 }
1083
1084 /**
1085 * Alias a method while keeping the context correct, to allow for overwriting of target method.
1086 *
1087 * @param {String} name The name of the target method.
1088 * @return {Function} The aliased method
1089 * @api private
1090 */
1091 function alias(name) {
1092 return function aliasClosure() {
1093 return this[name].apply(this, arguments);
1094 };
1095 }
1096
1097 /**
1098 * Returns the listener array for the specified event.
1099 * Will initialise the event object and listener arrays if required.
1100 * 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.
1101 * Each property in the object response is an array of listener functions.
1102 *
1103 * @param {String|RegExp} evt Name of the event to return the listeners from.
1104 * @return {Function[]|Object} All listener functions for the event.
1105 */
1106 proto.getListeners = function getListeners(evt) {
1107 var events = this._getEvents();
1108 var response;
1109 var key;
1110
1111 // Return a concatenated array of all matching events if
1112 // the selector is a regular expression.
1113 if (evt instanceof RegExp) {
1114 response = {};
1115 for (key in events) {
1116 if (events.hasOwnProperty(key) && evt.test(key)) {
1117 response[key] = events[key];
1118 }
1119 }
1120 }
1121 else {
1122 response = events[evt] || (events[evt] = []);
1123 }
1124
1125 return response;
1126 };
1127
1128 /**
1129 * Takes a list of listener objects and flattens it into a list of listener functions.
1130 *
1131 * @param {Object[]} listeners Raw listener objects.
1132 * @return {Function[]} Just the listener functions.
1133 */
1134 proto.flattenListeners = function flattenListeners(listeners) {
1135 var flatListeners = [];
1136 var i;
1137
1138 for (i = 0; i < listeners.length; i += 1) {
1139 flatListeners.push(listeners[i].listener);
1140 }
1141
1142 return flatListeners;
1143 };
1144
1145 /**
1146 * 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.
1147 *
1148 * @param {String|RegExp} evt Name of the event to return the listeners from.
1149 * @return {Object} All listener functions for an event in an object.
1150 */
1151 proto.getListenersAsObject = function getListenersAsObject(evt) {
1152 var listeners = this.getListeners(evt);
1153 var response;
1154
1155 if (listeners instanceof Array) {
1156 response = {};
1157 response[evt] = listeners;
1158 }
1159
1160 return response || listeners;
1161 };
1162
1163 /**
1164 * Adds a listener function to the specified event.
1165 * The listener will not be added if it is a duplicate.
1166 * If the listener returns true then it will be removed after it is called.
1167 * If you pass a regular expression as the event name then the listener will be added to all events that match it.
1168 *
1169 * @param {String|RegExp} evt Name of the event to attach the listener to.
1170 * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
1171 * @return {Object} Current instance of EventEmitter for chaining.
1172 */
1173 proto.addListener = function addListener(evt, listener) {
1174 var listeners = this.getListenersAsObject(evt);
1175 var listenerIsWrapped = typeof listener === 'object';
1176 var key;
1177
1178 for (key in listeners) {
1179 if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) {
1180 listeners[key].push(listenerIsWrapped ? listener : {
1181 listener: listener,
1182 once: false
1183 });
1184 }
1185 }
1186
1187 return this;
1188 };
1189
1190 /**
1191 * Alias of addListener
1192 */
1193 proto.on = alias('addListener');
1194
1195 /**
1196 * Semi-alias of addListener. It will add a listener that will be
1197 * automatically removed after its first execution.
1198 *
1199 * @param {String|RegExp} evt Name of the event to attach the listener to.
1200 * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
1201 * @return {Object} Current instance of EventEmitter for chaining.
1202 */
1203 proto.addOnceListener = function addOnceListener(evt, listener) {
1204 return this.addListener(evt, {
1205 listener: listener,
1206 once: true
1207 });
1208 };
1209
1210 /**
1211 * Alias of addOnceListener.
1212 */
1213 proto.once = alias('addOnceListener');
1214
1215 /**
1216 * 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.
1217 * You need to tell it what event names should be matched by a regex.
1218 *
1219 * @param {String} evt Name of the event to create.
1220 * @return {Object} Current instance of EventEmitter for chaining.
1221 */
1222 proto.defineEvent = function defineEvent(evt) {
1223 this.getListeners(evt);
1224 return this;
1225 };
1226
1227 /**
1228 * Uses defineEvent to define multiple events.
1229 *
1230 * @param {String[]} evts An array of event names to define.
1231 * @return {Object} Current instance of EventEmitter for chaining.
1232 */
1233 proto.defineEvents = function defineEvents(evts) {
1234 for (var i = 0; i < evts.length; i += 1) {
1235 this.defineEvent(evts[i]);
1236 }
1237 return this;
1238 };
1239
1240 /**
1241 * Removes a listener function from the specified event.
1242 * When passed a regular expression as the event name, it will remove the listener from all events that match it.
1243 *
1244 * @param {String|RegExp} evt Name of the event to remove the listener from.
1245 * @param {Function} listener Method to remove from the event.
1246 * @return {Object} Current instance of EventEmitter for chaining.
1247 */
1248 proto.removeListener = function removeListener(evt, listener) {
1249 var listeners = this.getListenersAsObject(evt);
1250 var index;
1251 var key;
1252
1253 for (key in listeners) {
1254 if (listeners.hasOwnProperty(key)) {
1255 index = indexOfListener(listeners[key], listener);
1256
1257 if (index !== -1) {
1258 listeners[key].splice(index, 1);
1259 }
1260 }
1261 }
1262
1263 return this;
1264 };
1265
1266 /**
1267 * Alias of removeListener
1268 */
1269 proto.off = alias('removeListener');
1270
1271 /**
1272 * Adds listeners in bulk using the manipulateListeners method.
1273 * 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.
1274 * You can also pass it a regular expression to add the array of listeners to all events that match it.
1275 * Yeah, this function does quite a bit. That's probably a bad thing.
1276 *
1277 * @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.
1278 * @param {Function[]} [listeners] An optional array of listener functions to add.
1279 * @return {Object} Current instance of EventEmitter for chaining.
1280 */
1281 proto.addListeners = function addListeners(evt, listeners) {
1282 // Pass through to manipulateListeners
1283 return this.manipulateListeners(false, evt, listeners);
1284 };
1285
1286 /**
1287 * Removes listeners in bulk using the manipulateListeners method.
1288 * 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.
1289 * You can also pass it an event name and an array of listeners to be removed.
1290 * You can also pass it a regular expression to remove the listeners from all events that match it.
1291 *
1292 * @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.
1293 * @param {Function[]} [listeners] An optional array of listener functions to remove.
1294 * @return {Object} Current instance of EventEmitter for chaining.
1295 */
1296 proto.removeListeners = function removeListeners(evt, listeners) {
1297 // Pass through to manipulateListeners
1298 return this.manipulateListeners(true, evt, listeners);
1299 };
1300
1301 /**
1302 * 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.
1303 * The first argument will determine if the listeners are removed (true) or added (false).
1304 * 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.
1305 * You can also pass it an event name and an array of listeners to be added/removed.
1306 * You can also pass it a regular expression to manipulate the listeners of all events that match it.
1307 *
1308 * @param {Boolean} remove True if you want to remove listeners, false if you want to add.
1309 * @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.
1310 * @param {Function[]} [listeners] An optional array of listener functions to add/remove.
1311 * @return {Object} Current instance of EventEmitter for chaining.
1312 */
1313 proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) {
1314 var i;
1315 var value;
1316 var single = remove ? this.removeListener : this.addListener;
1317 var multiple = remove ? this.removeListeners : this.addListeners;
1318
1319 // If evt is an object then pass each of its properties to this method
1320 if (typeof evt === 'object' && !(evt instanceof RegExp)) {
1321 for (i in evt) {
1322 if (evt.hasOwnProperty(i) && (value = evt[i])) {
1323 // Pass the single listener straight through to the singular method
1324 if (typeof value === 'function') {
1325 single.call(this, i, value);
1326 }
1327 else {
1328 // Otherwise pass back to the multiple function
1329 multiple.call(this, i, value);
1330 }
1331 }
1332 }
1333 }
1334 else {
1335 // So evt must be a string
1336 // And listeners must be an array of listeners
1337 // Loop over it and pass each one to the multiple method
1338 i = listeners.length;
1339 while (i--) {
1340 single.call(this, evt, listeners[i]);
1341 }
1342 }
1343
1344 return this;
1345 };
1346
1347 /**
1348 * Removes all listeners from a specified event.
1349 * If you do not specify an event then all listeners will be removed.
1350 * That means every event will be emptied.
1351 * You can also pass a regex to remove all events that match it.
1352 *
1353 * @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed.
1354 * @return {Object} Current instance of EventEmitter for chaining.
1355 */
1356 proto.removeEvent = function removeEvent(evt) {
1357 var type = typeof evt;
1358 var events = this._getEvents();
1359 var key;
1360
1361 // Remove different things depending on the state of evt
1362 if (type === 'string') {
1363 // Remove all listeners for the specified event
1364 delete events[evt];
1365 }
1366 else if (evt instanceof RegExp) {
1367 // Remove all events matching the regex.
1368 for (key in events) {
1369 if (events.hasOwnProperty(key) && evt.test(key)) {
1370 delete events[key];
1371 }
1372 }
1373 }
1374 else {
1375 // Remove all listeners in all events
1376 delete this._events;
1377 }
1378
1379 return this;
1380 };
1381
1382 /**
1383 * Alias of removeEvent.
1384 *
1385 * Added to mirror the node API.
1386 */
1387 proto.removeAllListeners = alias('removeEvent');
1388
1389 /**
1390 * Emits an event of your choice.
1391 * When emitted, every listener attached to that event will be executed.
1392 * If you pass the optional argument array then those arguments will be passed to every listener upon execution.
1393 * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately.
1394 * So they will not arrive within the array on the other side, they will be separate.
1395 * You can also pass a regular expression to emit to all events that match it.
1396 *
1397 * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
1398 * @param {Array} [args] Optional array of arguments to be passed to each listener.
1399 * @return {Object} Current instance of EventEmitter for chaining.
1400 */
1401 proto.emitEvent = function emitEvent(evt, args) {
1402 var listenersMap = this.getListenersAsObject(evt);
1403 var listeners;
1404 var listener;
1405 var i;
1406 var key;
1407 var response;
1408
1409 for (key in listenersMap) {
1410 if (listenersMap.hasOwnProperty(key)) {
1411 listeners = listenersMap[key].slice(0);
1412 i = listeners.length;
1413
1414 while (i--) {
1415 // If the listener returns true then it shall be removed from the event
1416 // The function is executed either with a basic call or an apply if there is an args array
1417 listener = listeners[i];
1418
1419 if (listener.once === true) {
1420 this.removeListener(evt, listener.listener);
1421 }
1422
1423 response = listener.listener.apply(this, args || []);
1424
1425 if (response === this._getOnceReturnValue()) {
1426 this.removeListener(evt, listener.listener);
1427 }
1428 }
1429 }
1430 }
1431
1432 return this;
1433 };
1434
1435 /**
1436 * Alias of emitEvent
1437 */
1438 proto.trigger = alias('emitEvent');
1439
1440 /**
1441 * 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.
1442 * As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it.
1443 *
1444 * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
1445 * @param {...*} Optional additional arguments to be passed to each listener.
1446 * @return {Object} Current instance of EventEmitter for chaining.
1447 */
1448 proto.emit = function emit(evt) {
1449 var args = Array.prototype.slice.call(arguments, 1);
1450 return this.emitEvent(evt, args);
1451 };
1452
1453 /**
1454 * Sets the current value to check against when executing listeners. If a
1455 * listeners return value matches the one set here then it will be removed
1456 * after execution. This value defaults to true.
1457 *
1458 * @param {*} value The new value to check for when executing listeners.
1459 * @return {Object} Current instance of EventEmitter for chaining.
1460 */
1461 proto.setOnceReturnValue = function setOnceReturnValue(value) {
1462 this._onceReturnValue = value;
1463 return this;
1464 };
1465
1466 /**
1467 * Fetches the current value to check against when executing listeners. If
1468 * the listeners return value matches this one then it should be removed
1469 * automatically. It will return true by default.
1470 *
1471 * @return {*|Boolean} The current value to check for or the default, true.
1472 * @api private
1473 */
1474 proto._getOnceReturnValue = function _getOnceReturnValue() {
1475 if (this.hasOwnProperty('_onceReturnValue')) {
1476 return this._onceReturnValue;
1477 }
1478 else {
1479 return true;
1480 }
1481 };
1482
1483 /**
1484 * Fetches the events object and creates one if required.
1485 *
1486 * @return {Object} The events storage object.
1487 * @api private
1488 */
1489 proto._getEvents = function _getEvents() {
1490 return this._events || (this._events = {});
1491 };
1492
1493 /**
1494 * Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version.
1495 *
1496 * @return {Function} Non conflicting EventEmitter class.
1497 */
1498 EventEmitter.noConflict = function noConflict() {
1499 exports.EventEmitter = originalGlobalValue;
1500 return EventEmitter;
1501 };
1502
1503 // Expose the class either via AMD, CommonJS or the global object
1504 if (typeof define === 'function' && define.amd) {
1505 define(function () {
1506 return EventEmitter;
1507 });
1508 }
1509 else if (typeof module === 'object' && module.exports){
1510 module.exports = EventEmitter;
1511 }
1512 else {
1513 exports.EventEmitter = EventEmitter;
1514 }
1515 }.call(this));
1516
1517 },{}]},{},[1]);
1518 ; })();