/** * Purpose: A JavaScript library for displaying a div when the mouse moves over an HTML element * * Libraries: Prototype and Script.aculo.us * * Copyright: Up and Running * Date: 5/1/2008 * Author: Pete Hanson * * * To attach a popup dialog to a trigger element, do something like this: * Event.observe(window,"onload",function(){ * new Popup('trigger1','display1',{'option1':'value1','option2':'value2'}); * new Popup('trigger2','display2',{'option1':'value1','option2':'value2'}); * } */ var Popup = Class.create(); Popup.prototype = { element: null, // The element to display when this popup is triggered targetElement: null, // The element that triggers this popup display timer: null, // A timer used to time how long the mouse cursor has been off of the popup isVisible: true, // Keep track of whether the popup dialog is visible or not fxQueueName: null, // A unique name for this popup to keep track of which effects are active /** * Sets up a new popup dialog. Popup dialogs are created in the closed (hidden) state * Popup dialogs should be created immediately after the page first loads, they should not be created * in events linked to HTML elements. * * @param actionElem The HTML element or id of the HTML element that will trigger this popup to display * when the user moves their mouse over it. * @param dialogElem Controls what is shown when this popup is triggered. If the ajax option is not set or * is set to false this can either be an HTML element, an HTML element id or a string. If it is an HTML * element or the id of an HTML element that HTML element will be shown. If it is a string, that string * will be shown. If the ajax option is set to true then this can either be a string containing the URL * of the ajax request or a callback function which should return the URL of the request. The request will * be executed (and the callback called if using a callback) each time the popup is redisplayed. * @param options Sets a number of available options in the format {'option1': 'value1', 'option2': 'value2'} * Available Options: * ajax Set to true to use an Ajax request to obtain the text of the popup (default: false) * cssClass The CSS class to use for the popup display (if not using an existing HTML element) (default: protopopup) * This class should contain display: none; and/or visible: hidden; to prevent the popup from * flashing when the page loads. * closeAfter The number of milliseconds to close the window after the mouse leaves if closeOnLeave is enabled * (Default: 500) * closeButtonURL If showing a close button, this can be set to the URL of the image to use for the button * closeOnLeave Set to true if the popup should close when the mouse leaves it (default: false) * effectDuration The amount of time the script should take to show the effect. (default .5) * effectHide The effect to use when hiding the popup. Should be an effect from scriptaculous. * default: {'effectShow': Effect.BlindUp} * effectShow The effect to use when showing the popup. Should be an effect from scriptaculous. * default: {'effectShow': Effect.BlindDown} * position May be "above" or "below", controls where the popup is shown relative to actionElem (default: below) * alignment Control whether the popup is displayed to the left of the button, right of the button or * to the center of the button. Valid values: left, right, center (default 'center') * isFixed Set to true if the trigger element is fixed in the page layout (default: false) * showCloseButton Set to true to show a Close button in the popup (default: false) */ initialize: function(actionElem, dialogElem, options) { this.targetElement = $(actionElem); this.fxQueueName = this.targetElement.id; // Stores options for this popup this.options = {}; // Check for passed options, assign default value if not passed this.options.ajax = options.ajax; this.options.positionAbove = (options.position && options.position == "above"); this.options.positionRight = false; this.options.showCloseButton = options.showCloseButton; this.options.closeButtonURL = options.closeButtonURL ? options.closeButtonURL : "share/icons/close.gif" this.options.effectShow = options.effectShow ? options.effectShow : Effect.BlindDown; this.options.effectHide = options.effectHide ? options.effectHide : Effect.BlindUp; this.options.effectDuration = options.effectDuration ? options.effectDuration : .5; this.options.showAfter = options.showAfter ? options.showAfter : 0; this.options.closeAfter = options.closeAfter ? options.closeAfter : 500; this.options.cssClass = options.cssClass ? options.cssClass : 'protopopup'; this.options.closeOnLeave = options.closeOnLeave; this.options.isFixed = options.isFixed; if(options.alignment == 'left' || options.alignment == 'right') this.options.alignment = options.alignment; else this.options.alignment = 'center'; // If not using ajax to get the contents see if this points to an already existing element if(!this.options.ajax) this.element = $(dialogElem); else this.options.ajaxURL = dialogElem; // If not using an element already on the page, then create it if(!this.element){ // Add the popup layer to the page this.element = $(document.createElement("div")); this.element.className = this.options.cssClass; this.element.style.display = 'none'; document.body.appendChild(this.element); // Load initial contents if using static text if(!options.ajax) this.renderPopupWithText(dialogElem); }else if(this.options.showCloseButton){ // Add a close button to an existing element if requested this.addCloseButton(this.options.closeButtonURL); } // Make sure this element is hidden, it should be hidden using the pages CSS otherwise it will flash // on the screen before loading. this.element.style.visibility = 'hidden'; this.element.style.display = 'none'; this.isVisible = false; // Set the position of this popup this.setPosition(); // Listener functions this.eventTargetMouseOver = this.setShowTimer.bindAsEventListener(this); this.eventTargetMouseOut = this.setHideTimer.bindAsEventListener(this); this.eventMouseOver = this.clearTimer.bindAsEventListener(this); this.eventMouseOut = this.setHideTimer.bindAsEventListener(this); this.eventClickOnBody = this.parseEvents.bindAsEventListener(this); this.eventToggle = this.toggle.bindAsEventListener(this); // Attack the listeners this.registerEvents(); }, /** * Handle an Ajax request containing popup contents * Use this as a callback for Ajax.Request */ handleAjaxRequest: function(request) { this.renderPopupWithText(request.responseText); }, /** * Handle an Ajax error * Use this as a callback for Ajax.Request */ handleAjaxError: function(request) { this.renderPopupWithText("An error occurred while loading the popup text"); }, /** * Update the text shown in a popup created by this class (ie: not an existing HTML element) * * @param text The text/html to show in the popup */ renderPopupWithText: function(text) { this.element.update(text); // Add a close button if necessary if(this.options.showCloseButton) this.addCloseButton(this.options.closeButtonURL); }, /** * Add a close button to the popup. The close button will close this popup. * * @param The URL to use for the button image */ addCloseButton: function(buttonImage) { var closeButton = $(document.createElement("img")); closeButton.src = buttonImage; closeButton.alt = 'Close'; closeButton.align = 'right'; closeButton.style.margin = '3px;' Event.observe(closeButton,"click",this.hideElement.bindAsEventListener(this)); //this.element.appendChild(closeButton); this.element.insertBefore(closeButton,this.element.firstChild); }, toggle: function(event){ var animationInProgress = (Effect.Queues.get(this.fxQueueName).size() != 0); if(!animationInProgress){ if(!this.isVisible) this.showElement(event); else this.hideElement(); } }, /** * Shows a hidden popup */ showElement: function(event) { // Make sure that the popup is actually hidden and that an animation isn't already playing for it var animationInProgress = (Effect.Queues.get(this.fxQueueName).size() != 0); if(!animationInProgress && !this.isVisible){ // Trigger show effect, this effect is reponsible for making it visible this.options.effectShow(this.element,{'duration': this.options.effectDuration, 'queue': {position: 'end', scope: this.fxQueueName, limit: 1}}); this.setPosition(); this.isVisible = true; // If using ajax to fill this popup trigger a request if(this.options.ajax){ this.renderPopupWithText("Loading..."); var url = typeof(this.options.ajaxURL) == 'string' ? this.options.ajaxURL : this.options.ajaxURL(); new Ajax.Request(url,{onSuccess:this.handleAjaxRequest.bind(this),onFailure:this.handleAjaxError.bind(this)}); } } }, /** * Starts a countdown timer that will show the popup when it reaches zero */ setShowTimer: function() { this.clearTimer(); this.timer = setTimeout(this.showElement.bindAsEventListener(this), this.options.showAfter); }, /** * Starts a countdown timer that will close the popup when it reaches zero */ setHideTimer: function() { this.clearTimer(); this.timer = setTimeout(this.hideElement.bindAsEventListener(this), this.options.closeAfter); }, /** * Stops the countdown timer that closes the popup * */ clearTimer: function() { clearTimeout(this.timer); }, /** * When the user clicks anywhere on the page this handles it to see whether the popup should be closed */ parseEvents: function(event) { var element = Event.element(event); if(event.type == "click" && element != this.element && (!element.descendantOf || !element.descendantOf(this.element))){ this.hideElement(); } }, /** * Hide a visible popup */ hideElement: function() { // Clear the timer in case its running this.clearTimer(); // Make sure the window is currently visible and an animation is not being run var animationInProgress = (Effect.Queues.get(this.fxQueueName).size() != 0); if(!animationInProgress && this.isVisible){ this.options.effectHide(this.element,{'duration': this.options.effectDuration, 'queue': {position: 'end', scope: this.fxQueueName, limit: 1}}); this.isVisible = false; } }, /** * Set the position of the popup window relative to the target */ setPosition: function() { var left, top; var pos = util_getElementPosition(this.targetElement.id); this.element.style.position = 'absolute'; this.element.style.visibility = 'hidden'; this.element.style.display = 'block'; if(this.options.alignment == 'center') left = pos.left - parseInt(this.element.offsetWidth/2) + parseInt(this.targetElement.offsetWidth/2); else if(this.options.alignment == 'right') left = pos.left; else if(this.options.alignment == 'left') left = pos.left - this.element.offsetWidth + this.targetElement.offsetWidth; if(this.options.isFixed) scrollOffset = document.body.parentElement ? document.body.parentElement.scrollTop : window.pageYOffset; else scrollOffset = 0; if(this.options.positionAbove){ top = pos.top - this.element.offsetHeight + scrollOffset; }else{ top = pos.top + this.targetElement.offsetHeight + scrollOffset; } this.element.style.left = left + "px"; this.element.style.top = top + "px"; this.element.style.display = 'none'; this.element.style.visibility = 'visible'; }, /** * Attach event listeners to the different objects */ registerEvents: function(){ Event.observe(this.targetElement, "click", this.eventToggle); Event.observe(this.targetElement, "mouseover", this.eventTargetMouseOver); Event.observe(this.element, "mouseover", this.eventMouseOver); Event.observe(document, "click", this.eventClickOnBody); // These events are only triggered if the window should close when the mouse leaves if(this.options.closeOnLeave){ Event.observe(this.element, "mouseout", this.eventMouseOut); Event.observe(this.targetElement, "mouseout", this.eventTargetMouseOut); } } }