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

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