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