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