/*! * Ender-Overlay: Highly Customizable Overlay for Ender * copyright Andras Nemeseri @nemeseri 2012 | License MIT * https://github.com/nemeseri/ender-overlay */ !function ($) { var is, transition; // from valentine is = { fun: function (f) { return typeof f === 'function'; }, arr: function (ar) { return ar instanceof Array; }, obj: function (o) { return o instanceof Object && !is.fun(o) && !is.arr(o); } }; /* Based on Bootstrap Mozilla and Webkit support only */ transition = (function () { var st = document.createElement('div').style, transitionEnd = 'TransitionEnd', transitionProp = 'Transition', support = st.transition !== undefined || st.WebkitTransition !== undefined || st.MozTransition !== undefined; return support && { prop: (function () { if (st.WebkitTransition !== undefined) { transitionProp = 'WebkitTransition'; } else if (st.MozTransition !== undefined) { transitionProp = 'MozTransition'; } return transitionProp; }()), end: (function () { if (st.WebkitTransition !== undefined) { transitionEnd = 'webkitTransitionEnd'; } else if (st.MozTransition !== undefined) { transitionEnd = 'transitionend'; } return transitionEnd; }()) }; }()); function extend() { // based on jQuery deep merge var options, name, src, copy, clone, target = arguments[0], i = 1, length = arguments.length; for (; i < length; i += 1) { if ((options = arguments[i]) !== null) { // Extend the base object for (name in options) { src = target[name]; copy = options[name]; if (target === copy) { continue; } if (copy && (is.obj(copy))) { clone = src && is.obj(src) ? src : {}; target[name] = extend(clone, copy); } else if (copy !== undefined) { target[name] = copy; } } } } return target; } function clone(obj) { if (null === obj || 'object' !== typeof obj) { return obj; } var copy = obj.constructor(), attr; for (attr in obj) { if (obj.hasOwnProperty(attr)) { copy[attr] = obj[attr]; } } return copy; } // from jquery function proxy(fn, context) { var slice = Array.prototype.slice, args = slice.call(arguments, 2); return function () { return fn.apply(context, args.concat(slice.call(arguments))); }; } function animate(options) { var el = options.el, complete = options.complete ? options.complete : function () {}, animation, dummy; // no animation obj OR animation is not available, // fallback to css and call the callback if (! options.animation || ! (el.animate || (options.css3transition && transition))) { el.css(options.fallbackCss); complete(); return; } // we will animate, apply start CSS if (options.animStartCss) { if (options.animStartCss.opacity === 0) { options.animStartCss.opacity = 0.01; // ie quirk } el.css(options.animStartCss); } animation = options.animation; // css3 setted, if available apply the css if (options.css3transition && transition) { dummy = el[0].offsetWidth; // force reflow; source: bootstrap el[0].style[transition.prop] = 'all ' + animation.duration + 'ms ease-out'; // takaritas delete animation.duration; el.css(animation); //el.unbind(transition.end); el.on(transition.end, function () { // delete transition properties and events el.off(transition.end); el[0].style[transition.prop] = 'none'; complete(); }); } else if (window.ender) { // use morpheus el.animate(extend(animation, {'complete': complete})); } else { // use animate from jquery el.animate(animation, animation.duration, 'swing', complete); } } /* OverlayMask Constructor */ function OverlayMask(settings) { this.init(settings); } OverlayMask.prototype = { init: function (options) { this.options = { id: 'ender-overlay-mask', zIndex: 9998, opacity: 0.6, color: "#777" }; extend(this.options, options || {}); var $mask = $('#' + this.options.id), opt = this.options; if (! $mask.length) { $mask = $('
') .attr('id', this.options.id) .css({ display: 'none', position: 'absolute', top: 0, left: 0 }) .appendTo('body'); } this.$mask = $mask; }, show: function () { // apply instance mask options var opt = this.options, docSize = this.getDocSize(), animObj = false; this.$mask.css({ zIndex: opt.zIndex, backgroundColor: opt.color, width: docSize.width, height: docSize.height }); if (opt.durationIn) { animObj = { opacity: opt.opacity, duration: opt.durationIn }; } animate({ el: this.$mask, animStartCss: { opacity: 0.01, // ie quirk display: 'block' }, animation: animObj, fallbackCss: {display: 'block', opacity: opt.opacity}, css3transition: opt.css3transition }); }, hide: function () { var opt = this.options, self = this, animObj = false; if (opt.durationOut) { animObj = { opacity: 0, duration: opt.durationOut }; } animate({ el: this.$mask, animation: animObj, complete: function () { self.$mask.css({display: 'none'}); }, fallbackCss: {display: 'none'}, css3transition: opt.css3transition }); }, getDocSize: function () { if (window.ender) { // ender return { width: $.doc().width, height: $.doc().height }; } else if (window.Zepto) { // zepto caches the document height.. var doc = window.document, html = doc.documentElement, ie = /msie/i.test(navigator.userAgent), vp = { width: ie ? html.clientWidth : self.innerWidth, height: ie ? html.clientHeight : self.innerHeight } return { width: Math.max(doc.body.scrollWidth, html.scrollWidth, vp.width), height: Math.max(doc.body.scrollHeight, html.scrollHeight, vp.height) }; } else { // jquery / zepto return { width: $(document).width(), height: $(document).height() }; } }, getMask: function () { return this.$mask; } }; /* Overlay Constructor */ function Overlay(el, settings) { this.init(el, settings); // only return the API // instead of this return this.getApi(); } Overlay.prototype = { init: function ($el, options) { this.options = { top: 80, position: 'absolute', cssClass: 'ender-overlay', close: '.close', trigger: null, zIndex: 9999, showMask: true, closeOnEsc: true, closeOnMaskClick: true, autoOpen: false, allowMultipleDisplay: false, // morpheus required for JS fallback css3transition: false, // experimental // start values before animation startAnimationCss: { opacity: 0.01 // ie quirk }, // morpheus animation options animationIn: { opacity: 1, duration: 250 }, animationOut: { opacity: 0, duration: 250 }, mask: {}, onBeforeOpen: function () {}, onBeforeClose: function () {}, onOpen: function () {}, onClose: function () {} }; this.setOptions(options); this.$overlay = $el.css({ display: 'none' }); if (this.options.showMask) { this.mask = new OverlayMask(this.options.mask); } // prevent multiple event binding if (! this.$overlay.attr('data-overlayloaded')) { this.attachEvents(); this.$overlay.attr('data-overlayloaded', 1); } if (this.options.autoOpen) { this.open(); } }, attachEvents: function () { var self = this, opt = this.options; // Bind open method to trigger's click event if (opt.trigger && $(opt.trigger).length) { $(opt.trigger).on('click', function (e) { e.preventDefault(); self.open(); }); } this.$overlay.on('click', opt.close, function (e) { e.preventDefault(); self.close(); }); // attach event listeners $(document).on('ender-overlay.close', function () { self.close(); }); $(document).on('ender-overlay.closeOverlay', function () { self.close(true); }); if (opt.closeOnEsc) { $(document).on('keyup', function (e) { self.onKeyUp(e); }); } if (this.mask && opt.closeOnMaskClick) { this.mask.getMask().on('click', function () { self.close(); }); } }, setupOverlay: function () { var opt = this.options, topPos = opt.top, scrollTop = $(window).scrollTop(), overlayWidth = this.$overlay.width(); // setup overlay this.$overlay .addClass(opt.cssClass) .appendTo('body'); if (opt.position === 'absolute') { topPos += scrollTop; } // width is not defined explicitly // so we try to find out if (overlayWidth === 0) { this.$overlay.css({ display: 'block', position: 'absolute', left: -9999 }); overlayWidth = this.$overlay.width(); } this.$overlay.css({ display: 'none', position: opt.position, top: topPos, left: '50%', zIndex: opt.zIndex, marginLeft: overlayWidth / 2 * -1 }); }, open: function (dontOpenMask) { var opt = this.options, self = this, animationIn = opt.animationIn ? clone(opt.animationIn) : false, api = this.getApi(); if (this.$overlay.css('display') === 'block' || opt.onBeforeOpen(api) === false) { return; } this.setupOverlay(); if (! opt.allowMultipleDisplay) { $(document).trigger('ender-overlay.closeOverlay'); } animate({ el: this.$overlay, animStartCss: extend({display: 'block'}, opt.startAnimationCss), animation: animationIn, complete: function () { if (animationIn && animationIn.opacity === 1) { self.$overlay.css({ 'filter': '' }); // ie quirk } self.options.onOpen(api); }, fallbackCss: {display: 'block', opacity: 1}, css3transition: opt.css3transition }); if (this.mask && typeof dontOpenMask === 'undefined') { this.mask.show(); } }, close: function (dontHideMask) { var opt = this.options, self = this, animationOut = opt.animationOut ? clone(opt.animationOut) : false, api = this.getApi(); if (opt.onBeforeClose(api) === false || this.$overlay.css('display') !== 'block') { return; } animate({ el: this.$overlay, animation: animationOut, complete: function () { self.$overlay.css({display: 'none'}); self.options.onClose(api); }, fallbackCss: {display: 'none', opacity: 0}, css3transition: opt.css3transition }); if (this.mask && typeof dontHideMask === 'undefined') { this.mask.hide(); } }, onKeyUp: function (e) { if (e.keyCode === 27 && this.$overlay.css('display') !== 'none') { this.close(); } }, getOverlay: function () { return this.$overlay; }, getOptions: function () { return this.options; }, setOptions: function (options) { extend(this.options, options || {}); var opt = this.options; if (opt.animationIn === 'none') { opt.animationIn = false; } if (opt.animationOut === 'none') { opt.animationOut = false; } if (opt.showMask) { // If there is no explicit duration set for OverlayMask // set it from overlay animation if (! opt.mask.durationIn && opt.animationIn && opt.animationIn.duration) { opt.mask.durationIn = opt.animationIn.duration; } if (! opt.mask.durationOut && opt.animationOut && opt.animationOut.duration) { opt.mask.durationOut = opt.animationOut.duration; } // no animation if (! opt.mask.durationIn && ! opt.animationIn) { opt.mask.durationIn = 0; } if (! opt.mask.durationOut && ! opt.animationOut) { opt.mask.durationOut = 0; } if (typeof opt.mask.css3transition !== 'boolean') { opt.mask.css3transition = opt.css3transition; } } }, getApi: function () { return { open: proxy(this.open, this), close: proxy(this.close, this), getOverlay: proxy(this.getOverlay, this), getOptions: proxy(this.getOptions, this), setOptions: proxy(this.setOptions, this) }; } }; $.fn.overlay = function (options) { var el = $(this).first(); return new Overlay(el, options); }; }(window.ender || window.jQuery || window.Zepto);