franck@1: // $Id: popups.js,v 1.9.8.19 2010/12/10 02:51:17 drewish Exp $ franck@0: franck@0: /** franck@0: * Popup Modal Dialog API franck@0: * franck@0: * Provide an API for building and displaying JavaScript, in-page, popups modal dialogs. franck@0: * Modality is provided by a fixed, semi-opaque div, positioned in front of the page contents. franck@0: * franck@0: */ franck@0: franck@0: /* franck@0: * TODO franck@0: * * Return key in add node form not working. franck@0: * * Tabledrag breaking after ahah reload. franck@0: */ franck@0: franck@0: // *************************************************************************** franck@0: // DRUPAL Namespace franck@0: // *************************************************************************** franck@0: franck@0: /** franck@0: * Attach the popups bevior to the all the requested links on the page. franck@0: * franck@0: * @param context franck@0: * The jQuery object to apply the behaviors to. franck@0: */ franck@0: franck@0: Drupal.behaviors.popups = function(context) { franck@0: Popups.saveSettings(); franck@1: franck@0: var $body = $('body'); franck@0: if(!$body.hasClass('popups-processed')) { franck@0: $body.addClass('popups-processed'); franck@0: $(document).bind('keydown', Popups.keyHandle); franck@0: var $popit = $('#popit'); franck@0: if ($popit.length) { franck@0: $popit.remove(); franck@0: Popups.message($popit.html()); franck@0: } franck@1: franck@1: // Make note of all the CSS and JS on the page so when we load a popup we franck@1: // don't try to add them a second time. franck@1: $('link[rel="stylesheet"][href]').each(function(i, v) { franck@1: Popups.originalCSS[$(this).attr('href').replace(/^(\/.+)\?\w$/, '$1')] = 1; franck@1: }); franck@1: if (Drupal.settings.popups && Drupal.settings.popups.originalCSS) { franck@1: $.extend(Popups.originalCSS, Drupal.settings.popups.originalCSS); franck@1: } franck@1: $('script[src]').each(function(i, v) { franck@1: Popups.originalJS[$(this).attr('src').replace(/^(\/.+)\?\w$/, '$1')] = 1; franck@1: }); franck@1: if (Drupal.settings.popups && Drupal.settings.popups.originalJS) { franck@1: $.extend(Popups.originalJS, Drupal.settings.popups.originalJS); franck@1: } franck@0: } franck@1: franck@0: // Add the popups-link-in-dialog behavior to links defined in Drupal.settings.popups.links array. franck@0: // Get these from current Drupal.settings, not Popups.originalSettings, as each page has it's own hooks. franck@0: if (Drupal.settings.popups && Drupal.settings.popups.links) { franck@1: jQuery.each(Drupal.settings.popups.links, function (link, options) { franck@0: Popups.attach(context, link, Popups.options(options)); franck@0: }); franck@0: } franck@1: franck@1: Popups.attach(context, '.popups', Popups.options({updateMethod: 'none'})); franck@0: Popups.attach(context, '.popups-form', Popups.options({updateMethod: 'ajax'})); // ajax reload. franck@1: Popups.attach(context, '.popups-form-reload', Popups.options({updateMethod: 'reload'})); // whole page reload. franck@0: Popups.attach(context, '.popups-form-noupdate', Popups.options({updateMethod: 'none'})); // no reload at all. franck@0: }; franck@0: franck@0: // *************************************************************************** franck@0: // Popups Namespace ********************************************************** franck@0: // *************************************************************************** franck@0: /** franck@0: * The Popups namespace contains: franck@0: * * An ordered stack of Popup objects, franck@0: * * The state of the original page, franck@0: * * Functions for managing both of the above. franck@0: */ franck@0: Popups = function(){}; franck@0: franck@0: /** franck@0: * Static variables in the Popups namespace. franck@0: */ franck@1: Popups.popupStack = []; franck@1: Popups.addedCSS = {}; franck@1: Popups.addedJS = {}; franck@1: Popups.originalCSS = {}; franck@1: Popups.originalJS = {}; franck@0: Popups.originalSettings = null; // The initial popup options of the page. franck@0: /** franck@0: * Each popup object gets it's own set of options. franck@0: * These are the defaults. franck@0: */ franck@0: Popups.defaultOptions = { franck@0: doneTest: null, // null, *path*, *regexp*. how do we know when a multiform flow is done? franck@0: updateMethod: 'ajax', // none, ajax, reload, *callback* franck@0: updateSource: 'initial', // initial, final. Only used if updateMethod != none. franck@1: onUpdate: '', // Only used if updateMethod == callback. franck@1: href: null, franck@0: width: null, // Override the width specified in the css. franck@0: targetSelectors: null, // Hash of jQuery selectors that define the content to be swapped out. franck@0: titleSelectors: null, // Array of jQuery selectors to place the new page title. franck@0: reloadOnError: false, // Force the entire page to reload if the popup href is unaccessable. franck@1: noMessage: false, // Don't show drupal_set_message messages. franck@1: skipDirtyCheck: false, // If true, this popup will not check for edits on the originating page. franck@1: hijackDestination: true // Use the destiination param to force a form submit to return to the originating page. franck@0: }; franck@0: franck@0: // *************************************************************************** franck@0: // Popups.Popup Object ******************************************************* franck@0: // *************************************************************************** franck@0: /** franck@0: * A Popup is a single modal dialog. franck@0: * The popup object encapslated all the info about a single popup. franck@0: */ franck@0: Popups.Popup = function() { franck@0: this.id = 'popups-' + Popups.nextCounter(); franck@1: franck@0: // These properties are needed if the popup contains a form that will be ajax submitted. franck@0: this.parent = null; // The popup that spawned this one. If parent is null, this popup was spawned by the original page. franck@0: this.path = null; // If popup is showing content from a url, this is that path. franck@0: this.element = null; // The DOM element that was clicked to launch this popup. franck@0: this.options = null; // An option array that control how the popup behaves. See Popups.defaultOptions for explainations. franck@0: }; franck@0: Popups.Popup.prototype.$popup = function() { franck@0: return $('#' + this.id); franck@0: }; franck@0: Popups.Popup.prototype.$popupBody = function() { franck@0: return $('#' + this.id + ' .popups-body'); franck@0: }; franck@0: Popups.Popup.prototype.$popupClose = function() { franck@0: return $('#' + this.id + ' .popups-close'); franck@0: }; franck@0: Popups.Popup.prototype.$popupTitle = function() { franck@0: return $('#' + this.id + ' .popups-title'); franck@0: }; franck@0: Popups.Popup.prototype.$popupButtons = function() { franck@0: return $('#' + this.id + ' .popups-buttons'); franck@0: }; franck@0: Popups.Popup.prototype.$popupFooter = function() { franck@0: return $('#' + this.id + ' .popups-footer'); franck@0: }; franck@0: franck@0: /** franck@0: * Create the jQuery wrapped html at the heart of the popup object. franck@1: * franck@0: * @param title franck@0: * String franck@0: * @param body franck@0: * String/HTML franck@0: * @param buttons franck@0: * Hash/Object franck@0: * @return franck@0: * The $popup. franck@0: */ franck@0: Popups.Popup.prototype.fill = function(title, body, buttons) { franck@0: return $(Drupal.theme('popupDialog', this.id, title, body, buttons)); franck@0: } franck@0: franck@0: /** franck@1: * Hide the popup by pushing it off to the side. franck@0: * Just making it display:none causes flash in FF2. franck@0: */ franck@0: Popups.Popup.prototype.hide = function() { franck@0: this.$popup().css('left', '-9999px'); franck@0: }; franck@0: franck@0: Popups.Popup.prototype.show = function() { franck@0: Popups.resizeAndCenter(this); franck@0: }; franck@0: franck@0: Popups.Popup.prototype.open = function(title, body, buttons, width){ franck@0: return Popups.open(this, title, body, buttons, width); franck@0: }; franck@0: franck@1: Popups.Popup.prototype.removePopup = function() { franck@0: Popups.removePopup(this); franck@1: }; franck@0: franck@0: /** franck@0: * Remove everything. franck@0: */ franck@0: Popups.Popup.prototype.close = function() { franck@0: return Popups.close(this); franck@0: }; franck@0: franck@0: /** franck@0: * Set the focus on the popups to the first visible, enabled form element, or the close link. franck@0: */ franck@0: Popups.Popup.prototype.refocus = function() { franck@0: // Select the first visible enabled input element. franck@0: var $popup = this.$popup(); franck@0: var $focus = $popup.find(':input:visible:enabled:first'); franck@0: if (!$focus.length) { franck@0: // There is no visible enabled input element, so select the close link. franck@0: $focus = $popup.find('.popups-close a'); franck@0: } franck@0: $focus.focus(); franck@0: }; franck@0: franck@0: /** franck@0: * Return a selector that will find target content on the layer that spawned this popup. franck@1: * This is needed for the popup to do ajax updates. franck@0: */ franck@0: Popups.Popup.prototype.targetLayerSelector = function() { franck@0: if (this.parent === null) { franck@0: return 'body'; // Select content in the original page. franck@0: } franck@0: else { franck@0: return '#' + this.parent.id; // Select content in the parent popup. franck@1: } franck@0: }; franck@0: franck@0: /** franck@0: * Determine if we are at an end point of a form flow, or just moving from one popups to another. franck@1: * franck@0: * @param path franck@0: * The path of the page that the form flow has moved to. franck@1: * This path is relative to the base_path. franck@0: * Ex: node/add/story, not http://localhost/drupal6/node/add/story or drupa6/node/add/story. franck@0: * @return bool franck@1: */ franck@0: Popups.Popup.prototype.isDone = function(path) { franck@0: var done; franck@0: if (this.options.doneTest) { franck@0: // Test if we are at the path specified by doneTest. franck@1: done = (path === this.options.doneTest || path.match(this.options.doneTest)); franck@0: } franck@1: else { franck@0: if (this.parent) { franck@0: // Test if we are back to the parent popup's path. franck@1: done = (path === this.parent.path); franck@0: } franck@0: else { franck@0: // Test if we are back to the original page's path. franck@0: done = (path === Popups.originalSettings.popups.originalPath); franck@0: } franck@1: }; franck@1: return done; franck@0: }; franck@0: franck@0: franck@0: // *************************************************************************** franck@0: // Popups Functions ********************************************************** franck@0: // *************************************************************************** franck@0: franck@0: /** franck@1: * Test if the param has been set. franck@0: * Used to distinguish between a value set to null or false and on not yet unset. franck@0: */ franck@0: Popups.isset = function(v) { franck@0: return (typeof(v) !== 'undefined'); franck@0: }; franck@0: franck@0: /** franck@0: * Get the currently active popup in the page. franck@0: * Currently it is the only one visible, but that could change. franck@0: */ franck@0: Popups.activePopup = function() { franck@0: if (Popups.popupStack.length) { franck@0: return Popups.popupStack[Popups.popupStack.length - 1]; // top of stack. franck@0: } franck@0: else { franck@0: return null; franck@0: } franck@0: }; franck@0: franck@0: /** franck@0: * Manage the page wide popupStack. franck@0: */ franck@0: Popups.push = function(popup) { franck@0: Popups.popupStack.push(popup); franck@0: }; franck@0: // Should I integrate this with popupRemove?? franck@0: Popups.pop = function(popup) { franck@0: return Popups.popupStack.pop(); franck@0: }; franck@0: franck@0: /** franck@0: * Build an options hash from defaults. franck@1: * franck@0: * @param overrides franck@0: * Hash of values to override the defaults. franck@0: */ franck@0: Popups.options = function(overrides) { franck@0: var defaults = Popups.defaultOptions; franck@1: return Popups.overrideOptions(defaults, overrides); franck@0: } franck@0: franck@0: /** franck@1: * Build an options hash. franck@0: * Also maps deprecated options to current options. franck@1: * franck@0: * @param defaults franck@0: * Hash of default values franck@0: * @param overrides franck@0: * Hash of values to override the defaults with. franck@0: */ franck@0: Popups.overrideOptions = function(defaults, overrides) { franck@0: var options = {}; franck@0: for(var option in defaults) { franck@0: var value; franck@0: if (Popups.isset(overrides[option])) { franck@0: options[option] = overrides[option]; franck@0: } franck@1: else { franck@0: options[option] = defaults[option]; franck@0: } franck@0: } franck@0: // Map deprecated options. franck@0: if (overrides['noReload'] || overrides['noUpdate']) { franck@0: options['updateMethod'] = 'none'; franck@1: } franck@0: if (overrides['reloadWhenDone']) { franck@0: options['updateMethod'] = 'reload'; franck@1: } franck@0: if (overrides['afterSubmit']) { franck@0: options['updateMethod'] = 'callback'; franck@0: options['onUpdate'] = overrides['afterSubmit']; franck@1: } franck@0: if (overrides['forceReturn']) { franck@0: options['doneTest'] = overrides['forceReturn']; franck@1: } franck@0: return options; franck@0: } franck@0: franck@0: /** franck@0: * Attach the popups behavior to all elements inside the context that match the selector. franck@0: * franck@0: * @param context franck@0: * Chunk of html to search. franck@0: * @param selector franck@0: * jQuery selector for elements to attach popups behavior to. franck@0: * @param options franck@0: * Hash of options associated with these links. franck@0: */ franck@0: Popups.attach = function(context, selector, options) { franck@0: $(selector, context).not('.popups-processed').each(function() { franck@1: var $element = $(this); franck@1: franck@1: // Mark the element as processed. franck@0: $element.addClass('popups-processed'); franck@1: franck@0: // Append note to link title. franck@0: var title = ''; franck@0: if ($element.attr('title')) { franck@0: title = $element.attr('title') + ' '; franck@0: } franck@0: title += Drupal.t('[Popup]'); franck@1: $element.attr('title', title); franck@1: franck@0: // Attach the on-click popup behavior to the element. franck@0: $element.click(function(event){ franck@0: return Popups.clickPopupElement(this, options); franck@0: }); franck@0: }); franck@1: }; franck@0: franck@0: /** franck@0: * Respond to click by opening a popup. franck@1: * franck@0: * @param element franck@0: * The element that was clicked. franck@0: * @param options franck@0: * Hash of options associated with the element. franck@0: */ franck@0: Popups.clickPopupElement = function(element, options) { franck@0: Popups.saveSettings(); franck@1: franck@0: // If the element contains a on-popups-options attribute, override default options param. franck@0: if ($(element).attr('on-popups-options')) { franck@1: var overrides = Drupal.parseJson($(element).attr('on-popups-options')); franck@0: options = Popups.overrideOptions(options, overrides); franck@0: } franck@1: franck@0: // The parent of the new popup is the currently active popup. franck@0: var parent = Popups.activePopup(); franck@1: franck@0: // If the option is distructive, check if the page is already modified, and offer to save. franck@0: var willModifyOriginal = !(options.updateMethod === 'none' || options.skipDirtyCheck); franck@0: if (willModifyOriginal && Popups.activeLayerIsEdited()) { franck@0: // The user will lose modifications, so show dialog offering to save current state. franck@0: Popups.offerToSave(element, options, parent); franck@0: } franck@0: else { franck@0: // Page is clean, or popup is safe, so just open it. franck@0: Popups.openPath(element, options, parent); franck@0: } franck@1: return false; franck@0: }; franck@0: franck@0: /** franck@0: * Test if the active layer been edited. franck@0: * Active layer is either the original page, or the active Popup. franck@0: */ franck@0: Popups.activeLayerIsEdited = function() { franck@0: var layer = Popups.activePopup(); franck@0: var $context = Popups.getLayerContext(layer); franck@1: // TODO: better test for edited page, maybe capture change event on :inputs. franck@1: var edited = $context.find('span.tabledrag-changed').length; franck@0: return edited; franck@0: } franck@0: franck@0: /** franck@0: * Show dialog offering to save form on parent layer. franck@1: * franck@0: * @param element franck@0: * The DOM element that was clicked. franck@0: * @param options franck@0: * The options associated with that element. franck@0: * @param parent franck@0: * The layer that has the unsaved edits. Null means the underlying page. franck@0: */ franck@0: Popups.offerToSave = function(element, options, parent) { franck@0: var popup = new Popups.Popup(); franck@0: var body = Drupal.t("There are unsaved changes in the form, which you will lose if you continue."); franck@0: var buttons = { franck@0: 'popup_save': {title: Drupal.t('Save Changes'), func: function(){Popups.saveFormOnLayer(element, options, parent);}}, franck@0: 'popup_submit': {title: Drupal.t('Continue'), func: function(){popup.removePopup(); Popups.openPath(element, options, parent);}}, franck@0: 'popup_cancel': {title: Drupal.t('Cancel'), func: function(){popup.close();}} franck@0: }; franck@1: popup.open(Drupal.t('Warning: Please Confirm'), body, buttons); franck@0: }; franck@0: franck@0: /** franck@0: * Generic dialog builder. franck@0: * Adds the newly built popup into the DOM. franck@1: * franck@0: * TODO: capture the focus if it tabs out of the dialog. franck@0: * franck@0: * @param popup franck@0: * Popups.Popup object to fill with content, place in the DOM, and show on the screen. franck@0: * @param String title franck@0: * String: title of new dialog. franck@0: * @param body (optional) franck@0: * String: body of new dialog. franck@0: * @param buttons (optional) franck@0: * Hash of button parameters. franck@0: * @param width (optional) franck@0: * Width of new dialog. franck@1: * franck@0: * @return popup object franck@0: */ franck@0: Popups.open = function(popup, title, body, buttons, width){ franck@0: Popups.addOverlay(); franck@1: franck@0: if (Popups.activePopup()) { franck@0: // Hiding previously active popup. franck@0: Popups.activePopup().hide(); franck@0: } franck@1: franck@0: if (!popup) { franck@0: // Popup object was not handed in, so create a new one. franck@0: popup = new Popups.Popup(); franck@0: } franck@0: Popups.push(popup); // Put this popup at the top of the stack. franck@0: franck@0: // Create the jQuery wrapped html for the new popup. franck@0: var $popup = popup.fill(title, body, buttons); franck@0: popup.hide(); // Hide the new popup until it is finished and sized. franck@0: franck@0: if (width) { franck@0: $popup.css('width', width); franck@0: } franck@1: franck@0: // Add the new popup to the DOM. franck@1: $('body').append($popup); franck@0: franck@0: // Add button function callbacks. franck@0: if (buttons) { franck@0: jQuery.each(buttons, function(id, button){ franck@0: $('#' + id).click(button.func); franck@0: }); franck@0: } franck@1: franck@0: // Add the default click-to-close behavior. franck@0: popup.$popupClose().click(function(){ franck@0: return Popups.close(popup); franck@0: }); franck@0: franck@0: Popups.resizeAndCenter(popup); franck@0: franck@0: // Focus on the first input element in the popup window. franck@1: popup.refocus(); franck@1: franck@0: // TODO - this isn't the place for this - should mirror addLoading calls. franck@0: // Remove the loading image. franck@0: Popups.removeLoading(); franck@1: franck@0: return popup; franck@1: }; franck@0: franck@0: /** franck@0: * Adjust the popup's height to fit it's content. franck@0: * Move it to be centered on the screen. franck@0: * This undoes the effects of popup.hide(). franck@1: * franck@0: * @param popup franck@0: */ franck@0: Popups.resizeAndCenter = function(popup) { franck@0: var $popup = popup.$popup(); franck@1: franck@0: // center on the screen, adding in offsets if the window has been scrolled franck@1: var popupWidth = $popup.width(); franck@0: var windowWidth = Popups.windowWidth(); franck@0: var left = (windowWidth / 2) - (popupWidth / 2) + Popups.scrollLeft(); franck@1: franck@0: // Get popups's height on the page. franck@0: $popup.css('height', 'auto'); // Reset height. franck@1: var popupHeight = $popup.height(); franck@0: $popup.height(popupHeight); franck@0: var windowHeight = Popups.windowHeight(); franck@1: franck@0: if (popupHeight > (0.9 * windowHeight) ) { // Must fit in 90% of window. franck@0: popupHeight = 0.9 * windowHeight; franck@0: $popup.height(popupHeight); franck@1: } franck@0: var top = (windowHeight / 2) - (popupHeight / 2) + Popups.scrollTop(); franck@0: franck@1: $popup.css('top', top).css('left', left); // Position the popups to be visible. franck@0: }; franck@1: franck@0: franck@0: /** franck@0: * Create and show a simple popup dialog that functions like the browser's alert box. franck@0: */ franck@0: Popups.message = function(title, message) { franck@0: message = message || ''; franck@0: var popup = new Popups.Popup(); franck@0: var buttons = { franck@0: 'popup_ok': {title: Drupal.t('OK'), func: function(){popup.close();}} franck@0: }; franck@0: popup.open(title, message, buttons); franck@0: return popup; franck@0: }; franck@0: franck@0: /** franck@0: * Handle any special keys when popups is active. franck@0: */ franck@0: Popups.keyHandle = function(e) { franck@0: if (!e) { franck@0: e = window.event; franck@0: } franck@0: switch (e.keyCode) { franck@0: case 27: // esc franck@0: Popups.close(); franck@0: break; franck@0: case 191: // '?' key, show help. franck@0: if (e.shiftKey && e.ctrlKey) { franck@0: var $help = $('a.popups.more-help'); franck@0: if ($help.size()) { franck@0: $help.click(); franck@0: } franck@0: else { franck@0: Popups.message(Drupal.t("Sorry, there is no additional help for this page")); franck@0: } franck@0: } franck@0: break; franck@0: } franck@0: }; franck@0: franck@0: /***************************************************************************** franck@0: * Appearence Functions (overlay, loading graphic, remove popups) ********* franck@0: *****************************************************************************/ franck@1: franck@0: /** franck@0: * Add full page div between the page and the dialog, to make the popup modal. franck@0: */ franck@0: Popups.addOverlay = function() { franck@0: var $overlay = $('#popups-overlay'); franck@0: if (!$overlay.length) { // Overlay does not already exist, so create it. franck@0: $overlay = $(Drupal.theme('popupOverlay')); franck@0: $overlay.css('opacity', '0.4'); // for ie6(?) franck@0: // Doing absolute positioning, so make overlay's size equal the entire body. franck@0: var $doc = $(document); franck@1: $overlay.width($doc.width()).height($doc.height()); franck@0: $overlay.click(function(){Popups.close();}); franck@0: $('body').prepend($overlay); franck@0: } franck@0: }; franck@0: franck@0: /** franck@0: * Remove overlay if popupStack is empty. franck@0: */ franck@0: Popups.removeOverlay = function() { franck@0: if (!Popups.popupStack.length) { franck@0: $('#popups-overlay').remove(); franck@0: } franck@0: }; franck@0: franck@0: /** franck@0: * Add a "Loading" message while we are waiting for the ajax response. franck@0: */ franck@0: Popups.addLoading = function() { franck@0: var $loading = $('#popups-loading'); franck@0: if (!$loading.length) { // Loading image does not already exist, so create it. franck@0: $loading = $(Drupal.theme('popupLoading')); franck@0: $('body').prepend($loading); // Loading div is initially display:none. franck@0: var width = $loading.width(); franck@0: var height = $loading.height(); franck@0: var left = (Popups.windowWidth() / 2) - (width / 2) + Popups.scrollLeft(); franck@0: var top = (Popups.windowHeight() / 2) - (height / 2) + Popups.scrollTop(); franck@0: $loading.css({'top': top, 'left': left, 'display': 'block'}); // Center it and make it visible. franck@0: } franck@0: }; franck@0: franck@0: Popups.removeLoading = function() { franck@0: $('#popups-loading').remove(); franck@0: }; franck@0: franck@0: // Should I fold this function into Popups.pop? franck@1: Popups.removePopup = function(popup) { franck@0: if (!Popups.isset(popup)) { franck@0: popup = Popups.activePopup(); franck@0: } franck@0: if (popup) { franck@0: popup.$popup().remove(); franck@1: Popups.popupStack.splice(jQuery.inArray(popup,Popups.popupStack), 1); // Remove popup from stack. Probably should rework into .pop() franck@1: } franck@1: }; franck@0: franck@0: /** franck@0: * Remove everything. franck@0: */ franck@0: Popups.close = function(popup) { franck@0: if (!Popups.isset(popup)) { franck@0: popup = Popups.activePopup(); franck@0: } franck@0: Popups.removePopup(popup); // Should this be a pop?? franck@0: Popups.removeLoading(); franck@0: if (Popups.activePopup()) { franck@0: Popups.activePopup().show(); franck@0: Popups.activePopup().refocus(); franck@0: } franck@0: else { franck@0: Popups.removeOverlay(); franck@0: Popups.restorePage(); franck@0: } franck@0: return false; franck@0: }; franck@0: franck@0: /** franck@0: * Save the page's original Drupal.settings. franck@0: */ franck@0: Popups.saveSettings = function() { franck@0: if (!Popups.originalSettings) { franck@0: Popups.originalSettings = Drupal.settings; franck@0: } franck@0: }; franck@0: franck@0: /** franck@0: * Restore the page's original Drupal.settings. franck@0: */ franck@0: Popups.restoreSettings = function() { franck@1: Drupal.settings = Popups.originalSettings; franck@0: }; franck@0: franck@0: /** franck@0: * Remove as much of the effects of jit loading as possible. franck@0: */ franck@0: Popups.restorePage = function() { franck@0: Popups.restoreSettings(); franck@0: // Remove the CSS files that were jit loaded for popup. franck@1: for (var i in Popups.addedCSS) if (Popups.addedCSS.hasOwnProperty(i)) { franck@1: $('link[href='+ Popups.addedCSS[i] + ']').remove(); franck@0: } franck@0: Popups.addedCSS = []; franck@0: }; franck@0: franck@0: franck@0: /**************************************************************************** franck@0: * Utility Functions ****************************************************** franck@0: ****************************************************************************/ franck@0: franck@0: /** franck@0: * Get the position of the left side of the browser window. franck@0: */ franck@0: Popups.scrollLeft = function() { franck@0: return Math.max(document.documentElement.scrollLeft, document.body.scrollLeft); franck@0: }; franck@0: franck@0: /** franck@0: * Get the position of the top of the browser window. franck@0: */ franck@0: Popups.scrollTop = function() { franck@0: return Math.max(document.documentElement.scrollTop, document.body.scrollTop); franck@0: }; franck@0: franck@0: /** franck@0: * Get the height of the browser window. franck@0: * Fixes jQuery & Opera bug - http://drupal.org/node/366093 franck@0: */ franck@0: Popups.windowHeight = function() { franck@1: if ($.browser.opera && $.browser.version > "9.5" && $.fn.jquery <= "1.2.6") { franck@0: return document.documentElement.clientHeight; franck@0: } franck@0: return $(window).height(); franck@0: }; franck@0: franck@0: /** franck@0: * Get the height of the browser window. franck@0: * Fixes jQuery & Opera bug - http://drupal.org/node/366093 franck@0: */ franck@0: Popups.windowWidth = function() { franck@1: if ($.browser.opera && $.browser.version > "9.5" && $.fn.jquery <= "1.2.6") { franck@0: return document.documentElement.clientWidth; franck@0: } franck@0: return $(window).width(); franck@0: }; franck@0: franck@0: Popups.nextCounter = function() { franck@0: if (this.counter === undefined) { franck@0: this.counter = 0; franck@0: } franck@0: else { franck@0: this.counter++; franck@0: } franck@0: return this.counter; franck@0: }; franck@0: franck@0: /**************************************************************************** franck@0: * Ajax Functions ****************************************************** franck@0: ****************************************************************************/ franck@0: franck@0: /** franck@0: * Add additional CSS to the page. franck@0: */ franck@0: Popups.addCSS = function(css) { franck@0: Popups.addedCSS = []; franck@1: for (var type in css) if (css.hasOwnProperty(type)) { franck@1: for (var file in css[type]) if (css[type].hasOwnProperty(file)) { franck@0: var link = css[type][file]; franck@1: var href = $(link).attr('href'); franck@0: // Does the page already contain this stylesheet? franck@1: if (!Popups.originalCSS[href.replace(/^(\/.+)\?\w$/, '$1')] && !Popups.addedCSS[href]) { franck@0: $('head').append(link); franck@1: Popups.addedCSS[href] = 1; // Keep a list, so we can remove them later. franck@0: } franck@0: } franck@0: } franck@0: }; franck@0: franck@0: /** franck@0: * Add additional Javascript to the page. franck@0: */ franck@0: Popups.addJS = function(js) { franck@0: // Parse the json info about the new context. franck@0: var scripts = []; franck@0: var inlines = []; franck@1: var src; franck@1: for (var type in js) if (js.hasOwnProperty(type)) { franck@0: if (type != 'setting') { franck@1: for (var file in js[type]) if (js[type].hasOwnProperty(file)) { franck@0: if (type == 'inline') { franck@0: inlines.push($(js[type][file]).text()); franck@0: } franck@0: else { franck@1: src = $(js[type][file]).attr('src'); franck@1: if (!Popups.originalJS[src.replace(/^(\/.+)\?\w$/, '$1')] && !Popups.addedJS[src]) { franck@1: // Get the script from the server and execute it. franck@1: $.ajax({ franck@1: type: 'GET', franck@1: url: src, franck@1: dataType: 'script', franck@1: async : false, franck@1: success: function(script) { franck@1: eval(script); franck@1: } franck@1: }); franck@1: // Mark the js as added to the underlying page. franck@1: Popups.addedJS[src] = 1; franck@1: } franck@0: } franck@0: } franck@0: } franck@0: } franck@0: franck@0: // Add new JS settings to the page, needed for #ahah properties to work. franck@0: Drupal.settings = js.setting; franck@0: franck@0: return inlines; franck@0: }; franck@0: franck@0: /** franck@0: * Execute the jit loaded inline scripts. franck@0: * Q: Do we want to re-excute the ones already in the page? franck@1: * franck@0: * @param inlines franck@0: * Array of inline scripts. franck@0: */ franck@0: Popups.addInlineJS = function(inlines) { franck@0: // Load the inlines into the page. franck@0: for (var n in inlines) { franck@0: // If the script is not already in the page, execute it. franck@1: //if (!$('script:not([src]):contains(' + inlines[n] + ')').length) { franck@0: eval(inlines[n]); franck@1: //} franck@0: } franck@0: }; franck@0: franck@0: Popups.beforeSend = function(xhr) { franck@0: xhr.setRequestHeader("X-Drupal-Render-Mode", 'json/popups'); franck@0: }; franck@0: franck@0: /** franck@0: * Do before the form in the popups is submitted. franck@0: */ franck@0: Popups.beforeSubmit = function(formData, $form, options) { franck@0: Popups.removePopup(); // Remove just the dialog, but not the overlay. franck@0: Popups.addLoading(); franck@0: }; franck@0: franck@0: franck@0: /**************************************************************************** franck@0: * Page & Form in popups functions *** franck@0: ****************************************************************************/ franck@0: franck@0: /** franck@0: * Use Ajax to open a link in a popups window. franck@0: * franck@0: * @param element franck@0: * Element that was clicked to open the popups. franck@0: * @param options franck@0: * Hash of options controlling how the popups interacts with the underlying page. franck@0: * @param parent franck@0: * If path is being opened from inside another popup, that popup is the parent. franck@0: */ franck@0: Popups.openPath = function(element, options, parent) { franck@0: Popups.saveSettings(); franck@0: franck@0: // Let the user know something is happening. franck@0: $('body').css("cursor", "wait"); franck@1: franck@0: // TODO - get nonmodal working. franck@0: if (!options.nonModal) { franck@1: Popups.addOverlay(); franck@0: } franck@0: Popups.addLoading(); franck@1: franck@0: var href = options.href ? options.href : element.href; franck@0: $(document).trigger('popups_open_path', [element, href]); // Broadcast Popup Open Path event. franck@1: franck@1: var params = {}; franck@0: // Force the popups to return back to the orignal page when forms are done, unless hijackDestination option is set to FALSE. franck@1: if (options.hijackDestination) { franck@0: var returnPath; franck@0: if (parent) { franck@0: returnPath = parent.path; franck@0: } franck@0: else { // No parent, so bring flow back to original page. franck@0: returnPath = Popups.originalSettings.popups.originalPath; franck@1: } franck@0: href = href.replace(/destination=[^;&]*[;&]?/, ''); // Strip out any existing destination param. franck@1: params.destination = returnPath; // Set the destination to return to the parent's path. franck@0: } franck@0: franck@0: var ajaxOptions = { franck@0: url: href, franck@0: dataType: 'json', franck@0: data: params, franck@0: beforeSend: Popups.beforeSend, franck@1: success: function(json) { franck@0: // Add additional CSS to the page. franck@0: Popups.addCSS(json.css); franck@0: var inlines = Popups.addJS(json.js); franck@0: var popup = Popups.openPathContent(json.path, json.title, json.messages + json.content, element, options, parent); franck@1: Popups.addInlineJS(inlines); franck@0: // Broadcast an event that the path was opened. franck@1: $(document).trigger('popups_open_path_done', [element, href, popup]); franck@0: }, franck@0: complete: function() { franck@1: $('body').css("cursor", "auto"); // Return the cursor to normal state. franck@0: } franck@0: }; franck@0: franck@0: var ajaxOptions; franck@0: if (options.reloadOnError) { franck@0: ajaxOptions.error = function() { franck@0: location.reload(); // Reload on error. Is this working? franck@1: }; franck@0: } franck@0: else { franck@0: ajaxOptions.error = function() { franck@0: Popups.message("Unable to open: " + href); franck@0: }; franck@0: } franck@0: $.ajax(ajaxOptions); franck@1: franck@1: return false; franck@0: }; franck@0: franck@0: /** franck@0: * Open path's content in an ajax popups. franck@0: * franck@0: * @param title franck@0: * String title of the popups. franck@0: * @param content franck@0: * HTML to show in the popups. franck@0: * @param element franck@1: * A DOM object containing the element that was clicked to initiate the popup. franck@0: * @param options franck@0: * Hash of options controlling how the popups interacts with the underlying page. franck@0: * @param parent franck@1: * Spawning popup, or null if spawned from original page. franck@0: */ franck@1: Popups.openPathContent = function(path, title, content, element, options, parent) { franck@0: var popup = new Popups.Popup(); franck@1: Popups.open(popup, title, content, null, options.width); franck@0: franck@1: // Set properties on new popup. franck@0: popup.parent = parent; franck@0: popup.path = path; franck@0: popup.options = options; franck@0: popup.element = element; franck@0: franck@1: // Add behaviors to content in popups. franck@0: delete Drupal.behaviors.tableHeader; // Work-around for bug in tableheader.js (http://drupal.org/node/234377) franck@0: delete Drupal.behaviors.teaser; // Work-around for bug in teaser.js (sigh). franck@0: Drupal.attachBehaviors(popup.$popupBody()); franck@0: // Adding collapse moves focus. franck@0: popup.refocus(); franck@0: franck@0: // If the popups contains a form, capture submits. franck@4: var $form = $('form:not(.no-popup)', popup.$popupBody()); franck@0: if ($form.length) { franck@1: $form.ajaxForm({ franck@1: dataType: 'json', franck@0: beforeSubmit: Popups.beforeSubmit, franck@0: beforeSend: Popups.beforeSend, franck@0: success: function(json, status) { franck@0: Popups.formSuccess(popup, json); franck@0: }, franck@0: error: function() { franck@5: Popups.message(Drupal.t("Bad Response form submission")); franck@0: } franck@0: }); franck@0: } franck@0: return popup; franck@0: }; franck@0: franck@0: /** franck@0: * The form in the popups was successfully submitted franck@0: * Update the originating page. franck@0: * Show any messages in a popups. franck@1: * franck@0: * @param popup franck@0: * The popup object that contained the form that was just submitted. franck@0: * @param data franck@0: * JSON object from server with status of form submission. franck@0: */ franck@1: Popups.formSuccess = function(popup, data) { franck@0: // Determine if we are at an end point, or just moving from one popups to another. franck@0: var done = popup.isDone(data.path); franck@0: if (!done) { // Not done yet, so show new page in new popups. franck@0: Popups.removeLoading(); franck@0: Popups.openPathContent(data.path, data.title, data.messages + data.content, popup.element, popup.options, popup.parent); franck@0: } franck@0: else { // We are done with popup flow. franck@0: // Execute the onUpdate callback if available. franck@1: if (popup.options.updateMethod === 'callback' && popup.options.onUpdate) { franck@0: var result = eval(popup.options.onUpdate +'(data, popup.options, popup.element)'); franck@0: if (result === false) { // Give onUpdate callback a chance to skip normal processing. franck@0: return; franck@0: } franck@0: } franck@0: franck@0: if (popup.options.updateMethod === 'reload') { // Force a complete, non-ajax reload of the page. franck@0: if (popup.options.updateSource === 'final') { franck@0: location.href = Drupal.settings.basePath + data.path; // TODO: Need to test this. franck@0: } franck@0: else { // Reload originating page. franck@1: location.reload(); franck@0: } franck@0: } franck@0: else { // Normal, targeted ajax, reload behavior. franck@1: var showingMessagePopup = false; franck@0: // Show messages in dialog and embed the results in the original page. franck@1: // TODO - should seperate these two functions. franck@1: // var showMessage = data.messages.length && !popup.options.noMessage; franck@6: if (data.messages && data.messages.length) { franck@1: // If we just dismissed the last popup dialog. franck@1: if (!Popups.activePopup() && !popup.options.noMessage) { franck@1: // Show drupal_set_message in message popup. franck@1: var messagePopup = Popups.message(data.messages); franck@1: if (Popups.originalSettings.popups.autoCloseFinalMessage) { franck@1: setTimeout(function(){Popups.close(messagePopup);}, 2500); // Autoclose the message box in 2.5 seconds. franck@1: } franck@1: showingMessagePopup = true; franck@0: } franck@1: franck@1: franck@1: // Insert the message into the parent layer, above the content. franck@0: // Might not be the standard spot, but it is the easiest to find. franck@0: var $next; franck@0: if (popup.targetLayerSelector() === 'body') { franck@0: $next = $('body').find(Popups.originalSettings.popups.defaultTargetSelector); franck@0: } franck@0: else { franck@0: $next = $(popup.targetLayerSelector()).find('.popups-body'); franck@0: } franck@0: $next.parent().find('div.messages').remove(); // Remove the existing messages. franck@0: $next.before(data.messages); // Insert new messages. franck@0: } franck@1: franck@0: // Update the content area (defined by 'targetSelectors'). franck@1: if (popup.options.updateMethod !== 'none') { franck@0: Popups.testContentSelector(); // Kick up warning message if selector is bad. franck@0: franck@1: Popups.restoreSettings(); // Need to restore original Drupal.settings.popups.links before running attachBehaviors. This probably has CSS side effects! franck@0: if (popup.options.targetSelectors) { // Pick and choose what returned content goes where. franck@0: jQuery.each(popup.options.targetSelectors, function(t_new, t_old) { franck@0: if(!isNaN(t_new)) { franck@0: t_new = t_old; // handle case where targetSelectors is an array, not a hash. franck@0: } franck@0: var new_content = $(t_new, data.content); franck@0: var $c = $(popup.targetLayerSelector()).find(t_old).html(new_content); // Inject the new content into the original page. franck@0: franck@0: Drupal.attachBehaviors($c); franck@0: }); franck@0: } franck@0: else { // Put the entire new content into default content area. franck@0: var $c = $(popup.targetLayerSelector()).find(Popups.originalSettings.popups.defaultTargetSelector).html(data.content); franck@1: Drupal.attachBehaviors($c); franck@0: } franck@0: } franck@1: franck@0: // Update the title of the page. franck@0: if (popup.options.titleSelectors) { franck@0: jQuery.each(popup.options.titleSelectors, function() { franck@0: $(''+this).html(data.title); franck@0: }); franck@0: } franck@1: franck@0: // Done with changes to the original page, remove effects. franck@0: Popups.removeLoading(); franck@1: if (!showingMessagePopup) { franck@1: // If there is not a messages popups, pop the stack. franck@1: // Sending in null to Popups.close reveales the next popup in the stack. franck@1: // If the stack is empty, it will remove the overlay. franck@1: Popups.close(null); franck@0: } franck@0: } franck@1: franck@0: // Broadcast an event that popup form was done and successful. franck@0: $(document).trigger('popups_form_success', [popup]); franck@1: franck@0: } // End of updating spawning layer. franck@1: }; franck@0: franck@0: franck@0: /** franck@0: * Get a jQuery object for the content of a layer. franck@0: * @param layer franck@0: * Either a popup, or null to signify the original page. franck@0: */ franck@1: Popups.getLayerContext = function(layer) { franck@0: var $context; franck@0: if (!layer) { franck@0: $context = $('body').find(Popups.originalSettings.popups.defaultTargetSelector); franck@0: } franck@0: else { franck@0: $context = layer.$popupBody(); franck@0: } franck@0: return $context; franck@0: } franck@0: franck@0: /** franck@0: * Submit the page and reload the results, before popping up the real dialog. franck@0: * franck@0: * @param element franck@0: * Element that was clicked to open a new popup. franck@0: * @param options franck@0: * Hash of options controlling how the popups interacts with the underlying page. franck@0: * @param layer franck@1: * Popup with form to save, or null if form is on original page. franck@0: */ franck@0: Popups.saveFormOnLayer = function(element, options, layer) { franck@0: var $context = Popups.getLayerContext(layer); franck@0: var $form = $context.find('form'); franck@0: var ajaxOptions = { franck@0: dataType: 'json', franck@1: beforeSubmit: Popups.beforeSubmit, franck@0: beforeSend: Popups.beforeSend, franck@1: success: function(response, status) { franck@0: // Sync up the current page contents with the submit. franck@0: var $c = $context.html(response.content); // Inject the new content into the page. franck@0: Drupal.attachBehaviors($c); franck@0: // The form has been saved, the page reloaded, now safe to show the triggering link in a popup. franck@1: Popups.openPath(element, options, layer); franck@1: } franck@0: }; franck@1: $form.ajaxSubmit(ajaxOptions); // Submit the form. franck@0: }; franck@0: franck@0: /** franck@0: * Warn the user if ajax updates will not work franck@0: * due to mismatch between the theme and the theme's popup setting. franck@0: */ franck@0: Popups.testContentSelector = function() { franck@0: var target = Popups.originalSettings.popups.defaultTargetSelector; franck@0: var hits = $(target).length; franck@0: if (hits !== 1) { // 1 is the corrent answer. franck@0: var msg = Drupal.t('The popup content area for this theme is misconfigured.') + '\n'; franck@0: if (hits === 0) { franck@0: msg += Drupal.t('There is no element that matches ') + '"' + target + '"\n'; franck@0: } franck@0: else if (hits > 1) { franck@0: msg += Drupal.t('There are multiple elements that match: ') + '"' + target + '"\n'; franck@0: } franck@1: msg += Drupal.t('Go to admin/build/themes/settings, select your theme, and edit the "Content Selector" field'); franck@0: alert(msg); franck@0: } franck@0: }; franck@0: franck@0: franck@0: // **************************************************************************** franck@0: // * Theme Functions ******************************************************** franck@0: // **************************************************************************** franck@0: franck@0: Drupal.theme.prototype.popupLoading = function() { franck@0: var loading = '
'; franck@0: loading += ''; franck@0: loading += '
'; franck@0: return loading; franck@0: }; franck@0: franck@0: Drupal.theme.prototype.popupOverlay = function() { franck@0: return '
'; franck@0: }; franck@0: franck@0: Drupal.theme.prototype.popupButton = function(title, id) { franck@0: return ''; franck@0: }; franck@0: franck@0: Drupal.theme.prototype.popupDialog = function(popupId, title, body, buttons) { franck@0: var template = Drupal.theme('popupTemplate', popupId); franck@0: var popups = template.replace('%title', title).replace('%body', body); franck@1: franck@0: var themedButtons = ''; franck@0: if (buttons) { franck@1: jQuery.each(buttons, function (id, button) { franck@0: themedButtons += Drupal.theme('popupButton', button.title, id); franck@1: }); franck@1: } franck@1: popups = popups.replace('%buttons', themedButtons); franck@0: return popups; franck@0: }; franck@0: franck@0: Drupal.theme.prototype.popupTemplate = function(popupId) { franck@0: var template; franck@0: template += '
'; franck@0: template += '
'; franck@0: template += ' '; franck@0: template += '
%title
'; franck@0: template += '
'; franck@0: template += '
'; franck@0: template += '
%body
'; franck@0: template += '
%buttons
'; franck@0: template += ' '; franck@0: template += '
'; franck@0: return template; franck@0: };