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

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