1// $Id: popups.js,v 1.9.8.19 2010/12/10 02:51:17 drewish Exp $ 2 3/** 4 * Popup Modal Dialog API 5 * 6 * Provide an API for building and displaying JavaScript, in-page, popups modal dialogs. 7 * Modality is provided by a fixed, semi-opaque div, positioned in front of the page contents. 8 * 9 */ 10 11/* 12 * TODO 13 * * Return key in add node form not working. 14 * * Tabledrag breaking after ahah reload. 15 */ 16 17// *************************************************************************** 18// DRUPAL Namespace 19// *************************************************************************** 20 21/** 22 * Attach the popups bevior to the all the requested links on the page. 23 * 24 * @param context 25 * The jQuery object to apply the behaviors to. 26 */ 27 28Drupal.behaviors.popups = function(context) { 29 Popups.saveSettings(); 30 31 var $body = $('body'); 32 if(!$body.hasClass('popups-processed')) { 33 $body.addClass('popups-processed'); 34 $(document).bind('keydown', Popups.keyHandle); 35 var $popit = $('#popit'); 36 if ($popit.length) { 37 $popit.remove(); 38 Popups.message($popit.html()); 39 } 40 41 // Make note of all the CSS and JS on the page so when we load a popup we 42 // don't try to add them a second time. 43 $('link[rel="stylesheet"][href]').each(function(i, v) { 44 Popups.originalCSS[$(this).attr('href').replace(/^(\/.+)\?\w$/, '$1')] = 1; 45 }); 46 if (Drupal.settings.popups && Drupal.settings.popups.originalCSS) { 47 $.extend(Popups.originalCSS, Drupal.settings.popups.originalCSS); 48 } 49 $('script[src]').each(function(i, v) { 50 Popups.originalJS[$(this).attr('src').replace(/^(\/.+)\?\w$/, '$1')] = 1; 51 }); 52 if (Drupal.settings.popups && Drupal.settings.popups.originalJS) { 53 $.extend(Popups.originalJS, Drupal.settings.popups.originalJS); 54 } 55 } 56 57 // Add the popups-link-in-dialog behavior to links defined in Drupal.settings.popups.links array. 58 // Get these from current Drupal.settings, not Popups.originalSettings, as each page has it's own hooks. 59 if (Drupal.settings.popups && Drupal.settings.popups.links) { 60 jQuery.each(Drupal.settings.popups.links, function (link, options) { 61 Popups.attach(context, link, Popups.options(options)); 62 }); 63 } 64 65 Popups.attach(context, '.popups', Popups.options({updateMethod: 'none'})); 66 Popups.attach(context, '.popups-form', Popups.options({updateMethod: 'ajax'})); // ajax reload. 67 Popups.attach(context, '.popups-form-reload', Popups.options({updateMethod: 'reload'})); // whole page reload. 68 Popups.attach(context, '.popups-form-noupdate', Popups.options({updateMethod: 'none'})); // no reload at all. 69}; 70 71// *************************************************************************** 72// Popups Namespace ********************************************************** 73// *************************************************************************** 74/** 75 * The Popups namespace contains: 76 * * An ordered stack of Popup objects, 77 * * The state of the original page, 78 * * Functions for managing both of the above. 79 */ 80Popups = function(){}; 81 82/** 83 * Static variables in the Popups namespace. 84 */ 85Popups.popupStack = []; 86Popups.addedCSS = {}; 87Popups.addedJS = {}; 88Popups.originalCSS = {}; 89Popups.originalJS = {}; 90Popups.originalSettings = null; // The initial popup options of the page. 91/** 92 * Each popup object gets it's own set of options. 93 * These are the defaults. 94 */ 95Popups.defaultOptions = { 96 doneTest: null, // null, *path*, *regexp*. how do we know when a multiform flow is done? 97 updateMethod: 'ajax', // none, ajax, reload, *callback* 98 updateSource: 'initial', // initial, final. Only used if updateMethod != none. 99 onUpdate: '', // Only used if updateMethod == callback. 100 href: null, 101 width: null, // Override the width specified in the css. 102 targetSelectors: null, // Hash of jQuery selectors that define the content to be swapped out. 103 titleSelectors: null, // Array of jQuery selectors to place the new page title. 104 reloadOnError: false, // Force the entire page to reload if the popup href is unaccessable. 105 noMessage: false, // Don't show drupal_set_message messages.
106 skipDirtyCheck: false, // If true, this popup will not check for edits on the originating page. 107 hijackDestination: true // Use the destiination param to force a form submit to return to the originating page. 108}; 109 110// *************************************************************************** 111// Popups.Popup Object ******************************************************* 112// *************************************************************************** 113/** 114 * A Popup is a single modal dialog. 115 * The popup object encapslated all the info about a single popup. 116 */ 117Popups.Popup = function() { 118 this.id = 'popups-' + Popups.nextCounter(); 119 120 // These properties are needed if the popup contains a form that will be ajax submitted. 121 this.parent = null; // The popup that spawned this one. If parent is null, this popup was spawned by the original page. 122 this.path = null; // If popup is showing content from a url, this is that path. 123 this.element = null; // The DOM element that was clicked to launch this popup. 124 this.options = null; // An option array that control how the popup behaves. See Popups.defaultOptions for explainations. 125}; 126Popups.Popup.prototype.$popup = function() { 127 return $('#' + this.id); 128}; 129Popups.Popup.prototype.$popupBody = function() { 130 return $('#' + this.id + ' .popups-body'); 131}; 132Popups.Popup.prototype.$popupClose = function() { 133 return $('#' + this.id + ' .popups-close'); 134}; 135Popups.Popup.prototype.$popupTitle = function() { 136 return $('#' + this.id + ' .popups-title'); 137}; 138Popups.Popup.prototype.$popupButtons = function() { 139 return $('#' + this.id + ' .popups-buttons'); 140}; 141Popups.Popup.prototype.$popupFooter = function() { 142 return $('#' + this.id + ' .popups-footer'); 143}; 144 145/** 146 * Create the jQuery wrapped html at the heart of the popup object. 147 * 148 * @param title 149 * String 150 * @param body 151 * String/HTML 152 * @param buttons 153 * Hash/Object 154 * @return 155 * The $popup. 156 */ 157Popups.Popup.prototype.fill = function(title, body, buttons) { 158 return $(Drupal.theme('popupDialog', this.id, title, body, buttons)); 159} 160 161/** 162 * Hide the popup by pushing it off to the side. 163 * Just making it display:none causes flash in FF2. 164 */ 165Popups.Popup.prototype.hide = function() { 166 this.$popup().css('left', '-9999px'); 167}; 168 169Popups.Popup.prototype.show = function() { 170 Popups.resizeAndCenter(this); 171}; 172 173Popups.Popup.prototype.open = function(title, body, buttons, width){ 174 return Popups.open(this, title, body, buttons, width); 175}; 176 177Popups.Popup.prototype.removePopup = function() { 178 Popups.removePopup(this); 179}; 180 181/** 182 * Remove everything. 183 */ 184Popups.Popup.prototype.close = function() { 185 return Popups.close(this); 186}; 187 188/** 189 * Set the focus on the popups to the first visible, enabled form element, or the close link. 190 */ 191Popups.Popup.prototype.refocus = function() { 192 // Select the first visible enabled input element. 193 var $popup = this.$popup(); 194 var $focus = $popup.find(':input:visible:enabled:first'); 195 if (!$focus.length) { 196 // There is no visible enabled input element, so select the close link. 197 $focus = $popup.find('.popups-close a'); 198 } 199 $focus.focus(); 200}; 201 202/** 203 * Return a selector that will find target content on the layer that spawned this popup. 204 * This is needed for the popup to do ajax updates. 205 */ 206Popups.Popup.prototype.targetLayerSelector = function() { 207 if (this.parent === null) { 208 return 'body'; // Select content in the original page. 209 } 210 else { 211 return '#' + this.parent.id; // Select content in the parent popup. 212 } 213}; 214 215/** 216 * Determine if we are at an end point of a form flow, or just moving from one popups to another. 217 * 218 * @param path 219 * The path of the page that the form flow has moved to. 220 * This path is relative to the base_path. 221 * Ex: node/add/story, not http://localhost/drupal6/node/add/story or drupa6/node/add/story. 222 * @return bool 223 */ 224Popups.Popup.prototype.isDone = function(path) { 225 var done; 226 if (this.options.doneTest) { 227 // Test if we are at the path specified by doneTest. 228 done = (path === this.options.doneTest || path.match(this.options.doneTest)); 229 } 230 else { 231 if (this.parent) { 232 // Test if we are back to the parent popup's path. 233 done = (path === this.parent.path); 234 } 235 else { 236 // Test if we are back to the original page's path. 237 done = (path === Popups.originalSettings.popups.originalPath); 238 } 239 }; 240 return done; 241}; 242 243 244// *************************************************************************** 245// Popups Functions ********************************************************** 246// *************************************************************************** 247 248/** 249 * Test if the param has been set. 250 * Used to distinguish between a value set to null or false and on not yet unset. 251 */ 252Popups.isset = function(v) { 253 return (typeof(v) !== 'undefined'); 254}; 255 256/** 257 * Get the currently active popup in the page.
258 * Currently it is the only one visible, but that could change. 259 */ 260Popups.activePopup = function() { 261 if (Popups.popupStack.length) { 262 return Popups.popupStack[Popups.popupStack.length - 1]; // top of stack. 263 } 264 else { 265 return null; 266 } 267}; 268 269/** 270 * Manage the page wide popupStack. 271 */ 272Popups.push = function(popup) { 273 Popups.popupStack.push(popup); 274}; 275// Should I integrate this with popupRemove?? 276Popups.pop = function(popup) { 277 return Popups.popupStack.pop(); 278}; 279 280/** 281 * Build an options hash from defaults. 282 * 283 * @param overrides 284 * Hash of values to override the defaults. 285 */ 286Popups.options = function(overrides) { 287 var defaults = Popups.defaultOptions; 288 return Popups.overrideOptions(defaults, overrides); 289} 290 291/** 292 * Build an options hash. 293 * Also maps deprecated options to current options. 294 * 295 * @param defaults 296 * Hash of default values 297 * @param overrides 298 * Hash of values to override the defaults with. 299 */ 300Popups.overrideOptions = function(defaults, overrides) { 301 var options = {}; 302 for(var option in defaults) { 303 var value; 304 if (Popups.isset(overrides[option])) { 305 options[option] = overrides[option]; 306 } 307 else { 308 options[option] = defaults[option]; 309 } 310 } 311 // Map deprecated options. 312 if (overrides['noReload'] || overrides['noUpdate']) { 313 options['updateMethod'] = 'none'; 314 } 315 if (overrides['reloadWhenDone']) { 316 options['updateMethod'] = 'reload'; 317 } 318 if (overrides['afterSubmit']) { 319 options['updateMethod'] = 'callback'; 320 options['onUpdate'] = overrides['afterSubmit']; 321 } 322 if (overrides['forceReturn']) { 323 options['doneTest'] = overrides['forceReturn']; 324 } 325 return options; 326} 327 328/** 329 * Attach the popups behavior to all elements inside the context that match the selector. 330 * 331 * @param context 332 * Chunk of html to search. 333 * @param selector 334 * jQuery selector for elements to attach popups behavior to. 335 * @param options 336 * Hash of options associated with these links. 337 */ 338Popups.attach = function(context, selector, options) { 339 $(selector, context).not('.popups-processed').each(function() { 340 var $element = $(this); 341 342 // Mark the element as processed. 343 $element.addClass('popups-processed'); 344 345 // Append note to link title. 346 var title = ''; 347 if ($element.attr('title')) { 348 title = $element.attr('title') + ' '; 349 } 350 title += Drupal.t(''); 351 $element.attr('title', title); 352 353 // Attach the on-click popup behavior to the element. 354 $element.click(function(event){ 355 return Popups.clickPopupElement(this, options); 356 }); 357 }); 358}; 359 360/** 361 * Respond to click by opening a popup. 362 * 363 * @param element 364 * The element that was clicked. 365 * @param options 366 * Hash of options associated with the element. 367 */ 368Popups.clickPopupElement = function(element, options) { 369 Popups.saveSettings(); 370 371 // If the element contains a on-popups-options attribute, override default options param. 372 if ($(element).attr('on-popups-options')) { 373 var overrides = Drupal.parseJson($(element).attr('on-popups-options')); 374 options = Popups.overrideOptions(options, overrides); 375 } 376 377 // The parent of the new popup is the currently active popup. 378 var parent = Popups.activePopup(); 379 380 // If the option is distructive, check if the page is already modified, and offer to save. 381 var willModifyOriginal = !(options.updateMethod === 'none' || options.skipDirtyCheck); 382 if (willModifyOriginal && Popups.activeLayerIsEdited()) { 383 // The user will lose modifications, so show dialog offering to save current state. 384 Popups.offerToSave(element, options, parent); 385 } 386 else { 387 // Page is clean, or popup is safe, so just open it. 388 Popups.openPath(element, options, parent); 389 } 390 return false; 391}; 392 393/** 394 * Test if the active layer been edited. 395 * Active layer is either the original page, or the active Popup. 396 */ 397Popups.activeLayerIsEdited = function() { 398 var layer = Popups.activePopup(); 399 var $context = Popups.getLayerContext(layer); 400 // TODO: better test for edited page, maybe capture change event on :inputs. 401 var edited = $context.find('span.tabledrag-changed').length; 402 return edited; 403} 404 405/** 406 * Show dialog offering to save form on parent layer. 407 * 408 * @param element 409 * The DOM element that was clicked. 410 * @param options 411 * The options associated with that element. 412 * @param parent 413 * The layer that has the unsaved edits. Null means the underlying page. 414 */ 415Popups.offerToSave = function(element, options, parent) { 416 var popup = new Popups.Popup(); 417 var body = Drupal.t("There are unsaved changes in the form, which you will lose if you continue."); 418 var buttons = { 419 'popup_save': {title: Drupal.t('Save Changes'), func: function(){Popups.saveFormOnLayer(element, options, parent);}}, 420 'popup_submit': {title: Drupal.t('Continue'), func: function(){popup.removePopup(); Popups.openPath(element, options, parent);}}, 421 'popup_cancel': {title: Drupal.t('Cancel'), func: function(){popup.close();}} 422 }; 423 popup.open(Drupal.t('Warning: Please Confirm'), body, buttons); 424}; 425 426/** 427 * Generic dialog builder. 428 * Adds the newly built popup into the DOM. 429 * 430 * TODO: capture the focus if it tabs out of the dialog. 431 * 432 * @param popup 433 * Popups.Popup object to fill with content, place in the DOM, and show on the screen. 434 * @param String title 435 * String: title of new dialog. 436 * @param body (optional) 437 * String: body of new dialog. 438 * @param buttons (optional) 439 * Hash of button parameters. 440 * @param width (optional) 441 * Width of new dialog. 442 * 443 * @return popup object 444 */ 445Popups.open = function(popup, title, body, buttons, width){ 446 Popups.addOverlay(); 447 448 if (Popups.activePopup()) { 449 // Hiding previously active popup. 450 Popups.activePopup().hide(); 451 } 452 453 if (!popup) { 454 // Popup object was not handed in, so create a new one. 455 popup = new Popups.Popup(); 456 } 457 Popups.push(popup); // Put this popup at the top of the stack. 458 459 // Create the jQuery wrapped html for the new popup. 460 var $popup = popup.fill(title, body, buttons); 461 popup.hide(); // Hide the new popup until it is finished and sized. 462 463 if (width) { 464 $popup.css('width', width); 465 } 466 467 // Add the new popup to the DOM. 468 $('body').append($popup); 469 470 // Add button function callbacks. 471 if (buttons) { 472 jQuery.each(buttons, function(id, button){ 473 $('#' + id).click(button.func); 474 }); 475 } 476 477 // Add the default click-to-close behavior. 478 popup.$popupClose().click(function(){ 479 return Popups.close(popup); 480 }); 481 482 Popups.resizeAndCenter(popup); 483 484 // Focus on the first input element in the popup window. 485 popup.refocus(); 486 487 // TODO - this isn't the place for this - should mirror addLoading c
487alls. 488 // Remove the loading image. 489 Popups.removeLoading(); 490 491 return popup; 492}; 493 494/** 495 * Adjust the popup's height to fit it's content. 496 * Move it to be centered on the screen. 497 * This undoes the effects of popup.hide(). 498 * 499 * @param popup 500 */ 501Popups.resizeAndCenter = function(popup) { 502 var $popup = popup.$popup(); 503 504 // center on the screen, adding in offsets if the window has been scrolled 505 var popupWidth = $popup.width(); 506 var windowWidth = Popups.windowWidth(); 507 var left = (windowWidth / 2) - (popupWidth / 2) + Popups.scrollLeft(); 508 509 // Get popups's height on the page. 510 $popup.css('height', 'auto'); // Reset height. 511 var popupHeight = $popup.height(); 512 $popup.height(popupHeight); 513 var windowHeight = Popups.windowHeight(); 514 515 if (popupHeight > (0.9 * windowHeight) ) { // Must fit in 90% of window. 516 popupHeight = 0.9 * windowHeight; 517 $popup.height(popupHeight); 518 } 519 var top = (windowHeight / 2) - (popupHeight / 2) + Popups.scrollTop(); 520 521 $popup.css('top', top).css('left', left); // Position the popups to be visible. 522}; 523 524 525/** 526 * Create and show a simple popup dialog that functions like the browser's alert box. 527 */ 528Popups.message = function(title, message) { 529 message = message || ''; 530 var popup = new Popups.Popup(); 531 var buttons = { 532 'popup_ok': {title: Drupal.t('OK'), func: function(){popup.close();}} 533 }; 534 popup.open(title, message, buttons); 535 return popup; 536}; 537 538/** 539 * Handle any special keys when popups is active. 540 */ 541Popups.keyHandle = function(e) { 542 if (!e) { 543 e = window.event; 544 } 545 switch (e.keyCode) { 546 case 27: // esc 547 Popups.close(); 548 break; 549 case 191: // '?' key, show help. 550 if (e.shiftKey && e.ctrlKey) { 551 var $help = $('a.popups.more-help'); 552 if ($help.size()) { 553 $help.click(); 554 } 555 else { 556 Popups.message(Drupal.t("Sorry, there is no additional help for this page")); 557 } 558 } 559 break; 560 } 561}; 562 563/***************************************************************************** 564 * Appearence Functions (overlay, loading graphic, remove popups) ********* 565 *****************************************************************************/ 566 567/** 568 * Add full page div between the page and the dialog, to make the popup modal. 569 */ 570Popups.addOverlay = function() {
571 var $overlay = $('#popups-overlay'); 572 if (!$overlay.length) { // Overlay does not already exist, so create it. 573 $overlay = $(Drupal.theme('popupOverlay')); 574 $overlay.css('opacity', '0.4'); // for ie6(?) 575 // Doing absolute positioning, so make overlay's size equal the entire body. 576 var $doc = $(document); 577 $overlay.width($doc.width()).height($doc.height()); 578 $overlay.click(function(){Popups.close();}); 579 $('body').prepend($overlay); 580 } 581}; 582 583/** 584 * Remove overlay if popupStack is empty. 585 */ 586Popups.removeOverlay = function() { 587 if (!Popups.popupStack.length) { 588 $('#popups-overlay').remove(); 589 } 590}; 591 592/** 593 * Add a "Loading" message while we are waiting for the ajax response. 594 */ 595Popups.addLoading = function() { 596 var $loading = $('#popups-loading'); 597 if (!$loading.length) { // Loading image does not already exist, so create it. 598 $loading = $(Drupal.theme('popupLoading')); 599 $('body').prepend($loading); // Loading div is initially display:none. 600 var width = $loading.width(); 601 var height = $loading.height(); 602 var left = (Popups.windowWidth() / 2) - (width / 2) + Popups.scrollLeft(); 603 var top = (Popups.windowHeight() / 2) - (height / 2) + Popups.scrollTop(); 604 $loading.css({'top': top, 'left': left, 'display': 'block'}); // Center it and make it visible. 605 } 606}; 607 608Popups.removeLoading = function() { 609 $('#popups-loading').remove(); 610}; 611 612// Should I fold this function into Popups.pop? 613Popups.removePopup = function(popup) { 614 if (!Popups.isset(popup)) { 615 popup = Popups.activePopup(); 616 } 617 if (popup) { 618 popup.$popup().remove(); 619 Popups.popupStack.splice(jQuery.inArray(popup,Popups.popupStack), 1); // Remove popup from stack. Probably should rework into .pop() 620 } 621}; 622 623/** 624 * Remove everything. 625 */ 626Popups.close = function(popup) { 627 if (!Popups.isset(popup)) { 628 popup = Popups.activePopup(); 629 } 630 Popups.removePopup(popup); // Should this be a pop?? 631 Popups.removeLoading(); 632 if (Popups.activePopup()) { 633 Popups.activePopup().show(); 634 Popups.activePopup().refocus(); 635 } 636 else { 637 Popups.removeOverlay(); 638 Popups.restorePage(); 639 } 640 return false; 641}; 642 643/** 644 * Save the page's original Drupal.settings. 645 */ 646Popups.saveSettings = function() { 647 if (!Popups.originalSettings) { 648 Popups.originalSettings = Drupal.settings; 649 } 650}; 651 652/** 653 * Restore the page's original Drupal.settings. 654 */ 655Popups.restoreSettings = function() { 656 Drupal.settings = Popups.originalSettings; 657}; 658 659/** 660 * Remove as much of the effects of jit loading as possible. 661 */ 662Popups.restorePage = function() { 663 Popups.restoreSettings(); 664 // Remove the CSS files that were jit loaded for popup. 665 for (var i in Popups.addedCSS) if (Popups.addedCSS.hasOwnProperty(i)) { 666 $('link[href='+ Popups.addedCSS[i] + ']').remove(); 667 } 668 Popups.addedCSS = []; 669}; 670 671 672/**************************************************************************** 673 * Utility Functions ****************************************************** 674 ****************************************************************************/ 675 676/** 677 * Get the position of the left side of the browser window. 678 */ 679Popups.scrollLeft = function() { 680 return Math.max(document.documentElement.scrollLeft, document.body.scrollLeft); 681}; 682 683/** 684 * Get the position of the top of the browser window. 685 */ 686Popups.scrollTop = function() { 687 return Math.max(document.documentElement.scrollTop, document.body.scrollTop); 688}; 689 690/** 691 * Get the height of the browser window. 692 * Fixes jQuery & Opera bug - http://drupal.org/node/366093 693 */ 694Popups.windowHeight = function() { 695 if ($.browser.opera && $.browser.version > "9.5" && $.fn.jquery <= "1.2.6") { 696 return document.documentElement.clientHeight; 697 } 698 return $(window).height(); 699}; 700 701/** 702 * Get the height of the browser window. 703 * Fixes jQuery & Opera bug - http://drupal.org/node/366093 704 */ 705Popups.windowWidth = function() { 706 if ($.browser.opera && $.browser.version > "9.5" && $.fn.jquery <= "1.2.6") { 707 return document.documentElement.clientWidth; 708 } 709 return $(window).width(); 710}; 711 712Popups.nextCounter = function() { 713 if (this.counter === undefined) { 714 this.counter = 0; 715 } 716 else { 717 this.counter++; 718 } 719 return this.counter; 720}; 721 722/**************************************************************************** 723 * Ajax Functions ****************************************************** 724 ****************************************************************************/ 725 726/** 727 * Add additional CSS to the page. 728 */ 729Popups.addCSS = function(css) { 730 Popups.addedCSS = []; 731 for (var type in css) if (css.hasOwnProperty(type)) { 732 for (var file in css[type]) if (css[type].hasOwnProperty(file)) { 733 var link = css[type][file]; 734 var href = $(link).attr('href'); 735 // Does the page already contain this stylesheet? 736 if (!Popups.originalCSS[href.replace(/^(\/.+)\?\w$/, '$1')] && !Popups.addedCSS[href]) { 737 $('head').append(link); 738 Popups.addedCSS[href] = 1; // Keep a list, so we can remove them later. 739 } 740 } 741 } 742}; 743 744/** 745 * Add additional Javascript to the page. 746 */ 747Popups.addJS = function(js) {
748 // Parse the json info about the new context. 749 var scripts = []; 750 var inlines = []; 751 var src; 752 for (var type in js) if (js.hasOwnProperty(type)) { 753 if (type != 'setting') { 754 for (var file in js[type]) if (js[type].hasOwnProperty(file)) { 755 if (type == 'inline') { 756 inlines.push($(js[type][file]).text()); 757 } 758 else { 759 src = $(js[type][file]).attr('src'); 760 if (!Popups.originalJS[src.replace(/^(\/.+)\?\w$/, '$1')] && !Popups.addedJS[src]) { 761 // Get the script from the server and execute it. 762 $.ajax({ 763 type: 'GET', 764 url: src, 765 dataType: 'script', 766 async : false, 767 success: function(script) { 768 eval(script); 769 } 770 }); 771 // Mark the js as added to the underlying page. 772 Popups.addedJS[src] = 1; 773 } 774 } 775 } 776 } 777 } 778 779 // Add new JS settings to the page, needed for #ahah properties to work. 780 Drupal.settings = js.setting; 781 782 return inlines; 783}; 784 785/** 786 * Execute the jit loaded inline scripts. 787 * Q: Do we want to re-excute the ones already in the page? 788 * 789 * @param inlines 790 * Array of inline scripts. 791 */ 792Popups.addInlineJS = function(inlines) { 793 // Load the inlines into the page. 794 for (var n in inlines) { 795 // If the script is not already in the page, execute it. 796 //if (!$('script:not([src]):contains(' + inlines[n] + ')').length) { 797 eval(inlines[n]); 798 //} 799 } 800}; 801 802Popups.beforeSend = function(xhr) { 803 xhr.setRequestHeader("X-Drupal-Render-Mode", 'json/popups'); 804}; 805 806/** 807 * Do before the form in the popups is submitted. 808 */ 809Popups.beforeSubmit = function(formData, $form, options) { 810 Popups.removePopup(); // Remove just the dialog, but not the overlay. 811 Popups.addLoading(); 812}; 813 814 815/**************************************************************************** 816 * Page & Form in popups functions *** 817 ****************************************************************************/ 818 819/** 820 * Use Ajax to open a link in a popups window. 821 * 822 * @param element 823 * Element that was clicked to open the popups. 824 * @param options 825 * Hash of options controlling how the popups interacts with the underlying page. 826 * @param parent 827 * If path is being opened from inside another popup, that popup is the parent. 828 */ 829Popups.openPath = function(element, options, parent) { 830 Popups.saveSettings(); 831 832 // Let the user know something is happening. 833 $('body').css("cursor", "wait"); 834 835 // TODO - get nonmodal working. 836 if (!options.nonModal) { 837 Popups.addOverlay(); 838 } 839 Popups.addLoading(); 840 841 var href = options.href ? options.href : element.href; 842 $(document).trigger('popups_open_path', [element, href]); // Broadcast Popup Open Path event. 843 844 var params = {}; 845 // Force the popups to return back to the orignal page when forms are done, unless hijackDestination option is set to FALSE. 846 if (options.hijackDestination) { 847 var returnPath; 848 if (parent) { 849 returnPath = parent.path; 850 } 851 else { // No parent, so bring flow back to original page. 852 returnPath = Popups.originalSettings.popups.originalPath; 853 } 854 href = href.replace(/destination=[^;&]*[;&]?/, ''); // Strip out any existing destination param. 855 params.destination = returnPath; // Set the destination to return to the parent's path. 856 } 857 858 var ajaxOptions = { 859 url: href, 860 dataType: 'json', 861 data: params, 862 beforeSend: Popups.beforeSend, 863 success: function(json) { 864 // Add additional CSS to the page. 865 Popups.addCSS(json.css); 866 var inlines = Popups.addJS(json.js); 867 var popup = Popups.openPathContent(json.path, json.title, json.messages + json.content, element, options, parent); 868 Popups.addInlineJS(inlines); 869 // Broadcast an event that the path was opened. 870 $(document).trigger('popups_open_path_done', [element, href, popup]); 871 }, 872 complete: function() { 873 $('body').css("cursor", "auto"); // Return the cursor to normal state. 874 } 875 }; 876 877 var ajaxOptions; 878 if (options.reloadOnError) { 879 ajaxOptions.error = function() { 880 location.reload(); // Reload on error. Is this working? 881 }; 882 } 883 else { 884 ajaxOptions.error = function() { 885 Popups.message("Unable to open: " + href); 886 }; 887 } 888 $.ajax(ajaxOptions); 889 890 return false;
891}; 892 893/** 894 * Open path's content in an ajax popups. 895 * 896 * @param title 897 * String title of the popups. 898 * @param content 899 * HTML to show in the popups. 900 * @param element 901 * A DOM object containing the element that was clicked to initiate the popup. 902 * @param options 903 * Hash of options controlling how the popups interacts with the underlying page. 904 * @param parent 905 * Spawning popup, or null if spawned from original page. 906 */ 907Popups.openPathContent = function(path, title, content, element, options, parent) { 908 var popup = new Popups.Popup(); 909 Popups.open(popup, title, content, null, options.width); 910 911 // Set properties on new popup. 912 popup.parent = parent; 913 popup.path = path; 914 popup.options = options; 915 popup.element = element; 916 917 // Add behaviors to content in popups. 918 delete Drupal.behaviors.tableHeader; // Work-around for bug in tableheader.js (http://drupal.org/node/234377) 919 delete Drupal.behaviors.teaser; // Work-around for bug in teaser.js (sigh). 920 Drupal.attachBehaviors(popup.$popupBody()); 921 // Adding collapse moves focus. 922 popup.refocus(); 923 924 // If the popups contains a form, capture submits. 925 var $form = $('form', popup.$popupBody()); 926 if ($form.length) { 927 $form.ajaxForm({ 928 dataType: 'json', 929 beforeSubmit: Popups.beforeSubmit, 930 beforeSend: Popups.beforeSend, 931 success: function(json, status) { 932 Popups.formSuccess(popup, json); 933 }, 934 error: function() { 935 Popups.message("Bad Response form submission"); 936 } 937 }); 938 } 939 return popup; 940}; 941 942/** 943 * The form in the popups was successfully submitted 944 * Update the originating page. 945 * Show any messages in a popups. 946 * 947 * @param popup 948 * The popup object that contained the form that was just submitted. 949 * @param data 950 * JSON object from server with status of form submission. 951 */ 952Popups.formSuccess = function(popup, data) { 953 // Determine if we are at an end point, or just moving from one popups to another. 954 var done = popup.isDone(data.path); 955 if (!done) { // Not done yet, so show new page in new popups. 956 Popups.removeLoading(); 957 Popups.openPathContent(data.path, data.title, data.messages + data.content, popup.element, popup.options, popup.parent); 958 } 959 else { // We are done with popup flow. 960 // Execute the onUpdate callback if available. 961 if (popup.options.updateMethod === 'callback' && popup.options.onUpdate) { 962 var result = eval(popup.options.onUpdate +'(data, popup.options, popup.element)'); 963 if (result === false) { // Give onUpdate callback a chance to skip normal processing. 964 return; 965 } 966 } 967 968 if (popup.options.updateMethod === 'reload') { // Force a complete, non-ajax reload of the page. 969 if (popup.options.updateSource === 'final') { 970 location.href = Drupal.settings.basePath + data.path; // TODO: Need to test this. 971 } 972 else { // Reload originating page. 973 location.reload(); 974 } 975 } 976 else { // Normal, targeted ajax, reload behavior. 977 var showingMessagePopup = false; 978 // Show messages in dialog and embed the results in the original page. 979 // TODO - should seperate these two functions. 980// var showMessage = data.messages.length && !popup.options.noMessage; 981 if (data.messages.length) { 982 // If we just dismissed the last popup dialog. 983 if (!Popups.activePopup() && !popup.options.noMessage) { 984 // Show drupal_set_message in message popup. 985 var messagePopup = Popups.message(data.messages); 986 if (Popups.originalSettings.popups.autoCloseFinalMessage) { 987 setTimeout(function(){Popups.close(messagePopup);}, 2500); // Autoclose the message box in 2.5 seconds. 988 } 989 showingMessagePopup = true; 990 } 991 992 993 // Insert the message into the parent layer, above the content. 994 // Might not be the standard spot, but it is the easiest to find. 995 var $next; 996 if (popup.targetLayerSelector() === 'body') { 997 $next = $('body').find(Popups.originalSettings.popups.defaultTargetSelector); 998 } 999 else { 1000 $next = $(popup.targetLayerSelector()).find('.popups-body'); 1001 } 1002 $next.parent().find('div.messages').remove(); // Remove the existing messages. 1003 $next.before(data.messages); // Insert new messages. 1004 } 1005 1006 // Update the content area (defined by 'targetSelectors'). 1007 if (popup.options.updateMethod !== 'none') { 1008 Popups.testContentSelector(); // Kick up warning message if selector is bad. 1009 1010 Popups.restoreSettings(); // Need to restore original Drupal.settings.popups.links before running attachBehaviors. This probably has CSS side effects! 1011 if (popup.options.targetSelectors) { // Pick and choose what returned content goes where. 1012 jQuery.each(popup.options.targetSelectors, function(t_new, t_old) { 1013 if(!isNaN(t_new)) { 1014 t_new = t_old; // handle case where targetSelectors is an array, not a hash. 1015 } 1016 var new_content = $(t_new, data.content); 1017 var $c = $(popup.targetLayerSelector()).find(t_old).html(new_content); // Inject the new content into the original page. 1018 1019 Drupal.attachBehaviors($c); 1020 }); 1021 } 1022 else { // Put the entire new content into default content area. 1023 var $c = $(popup.targetLayerSelector()).find(Popups.originalSettings.popups.defaultTargetSelector).html(data.content); 1024 Drupal.attachBehaviors($c); 1025 } 1026 } 1027 1028 // Update the title of the page. 1029 if (popup.options.titleSelectors) { 1030 jQuery.each(popup.options.titleSelectors, function() { 1031 $(''+this).html(data.title); 1032 }); 1033 } 1034 1035 // Done with changes to the original page, remove effects. 1036 Popups.removeLoading(); 1037 if (!showingMessagePopup) { 1038 // If there is not a messages popups, pop the stack. 1039 // Sending in null to Popups.close reveales the next popup in the stack. 1040 // If the stack is empty, it will remove the overlay. 1041 Popups.close(null); 1042 } 1043 } 1044 1045 // Broadcast an event that popup form was done and successful. 1046 $(document).trigger('popups_form_success', [popup]); 1047 1048 } // End of updating spawning layer. 1049}; 1050 1051 1052/** 1053 * Get a jQuery object for the content of a layer. 1054 * @param layer 1055 * Either a popup, or null to signify the original page. 1056 */ 1057Popups.getLayerContext = function(layer) { 1058 var $context; 1059 if (!layer) { 1060 $context = $('body').find(Popups.originalSettings.popups.defaultTargetSelector); 1061 } 1062 else { 1063 $context = layer.$popupBody(); 1064 } 1065 return $context; 1066} 1067 1068/** 1069 * Submit the page and reload the results, before popping up the real dialog. 1070 * 1071 * @param element 1072 * Element that was clicked to open a new popup. 1073 * @param options 1074 * Hash of options controlling how the popups interacts with the underlying page. 1075 * @param layer 1076 * Popup with form to save, or null if form is on original page. 1077 */ 1078Popups.saveFormOnLayer = function(element, options, layer) { 1079 var $context = Popups.getLayerContext(layer); 1080 var $form = $context.find('form'); 1081 var ajaxOptions = { 1082 dataType: 'json', 1083 beforeSubmit: Popups.beforeSubmit, 1084 beforeSend: Popups.beforeSend, 1085 success: function(response, status) { 1086 // Sync up the current page contents with the submit. 1087 var $c = $context.html(response.content); // Inject the new content into the page. 1088 Drupal.attachBehaviors($c); 1089 // The form has been saved, the page reloaded, now safe to show the triggering link in a popup. 1090 Popups.openPath(element, options, layer); 1091 } 1092 }; 1093 $form.ajaxSubmit(ajaxOptions); // Submit the form. 1094}; 1095 1096/** 1097 * Warn the user if ajax updates will not work 1098 * due to mismatch between the theme and the theme's popup setting. 1099 */ 1100Popups.testContentSelector = function() { 1101 var target = Popups.originalSettings.popups.defaultTargetSelector; 1102 var hits = $(target).length; 1103 if (hits !== 1) { // 1 is the corrent answer. 1104 var msg = Drupal.t('The popup content area for this theme is misconfigured.') + '\n'; 1105 if (hits === 0) { 1106 msg += Drupal.t('There is no element that matches ') + '"' + target + '"\n'; 1107 } 1108 else if (hits > 1) { 1109 msg += Drupal.t('There are multiple elements that match: ') + '"' + target + '"\n'; 1110 }
1111 msg += Drupal.t('Go to admin/build/themes/settings, select your theme, and edit the "Content Selector" field'); 1112 alert(msg); 1113 } 1114}; 1115 1116 1117// **************************************************************************** 1118// * Theme Functions ******************************************************** 1119// **************************************************************************** 1120 1121Drupal.theme.prototype.popupLoading = function() { 1122 var loading = '<div id="popups-loading">'; 1123 loading += '<img src="'+ Drupal.settings.basePath + Popups.originalSettings.popups.modulePath + '/ajax-loader.gif" />'; 1124 loading += '</div>'; 1125 return loading; 1126}; 1127 1128Drupal.theme.prototype.popupOverlay = function() { 1129 return '<div id="popups-overlay"></div>'; 1130}; 1131 1132Drupal.theme.prototype.popupButton = function(title, id) { 1133 return '<input type="button" value="'+ title +'" id="'+ id +'" />'; 1134}; 1135 1136Drupal.theme.prototype.popupDialog = function(popupId, title, body, buttons) { 1137 var template = Drupal.theme('popupTemplate', popupId); 1138 var popups = template.replace('%title', title).replace('%body', body); 1139 1140 var themedButtons = ''; 1141 if (buttons) { 1142 jQuery.each(buttons, function (id, button) { 1143 themedButtons += Drupal.theme('popupButton', button.title, id); 1144 }); 1145 } 1146 popups = popups.replace('%buttons', themedButtons); 1147 return popups; 1148}; 1149 1150Drupal.theme.prototype.popupTemplate = function(popupId) { 1151 var template; 1152 template += '<div id="'+ popupId + '" class="popups-box">'; 1153 template += ' <div class="popups-title">'; 1154 template += ' <div class="popups-close"><a href="#">' + Drupal.t('Close') + '</a></div>'; 1155 template += ' <div class="title">%title</div>'; 1156 template += ' <div class="clear-block"></div>'; 1157 template += ' </div>'; 1158 template += ' <div class="popups-body" >%body</div>'; 1159 template += ' <div class="popups-buttons">%buttons</div>'; 1160 template += ' <div class="popups-footer"></div>'; 1161 template += '</div>'; 1162 return template; 1163};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.