/** * @author Ryan Johnson * @copyright 2008 PersonalGrid Corporation * @package LivePipe UI * @license MIT * @url http://livepipe.net/control/window * @require prototype.js, effects.js, draggable.js, resizable.js, livepipe.js */ //adds onDraw and constrainToViewport option to draggable if(typeof(Draggable) != 'undefined'){ //allows the point to be modified with an onDraw callback Draggable.prototype.draw = function(point) { var pos = Position.cumulativeOffset(this.element); if(this.options.ghosting) { var r = Position.realOffset(this.element); pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; } var d = this.currentDelta(); pos[0] -= d[0]; pos[1] -= d[1]; if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; } var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this)); if(this.options.snap) { if(typeof this.options.snap == 'function') { p = this.options.snap(p[0],p[1],this); } else { if(this.options.snap instanceof Array) { p = p.map( function(v, i) {return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) } else { p = p.map( function(v) {return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) } } } if(this.options.onDraw) this.options.onDraw.bind(this)(p); else{ var style = this.element.style; if(this.options.constrainToViewport){ var viewport_dimensions = document.viewport.getDimensions(); var container_dimensions = this.element.getDimensions(); var margin_top = parseInt(this.element.getStyle('margin-top')); var margin_left = parseInt(this.element.getStyle('margin-left')); var boundary = [[ 0 - margin_left, 0 - margin_top ],[ (viewport_dimensions.width - container_dimensions.width) - margin_left, (viewport_dimensions.height - container_dimensions.height) - margin_top ]]; if((!this.options.constraint) || (this.options.constraint=='horizontal')){ if((p[0] >= boundary[0][0]) && (p[0] <= boundary[1][0])) this.element.style.left = p[0] + "px"; else this.element.style.left = ((p[0] < boundary[0][0]) ? boundary[0][0] : boundary[1][0]) + "px"; } if((!this.options.constraint) || (this.options.constraint=='vertical')){ if((p[1] >= boundary[0][1] ) && (p[1] <= boundary[1][1])) this.element.style.top = p[1] + "px"; else this.element.style.top = ((p[1] <= boundary[0][1]) ? boundary[0][1] : boundary[1][1]) + "px"; } }else{ if((!this.options.constraint) || (this.options.constraint=='horizontal')) style.left = p[0] + "px"; if((!this.options.constraint) || (this.options.constraint=='vertical')) style.top = p[1] + "px"; } if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering } }; } if(typeof(Prototype) == "undefined") throw "Control.Window requires Prototype to be loaded."; if(typeof(IframeShim) == "undefined") throw "Control.Window requires IframeShim to be loaded."; if(typeof(Object.Event) == "undefined") throw "Control.Window requires Object.Event to be loaded."; /* known issues: - when iframe is clicked is does not gain focus - safari can't open multiple iframes properly - constrainToViewport: body must have no margin or padding for this to work properly - iframe will be mis positioned during fade in - document.viewport does not account for scrollbars (this will eventually be fixed in the prototype core) notes - setting constrainToViewport only works when the page is not scrollable - setting draggable: true will negate the effects of position: center */ Control.Window = Class.create({ initialize: function(container,options){ Control.Window.windows.push(this); //attribute initialization this.container = false; this.isOpen = false; this.href = false; this.sourceContainer = false; //this is optionally the container that will open the window this.ajaxRequest = false; this.remoteContentLoaded = false; //this is set when the code to load the remote content is run, onRemoteContentLoaded is fired when the connection is closed this.numberInSequence = Control.Window.windows.length + 1; //only useful for the effect scoping this.indicator = false; this.effects = { fade: false, appear: false }; this.indicatorEffects = { fade: false, appear: false }; //options this.options = Object.extend({ //lifecycle beforeOpen: Prototype.emptyFunction, afterOpen: Prototype.emptyFunction, beforeClose: Prototype.emptyFunction, afterClose: Prototype.emptyFunction, //dimensions and modes height: null, width: null, className: false, position: 'center', //'center', 'center_once', 'relative', [x,y], [function(){return x;},function(){return y;}] offsetLeft: 0, //available only for anchors opening the window, or windows set to position: hover offsetTop: 0, //"" iframe: false, //if the window has an href, this will display the href as an iframe instead of requesting the url as an an Ajax.Request hover: false, //element object to hover over, or if "true" only available for windows with sourceContainer (an anchor or any element already on the page with an href attribute) indicator: false, //element to show or hide when ajax requests, images and iframes are loading closeOnClick: false, //does not work with hover,can be: true (click anywhere), 'container' (will refer to this.container), or element (a specific element) iframeshim: true, //whether or not to position an iFrameShim underneath the window //effects fade: false, fadeDuration: 0.75, //draggable draggable: false, onDrag: Prototype.emptyFunction, //resizable resizable: false, minHeight: false, minWidth: false, maxHeight: false, maxWidth: false, onResize: Prototype.emptyFunction, //draggable and resizable constrainToViewport: false, //ajax method: 'post', parameters: {}, onComplete: Prototype.emptyFunction, onSuccess: Prototype.emptyFunction, onFailure: Prototype.emptyFunction, onException: Prototype.emptyFunction, //any element with an href (image,iframe,ajax) will call this after it is done loading onRemoteContentLoaded: Prototype.emptyFunction, insertRemoteContentAt: false //false will set this to this.container, can be string selector (first returned will be selected), or an Element that must be a child of this.container },options || {}); //container setup this.indicator = this.options.indicator ? $(this.options.indicator) : false; if(container){ if(typeof(container) == "string" && container.match(Control.Window.uriRegex)) this.href = container; else{ this.container = $(container); //need to create the container now for tooltips (or hover: element with no container already on the page) //second call made below will not create the container since the check is done inside createDefaultContainer() this.createDefaultContainer(container); //if an element with an href was passed in we use it to activate the window if(this.container && ((this.container.readAttribute('href') && this.container.readAttribute('href') != '') || (this.options.hover && this.options.hover !== true))){ if(this.options.hover && this.options.hover !== true) this.sourceContainer = $(this.options.hover); else{ this.sourceContainer = this.container; this.href = this.container.readAttribute('href'); var rel = this.href.match(/^#(.+)$/); if(rel && rel[1]){ this.container = $(rel[1]); this.href = false; }else this.container = false; } //hover or click handling this.sourceContainerOpenHandler = function(event){ this.open(event); event.stop(); return false; }.bindAsEventListener(this); this.sourceContainerCloseHandler = function(event){ this.close(event); }.bindAsEventListener(this); this.sourceContainerMouseMoveHandler = function(event){ this.position(event); }.bindAsEventListener(this); if(this.options.hover){ this.sourceContainer.observe('mouseenter',this.sourceContainerOpenHandler); this.sourceContainer.observe('mouseleave',this.sourceContainerCloseHandler); if(this.options.position == 'mouse') this.sourceContainer.observe('mousemove',this.sourceContainerMouseMoveHandler); }else this.sourceContainer.observe('click',this.sourceContainerOpenHandler); } } } this.createDefaultContainer(container); if(this.options.insertRemoteContentAt === false) this.options.insertRemoteContentAt = this.container; var styles = { margin: 0, position: 'absolute', zIndex: Control.Window.initialZIndexForWindow() }; if(this.options.width) styles.width = $value(this.options.width) + 'px'; if(this.options.height) styles.height = $value(this.options.height) + 'px'; this.container.setStyle(styles); if(this.options.className) this.container.addClassName(this.options.className); this.positionHandler = this.position.bindAsEventListener(this); this.outOfBoundsPositionHandler = this.ensureInBounds.bindAsEventListener(this); this.bringToFrontHandler = this.bringToFront.bindAsEventListener(this); this.container.observe('mousedown',this.bringToFrontHandler); this.container.hide(); this.closeHandler = this.close.bindAsEventListener(this); //iframeshim setup if(this.options.iframeshim){ this.iFrameShim = new IframeShim(); this.iFrameShim.hide(); } //resizable support this.applyResizable(); //draggable support this.applyDraggable(); //makes sure the window can't go out of bounds Event.observe(window,'resize',this.outOfBoundsPositionHandler); this.notify('afterInitialize'); }, open: function(event){ if(this.isOpen){ this.bringToFront(); return false; } if(this.notify('beforeOpen') === false) return false; //closeOnClick if(this.options.closeOnClick){ if(this.options.closeOnClick === true) this.closeOnClickContainer = $(document.body); else if(this.options.closeOnClick == 'container') this.closeOnClickContainer = this.container; else if (this.options.closeOnClick == 'overlay'){ Control.Overlay.load(); this.closeOnClickContainer = Control.Overlay.container; }else this.closeOnClickContainer = $(this.options.closeOnClick); this.closeOnClickContainer.observe('click',this.closeHandler); } if(this.href && !this.options.iframe && !this.remoteContentLoaded){ //link to image this.remoteContentLoaded = true; if(this.href.match(/\.(jpe?g|gif|png|tiff?)$/i)){ var img = new Element('img'); img.observe('load',function(img){ this.getRemoteContentInsertionTarget().insert(img); this.position(); if(this.notify('onRemoteContentLoaded') !== false){ if(this.options.indicator) this.hideIndicator(); this.finishOpen(); } }.bind(this,img)); img.writeAttribute('src',this.href); }else{ //if this is an ajax window it will only open if the request is successful if(!this.ajaxRequest){ if(this.options.indicator) this.showIndicator(); this.ajaxRequest = new Ajax.Request(this.href,{ method: this.options.method, parameters: this.options.parameters, onComplete: function(request){ this.notify('onComplete',request); this.ajaxRequest = false; }.bind(this), onSuccess: function(request){ this.getRemoteContentInsertionTarget().insert(request.responseText); this.notify('onSuccess',request); if(this.notify('onRemoteContentLoaded') !== false){ if(this.options.indicator) this.hideIndicator(); this.finishOpen(); } }.bind(this), onFailure: function(request){ this.notify('onFailure',request); if(this.options.indicator) this.hideIndicator(); }.bind(this), onException: function(request,e){ this.notify('onException',request,e); if(this.options.indicator) this.hideIndicator(); }.bind(this) }); } } return true; }else if(this.options.iframe && !this.remoteContentLoaded){ //iframe this.remoteContentLoaded = true; if(this.options.indicator) this.showIndicator(); this.getRemoteContentInsertionTarget().insert(Control.Window.iframeTemplate.evaluate({ href: this.href })); var iframe = this.container.down('iframe'); iframe.onload = function(){ this.notify('onRemoteContentLoaded'); if(this.options.indicator) this.hideIndicator(); iframe.onload = null; }.bind(this); } this.finishOpen(event); return true }, close: function(event){ //event may or may not be present if(!this.isOpen || this.notify('beforeClose',event) === false) return false; if(this.options.closeOnClick) this.closeOnClickContainer.stopObserving('click',this.closeHandler); if(this.options.fade){ this.effects.fade = new Effect.Fade(this.container,{ queue: { position: 'front', scope: 'Control.Window' + this.numberInSequence }, from: 1, to: 0, duration: this.options.fadeDuration / 2, afterFinish: function(){ if(this.iFrameShim) this.iFrameShim.hide(); this.isOpen = false; this.notify('afterClose'); }.bind(this) }); }else{ this.container.hide(); if(this.iFrameShim) this.iFrameShim.hide(); } if(this.ajaxRequest) this.ajaxRequest.transport.abort(); if(!(this.options.draggable || this.options.resizable) && this.options.position == 'center') Event.stopObserving(window,'resize',this.positionHandler); if(!this.options.draggable && this.options.position == 'center') Event.stopObserving(window,'scroll',this.positionHandler); if(this.options.indicator) this.hideIndicator(); if(!this.options.fade){ this.isOpen = false; this.notify('afterClose'); } return true; }, position: function(event){ if(this.isMobile() && (typeof event != 'undefined' && (event.type == 'resize' || event.type == 'scroll'))){ return; } //this is up top for performance reasons if(this.options.position == 'mouse'){ var xy = [Event.pointerX(event),Event.pointerY(event)]; this.container.setStyle({ top: xy[1] + $value(this.options.offsetTop) + 'px', left: xy[0] + $value(this.options.offsetLeft) + 'px' }); return; } var container_dimensions = this.container.getDimensions(); var viewport_dimensions = document.viewport.getDimensions(); Position.prepare(); var offset_left = (Position.deltaX + Math.floor((viewport_dimensions.width - container_dimensions.width) / 2)); var offset_top = (Position.deltaY + ((viewport_dimensions.height > container_dimensions.height) ? Math.floor((viewport_dimensions.height - container_dimensions.height) / 2) : 0)); if(this.options.position == 'center' || this.options.position == 'center_once'){ this.container.setStyle({ top: (container_dimensions.height <= viewport_dimensions.height) ? ((offset_top != null && offset_top > 0) ? offset_top : 0) + 'px' : 0, left: (container_dimensions.width <= viewport_dimensions.width) ? ((offset_left != null && offset_left > 0) ? offset_left : 0) + 'px' : 0 }); }else if(this.options.position == 'relative'){ var xy = this.sourceContainer.cumulativeOffset(); var top = xy[1] + $value(this.options.offsetTop); var left = xy[0] + $value(this.options.offsetLeft); this.container.setStyle({ top: (container_dimensions.height <= viewport_dimensions.height) ? (this.options.constrainToViewport ? Math.max(0,Math.min(viewport_dimensions.height - (container_dimensions.height),top)) : top) + 'px' : 0, left: (container_dimensions.width <= viewport_dimensions.width) ? (this.options.constrainToViewport ? Math.max(0,Math.min(viewport_dimensions.width - (container_dimensions.width),left)) : left) + 'px' : 0 }); }else if(this.options.position.length){ var top = $value(this.options.position[1]) + $value(this.options.offsetTop); var left = $value(this.options.position[0]) + $value(this.options.offsetLeft); this.container.setStyle({ top: (container_dimensions.height <= viewport_dimensions.height) ? (this.options.constrainToViewport ? Math.max(0,Math.min(viewport_dimensions.height - (container_dimensions.height),top)) : top) + 'px' : 0, left: (container_dimensions.width <= viewport_dimensions.width) ? (this.options.constrainToViewport ? Math.max(0,Math.min(viewport_dimensions.width - (container_dimensions.width),left)) : left) + 'px' : 0 }); } if(this.iFrameShim) this.updateIFrameShimZIndex(); }, isMobile: function(){ if(typeof sagePayIsMobile == 'undefined') { return false; } return sagePayIsMobile(); }, ensureInBounds: function(){ if(!this.isOpen) return; if(this.isMobile()){ return; } var viewport_dimensions = document.viewport.getDimensions(); var container_offset = this.container.cumulativeOffset(); var container_dimensions = this.container.getDimensions(); if(container_offset.left + container_dimensions.width > viewport_dimensions.width){ this.container.setStyle({ left: (Math.max(0,viewport_dimensions.width - container_dimensions.width)) + 'px' }); } if(container_offset.top + container_dimensions.height > viewport_dimensions.height){ this.container.setStyle({ top: (Math.max(0,viewport_dimensions.height - container_dimensions.height)) + 'px' }); } }, bringToFront: function(){ Control.Window.bringToFront(this); this.notify('bringToFront'); }, destroy: function(){ this.container.stopObserving('mousedown',this.bringToFrontHandler); if(this.draggable){ Draggables.removeObserver(this.container); this.draggable.handle.stopObserving('mousedown',this.bringToFrontHandler); this.draggable.destroy(); } if(this.resizable){ Resizables.removeObserver(this.container); this.resizable.handle.stopObserving('mousedown',this.bringToFrontHandler); this.resizable.destroy(); } if(this.container && !this.sourceContainer) this.container.remove(); if(this.sourceContainer){ if(this.options.hover){ this.sourceContainer.stopObserving('mouseenter',this.sourceContainerOpenHandler); this.sourceContainer.stopObserving('mouseleave',this.sourceContainerCloseHandler); if(this.options.position == 'mouse') this.sourceContainer.stopObserving('mousemove',this.sourceContainerMouseMoveHandler); }else this.sourceContainer.stopObserving('click',this.sourceContainerOpenHandler); } if(this.iFrameShim) this.iFrameShim.destroy(); Event.stopObserving(window,'resize',this.outOfBoundsPositionHandler); Control.Window.windows = Control.Window.windows.without(this); this.notify('afterDestroy'); }, //private applyResizable: function(){ if(this.options.resizable){ if(typeof(Resizable) == "undefined") throw "Control.Window requires resizable.js to be loaded."; var resizable_handle = null; if(this.options.resizable === true){ resizable_handle = new Element('div',{ className: 'resizable_handle' }); this.container.insert(resizable_handle); }else resizable_handle = $(this.options.resziable); this.resizable = new Resizable(this.container,{ handle: resizable_handle, minHeight: this.options.minHeight, minWidth: this.options.minWidth, maxHeight: this.options.constrainToViewport ? function(element){ //viewport height - top - total border height return (document.viewport.getDimensions().height - parseInt(element.style.top || 0)) - (element.getHeight() - parseInt(element.style.height || 0)); } : this.options.maxHeight, maxWidth: this.options.constrainToViewport ? function(element){ //viewport width - left - total border width return (document.viewport.getDimensions().width - parseInt(element.style.left || 0)) - (element.getWidth() - parseInt(element.style.width || 0)); } : this.options.maxWidth }); this.resizable.handle.observe('mousedown',this.bringToFrontHandler); Resizables.addObserver(new Control.Window.LayoutUpdateObserver(this,function(){ if(this.iFrameShim) this.updateIFrameShimZIndex(); this.notify('onResize'); }.bind(this))); } }, applyDraggable: function(){ if(this.options.draggable){ if(typeof(Draggables) == "undefined") throw "Control.Window requires dragdrop.js to be loaded."; var draggable_handle = null; if(this.options.draggable === true){ draggable_handle = new Element('div',{ className: 'draggable_handle' }); this.container.insert(draggable_handle); }else draggable_handle = $(this.options.draggable); this.draggable = new Draggable(this.container,{ handle: draggable_handle, constrainToViewport: this.options.constrainToViewport, zindex: this.container.getStyle('z-index'), starteffect: function(){ if(Prototype.Browser.IE){ this.old_onselectstart = document.onselectstart; document.onselectstart = function(){ return false; }; } }.bind(this), endeffect: function(){ document.onselectstart = this.old_onselectstart; }.bind(this) }); this.draggable.handle.observe('mousedown',this.bringToFrontHandler); Draggables.addObserver(new Control.Window.LayoutUpdateObserver(this,function(){ if(this.iFrameShim) this.updateIFrameShimZIndex(); this.notify('onDrag'); }.bind(this))); } }, createDefaultContainer: function(container){ if(!this.container){ //no container passed or found, create it this.container = new Element('div',{ id: 'control_window_' + this.numberInSequence }); $(document.body).insert(this.container); if(typeof(container) == "string" && $(container) == null && !container.match(/^#(.+)$/) && !container.match(Control.Window.uriRegex)) this.container.update(container); } }, finishOpen: function(event){ this.bringToFront(); if(this.options.fade){ if(typeof(Effect) == "undefined") throw "Control.Window requires effects.js to be loaded." if(this.effects.fade) this.effects.fade.cancel(); this.effects.appear = new Effect.Appear(this.container,{ queue: { position: 'end', scope: 'Control.Window.' + this.numberInSequence }, from: 0, to: 1, duration: this.options.fadeDuration / 2, afterFinish: function(){ if(this.iFrameShim) this.updateIFrameShimZIndex(); this.isOpen = true; this.notify('afterOpen'); }.bind(this) }); }else this.container.show(); this.position(event); if(!(this.options.draggable || this.options.resizable) && this.options.position == 'center') Event.observe(window,'resize',this.positionHandler,false); if(!this.options.draggable && this.options.position == 'center') Event.observe(window,'scroll',this.positionHandler,false); if(!this.options.fade){ this.isOpen = true; this.notify('afterOpen'); } return true; }, showIndicator: function(){ this.showIndicatorTimeout = window.setTimeout(function(){ if(this.options.fade){ this.indicatorEffects.appear = new Effect.Appear(this.indicator,{ queue: { position: 'front', scope: 'Control.Window.indicator.' + this.numberInSequence }, from: 0, to: 1, duration: this.options.fadeDuration / 2 }); }else this.indicator.show(); }.bind(this),Control.Window.indicatorTimeout); }, hideIndicator: function(){ if(this.showIndicatorTimeout) window.clearTimeout(this.showIndicatorTimeout); this.indicator.hide(); }, getRemoteContentInsertionTarget: function(){ return typeof(this.options.insertRemoteContentAt) == "string" ? this.container.down(this.options.insertRemoteContentAt) : $(this.options.insertRemoteContentAt); }, updateIFrameShimZIndex: function(){ if(this.iFrameShim) this.iFrameShim.positionUnder(this.container); } }); //class methods Object.extend(Control.Window,{ windows: [], baseZIndex: 9999, indicatorTimeout: 250, iframeTemplate: new Template(''), uriRegex: /^(\/|\#|https?\:\/\/|[\w]+\/)/, bringToFront: function(w){ Control.Window.windows = Control.Window.windows.without(w); Control.Window.windows.push(w); Control.Window.windows.each(function(w,i){ var z_index = Control.Window.baseZIndex + i; w.container.setStyle({ zIndex: z_index }); if(w.isOpen){ if(w.iFrameShim) w.updateIFrameShimZIndex(); } if(w.options.draggable) w.draggable.options.zindex = z_index; }); }, open: function(container,options){ var w = new Control.Window(container,options); w.open(); return w; }, //protected initialZIndexForWindow: function(w){ return Control.Window.baseZIndex + (Control.Window.windows.length - 1); } }); Object.Event.extend(Control.Window); //this is the observer for both Resizables and Draggables Control.Window.LayoutUpdateObserver = Class.create({ initialize: function(w,observer){ this.w = w; this.element = $(w.container); this.observer = observer; }, onStart: Prototype.emptyFunction, onEnd: function(event_name,instance){ if(instance.element == this.element && this.iFrameShim) this.w.updateIFrameShimZIndex(); }, onResize: function(event_name,instance){ if(instance.element == this.element) this.observer(this.element); }, onDrag: function(event_name,instance){ if(instance.element == this.element) this.observer(this.element); } }); //overlay for Control.Modal Control.Overlay = { id: 'control_overlay', loaded: false, container: false, lastOpacity: 0, getStyles: function() { return { position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', zIndex: Control.Window.baseZIndex - 1 }; }, getIeStyles: function() { return { position: 'absolute', top: 0, left: 0, zIndex: Control.Window.baseZIndex - 1 }; }, effects: { fade: false, appear: false }, load: function(){ if(Control.Overlay.loaded) return false; Control.Overlay.loaded = true; Control.Overlay.container = new Element('div',{ id: Control.Overlay.id }); $(document.body).insert(Control.Overlay.container); if(Prototype.Browser.IE){ Control.Overlay.container.setStyle(Control.Overlay.getIeStyles()); Event.observe(window,'scroll',Control.Overlay.positionOverlay); Event.observe(window,'resize',Control.Overlay.positionOverlay); Control.Overlay.observe('beforeShow',Control.Overlay.positionOverlay); }else Control.Overlay.container.setStyle(Control.Overlay.getStyles()); Control.Overlay.iFrameShim = new IframeShim(); Control.Overlay.iFrameShim.hide(); Event.observe(window,'resize',Control.Overlay.positionIFrameShim); Control.Overlay.container.hide(); return true; }, unload: function(){ if(!Control.Overlay.loaded) return false; Event.stopObserving(window,'resize',Control.Overlay.positionOverlay); Control.Overlay.stopObserving('beforeShow',Control.Overlay.positionOverlay); Event.stopObserving(window,'resize',Control.Overlay.positionIFrameShim); Control.Overlay.iFrameShim.destroy(); Control.Overlay.container.remove(); Control.Overlay.loaded = false; return true; }, show: function(opacity,fade){ if(Control.Overlay.notify('beforeShow') === false) return false; Control.Overlay.lastOpacity = opacity; Control.Overlay.positionIFrameShim(); Control.Overlay.iFrameShim.show(); if(fade){ if(typeof(Effect) == "undefined") throw "Control.Window requires effects.js to be loaded." if(Control.Overlay.effects.fade) Control.Overlay.effects.fade.cancel(); Control.Overlay.effects.appear = new Effect.Appear(Control.Overlay.container,{ queue: { position: 'end', scope: 'Control.Overlay' }, afterFinish: function(){ Control.Overlay.notify('afterShow'); }, from: 0, to: Control.Overlay.lastOpacity, duration: (fade === true ? 0.75 : fade) / 2 }); }else{ Control.Overlay.container.setStyle({ opacity: opacity || 1 }); Control.Overlay.container.show(); Control.Overlay.notify('afterShow'); } return true; }, hide: function(fade){ if(Control.Overlay.notify('beforeHide') === false) return false; if(Control.Overlay.effects.appear) Control.Overlay.effects.appear.cancel(); Control.Overlay.iFrameShim.hide(); if(fade){ Control.Overlay.effects.fade = new Effect.Fade(Control.Overlay.container,{ queue: { position: 'front', scope: 'Control.Overlay' }, afterFinish: function(){ Control.Overlay.notify('afterHide'); }, from: Control.Overlay.lastOpacity, to: 0, duration: (fade === true ? 0.75 : fade) / 2 }); }else{ Control.Overlay.container.hide(); Control.Overlay.notify('afterHide'); } return true; }, positionIFrameShim: function(){ if(Control.Overlay.container.visible()) Control.Overlay.iFrameShim.positionUnder(Control.Overlay.container); }, //IE only positionOverlay: function(){ Control.Overlay.container.setStyle({ width: document.body.clientWidth + 'px', height: document.body.clientHeight + 'px' }); } }; Object.Event.extend(Control.Overlay); Control.ToolTip = Class.create(Control.Window,{ initialize: function($super,container,tooltip,options){ $super(tooltip,Object.extend(Object.extend(Object.clone(Control.ToolTip.defaultOptions),options || {}),{ position: 'mouse', hover: container })); } }); Object.extend(Control.ToolTip,{ defaultOptions: { offsetLeft: 10 } }); Control.Modal = Class.create(Control.Window,{ initialize: function($super,container,options){ Control.Modal.InstanceMethods.beforeInitialize.bind(this)(); $super(container,Object.extend(Object.clone(Control.Modal.defaultOptions),options || {})); }, closeWithoutOverlay: function(){ this.keepOverlay = true; this.close(); } }); Object.extend(Control.Modal,{ defaultOptions: { overlayOpacity: 0.5, closeOnClick: 'overlay' }, current: false, open: function(container,options){ var modal = new Control.Modal(container,options); modal.open(); return modal; }, close: function(){ if(Control.Modal.current) Control.Modal.current.close(); }, InstanceMethods: { beforeInitialize: function(){ Control.Overlay.load(); this.observe('beforeOpen',Control.Modal.Observers.beforeOpen.bind(this)); this.observe('afterOpen',Control.Modal.Observers.afterOpen.bind(this)); this.observe('afterClose',Control.Modal.Observers.afterClose.bind(this)); } }, Observers: { beforeOpen: function(){ Control.Window.windows.without(this).each(function(w){ if(w.closeWithoutOverlay && w.isOpen){ w.closeWithoutOverlay(); }else{ w.close(); } }); if(!Control.Overlay.overlayFinishedOpening){ Control.Overlay.observeOnce('afterShow',function(){ Control.Overlay.overlayFinishedOpening = true; this.open(); }.bind(this)); Control.Overlay.show(this.options.overlayOpacity,this.options.fade ? this.options.fadeDuration : false); throw $break; } }, afterOpen: function(){ Control.Overlay.show(this.options.overlayOpacity); Control.Overlay.overlayFinishedOpening = true; Control.Modal.current = this; }, afterClose: function(){ if(!this.keepOverlay){ Control.Overlay.hide(this.options.fade ? this.options.fadeDuration : false); Control.Overlay.overlayFinishedOpening = false; } this.keepOverlay = false; Control.Modal.current = false; } } }); Control.LightBox = Class.create(Control.Window,{ initialize: function($super,container,options){ this.allImagesLoaded = false; if(options.modal){ var options = Object.extend(Object.clone(Control.LightBox.defaultOptions),options || {}); options = Object.extend(Object.clone(Control.Modal.defaultOptions),options); options = Control.Modal.InstanceMethods.beforeInitialize.bind(this)(options); $super(container,options); }else $super(container,Object.extend(Object.clone(Control.LightBox.defaultOptions),options || {})); this.hasRemoteContent = this.href && !this.options.iframe; if(this.hasRemoteContent) this.observe('onRemoteContentLoaded',Control.LightBox.Observers.onRemoteContentLoaded.bind(this)); else this.applyImageObservers(); this.observe('beforeOpen',Control.LightBox.Observers.beforeOpen.bind(this)); }, applyImageObservers:function(){ var images = this.getImages(); this.numberImagesToLoad = images.length; this.numberofImagesLoaded = 0; images.each(function(image){ image.observe('load',function(image){ ++this.numberofImagesLoaded; if(this.numberImagesToLoad == this.numberofImagesLoaded){ this.allImagesLoaded = true; this.onAllImagesLoaded(); } }.bind(this,image)); image.hide(); }.bind(this)); }, onAllImagesLoaded: function(){ this.getImages().each(function(image){ this.showImage(image); }.bind(this)); if(this.hasRemoteContent){ if(this.options.indicator) this.hideIndicator(); this.finishOpen(); }else this.open(); }, getImages: function(){ return this.container.select(Control.LightBox.imageSelector); }, showImage: function(image){ image.show(); } }); Object.extend(Control.LightBox,{ imageSelector: 'img', defaultOptions: {}, Observers: { beforeOpen: function(){ if(!this.hasRemoteContent && !this.allImagesLoaded) throw $break; }, onRemoteContentLoaded: function(){ this.applyImageObservers(); if(!this.allImagesLoaded) throw $break; } } });