5a920362 |
1 | /***************** |
2 | |
3 | jQuery Tools overlay helpers. |
4 | |
5 | Copyright © 2010, The Plone Foundation |
6 | Licensed under the GPL, see LICENSE.txt for details. |
7 | |
8 | *****************/ |
9 | |
10 | /*jslint browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, newcap: true, immed: true, regexp: false, white:true */ |
11 | /*global jQuery, ajax_noresponse_message, window */ |
12 | |
13 | // Name space object for pipbox |
14 | var pb = {spinner: {}, overlay_counter: 1}; |
15 | |
16 | jQuery.tools.overlay.conf.oneInstance = false; |
17 | |
18 | jQuery(function ($) { |
19 | |
20 | pb.spinner.show = function () { |
21 | $('body').css('cursor', 'wait'); |
22 | }; |
23 | pb.spinner.hide = function () { |
24 | $('body').css('cursor', ''); |
25 | }; |
26 | |
27 | /****** |
28 | $.fn.prepOverlay jQuery plugin to inject overlay target into DOM and |
29 | annotate it with the data we'll need in order to display it. |
30 | ******/ |
31 | $.fn.prepOverlay = function (pba) { |
32 | return this.each(function () { |
33 | var o, pbo, config, onBeforeLoad, onLoad, src, parts; |
34 | |
35 | o = $(this); |
36 | |
37 | // copy options so that it's not just a reference |
38 | // to the parameter. |
39 | pbo = $.extend(true, {}, pba); |
40 | |
41 | // set overlay configuration |
42 | config = pbo.config || {}; |
43 | |
44 | // set onBeforeLoad handler |
45 | onBeforeLoad = pb[pbo.subtype]; |
46 | if (onBeforeLoad) { |
47 | config.onBeforeLoad = onBeforeLoad; |
48 | } |
49 | onLoad = config.onLoad; |
50 | config.onLoad = function () { |
51 | if (onLoad) { |
52 | onLoad.apply(this, arguments); |
53 | } |
54 | pb.fi_focus(this.getOverlay()); |
55 | }; |
56 | |
57 | // be promiscuous, pick up the url from |
58 | // href, src or action attributes |
59 | src = o.attr('href') || o.attr('src') || o.attr('action'); |
60 | |
61 | // translate url with config specifications |
62 | if (pbo.urlmatch) { |
63 | src = src.replace(new RegExp(pbo.urlmatch), pbo.urlreplace); |
64 | } |
65 | |
66 | if (pbo.subtype === 'inline') { |
67 | // we're going to let tools' overlay do all the real |
68 | // work. Just get the markers in place. |
69 | src = src.replace(/^.+#/, '#'); |
70 | $("[id='" + src.replace('#', '') + "']") |
71 | .addClass('overlay'); |
72 | o.removeAttr('href').attr('rel', src); |
73 | // use overlay on the source (clickable) element |
74 | o.overlay(); |
75 | } else { |
76 | // save various bits of information from the pbo options, |
77 | // and enable the overlay. |
78 | |
79 | // this is not inline, so in one fashion or another |
80 | // we'll be loading it via the beforeLoad callback. |
81 | // create a unique id for a target element |
82 | pbo.nt = 'pb_' + pb.overlay_counter; |
83 | pb.overlay_counter += 1; |
84 | |
85 | pbo.selector = pbo.filter || pbo.selector; |
86 | if (!pbo.selector) { |
87 | // see if one's been supplied in the src |
88 | parts = src.split(' '); |
89 | src = parts.shift(); |
90 | pbo.selector = parts.join(' '); |
91 | } |
92 | |
93 | pbo.src = src; |
94 | pbo.config = config; |
95 | |
96 | // remove any existing overlay and overlay handler |
97 | pb.remove_overlay(o); |
98 | |
99 | // save options on trigger element |
100 | o.data('pbo', pbo); |
101 | |
102 | // mark the source with a rel attribute so we can find |
103 | // the overlay, and a special class for styling |
104 | o.attr('rel', '#' + pbo.nt); |
105 | o.addClass('link-overlay'); |
106 | |
107 | // for some subtypes, we're setting click handlers |
108 | // and attaching overlay to the target element. That's |
109 | // so we'll know the dimensions early. |
110 | // Others, like iframe, just use overlay. |
111 | switch (pbo.subtype) { |
112 | case 'image': |
113 | o.click(pb.image_click); |
114 | break; |
115 | case 'ajax': |
116 | o.click(pb.ajax_click); |
117 | break; |
118 | case 'iframe': |
119 | pb.create_content_div(pbo); |
120 | o.overlay(config); |
121 | break; |
122 | default: |
123 | throw "Unsupported overlay type"; |
124 | } |
125 | |
126 | // in case the click source wasn't |
127 | // already a link. |
128 | o.css('cursor', 'pointer'); |
129 | } |
130 | }); |
131 | }; |
132 | |
133 | |
134 | /****** |
135 | pb.remove_overlay |
136 | Remove the overlay and handler associated with a jquery wrapped |
137 | trigger object |
138 | ******/ |
139 | pb.remove_overlay = function (o) { |
140 | var old_data = o.data('pbo'); |
141 | if (old_data) { |
142 | switch (old_data.subtype) { |
143 | case 'image': |
144 | o.unbind('click', pb.image_click); |
145 | break; |
146 | case 'ajax': |
147 | o.unbind('click', pb.ajax_click); |
148 | break; |
149 | default: |
150 | // it's probably the jqt overlay click handler, |
151 | // but we don't know the handler and are forced |
152 | // to do a generic unbind of click handlers. |
153 | o.unbind('click'); |
154 | } |
155 | if (old_data.nt) { |
156 | $('#' + old_data.nt).remove(); |
157 | } |
158 | } |
159 | }; |
160 | |
161 | |
162 | /****** |
163 | pb.create_content_div |
164 | create a div to act as an overlay; append it to |
165 | the body; return it |
166 | ******/ |
167 | pb.create_content_div = function (pbo) { |
168 | var content; |
169 | |
170 | content = $( |
171 | '<div id="' + pbo.nt + |
172 | '" class="overlay overlay-' + pbo.subtype + |
173 | ' ' + (pbo.cssclass || '') + |
174 | '"><div class="close"><span>Close</span></div></div>' |
175 | ); |
176 | |
177 | content.data('pbo', pbo); |
178 | |
179 | // if we've a width specified, set it on the overlay div |
180 | if (pbo.width) { |
181 | content.width(pbo.width); |
182 | } |
183 | |
184 | // add the target element at the end of the body. |
185 | content.appendTo($("body")); |
186 | |
187 | return content; |
188 | }; |
189 | |
190 | |
191 | /****** |
192 | pb.image_click |
193 | click handler for ajax sources. |
194 | ******/ |
195 | pb.image_click = function (event) { |
196 | var ethis, content, api, img, el, pbo; |
197 | |
198 | ethis = $(this); |
199 | pbo = ethis.data('pbo'); |
200 | |
201 | // find target container |
202 | content = $(ethis.attr('rel')); |
203 | if (!content.length) { |
204 | content = pb.create_content_div(pbo); |
205 | content.overlay(pbo.config); |
206 | } |
207 | api = content.overlay(); |
208 | |
209 | // is the image loaded yet? |
210 | if (content.find('img').length === 0) { |
211 | // load the image. |
212 | if (pbo.src) { |
213 | pb.spinner.show(); |
214 | |
215 | // create the image and stuff it |
216 | // into our target |
217 | img = new Image(); |
218 | img.src = pbo.src; |
219 | el = $(img); |
220 | content.append(el.addClass('pb-image')); |
221 | |
222 | // Now, we'll cause the overlay to |
223 | // load when the image is loaded. |
224 | el.load(function () { |
225 | pb.spinner.hide(); |
226 | api.load(); |
227 | }); |
228 | |
229 | } |
230 | } else { |
231 | api.load(); |
232 | } |
233 | |
234 | return false; |
235 | }; |
236 | |
237 | |
238 | /****** |
239 | pb.fi_focus |
240 | First-input focus inside $ selection. |
241 | ******/ |
242 | pb.fi_focus = function (jqo) { |
243 | if (! jqo.find("form div.error :input:first").focus().length) { |
244 | jqo.find("form :input:visible:first").focus(); |
245 | } |
246 | }; |
247 | |
248 | |
249 | /****** |
250 | pb.ajax_error_recover |
251 | jQuery's ajax load function does not load error responses. |
252 | This routine returns the cooked error response. |
253 | ******/ |
254 | pb.ajax_error_recover = function (responseText, selector) { |
255 | var tcontent = $('<div/>') |
256 | .append(responseText.replace(/<script(.|\s)*?\/script>/gi, "")); |
257 | return selector ? tcontent.find(selector) : tcontent; |
258 | }; |
259 | |
260 | |
261 | /****** |
262 | pb.add_ajax_load |
263 | Adds a hidden ajax_load input to form |
264 | ******/ |
265 | pb.add_ajax_load = function (form) { |
266 | if (form.find('input[name=ajax_load]').length === 0) { |
267 | form.prepend($('<input type="hidden" name="ajax_load" value="' + |
268 | (new Date().getTime()) + |
269 | '" />')); |
270 | } |
271 | }; |
272 | |
273 | /****** |
274 | pb.prep_ajax_form |
275 | Set up form with ajaxForm, including success and error handlers. |
276 | ******/ |
277 | pb.prep_ajax_form = function (form) { |
278 | var ajax_parent = form.closest('.pb-ajax'), |
279 | data_parent = ajax_parent.closest('.overlay-ajax'), |
280 | pbo = data_parent.data('pbo'), |
281 | formtarget = pbo.formselector, |
282 | closeselector = pbo.closeselector, |
283 | beforepost = pbo.beforepost, |
284 | afterpost = pbo.afterpost, |
285 | noform = pbo.noform, |
286 | api = data_parent.overlay(), |
287 | selector = pbo.selector, |
288 | options = {}; |
289 | |
290 | options.beforeSerialize = function () { |
291 | pb.spinner.show(); |
292 | }; |
293 | |
294 | if (beforepost) { |
295 | options.beforeSubmit = function (arr, form, options) { |
296 | return beforepost(form, arr, options); |
297 | }; |
298 | } |
299 | options.success = function (responseText, statusText, xhr, form) { |
300 | $(document).trigger('formOverlayStart', [this, responseText, statusText, xhr, form]); |
301 | // success comes in many forms, some of which are errors; |
302 | // |
303 | |
304 | var el, myform, success, target; |
305 | |
306 | success = statusText === "success" || statusText === "notmodified"; |
307 | |
308 | if (! success) { |
309 | // The responseText parameter is actually xhr |
310 | responseText = responseText.responseText; |
311 | } |
312 | // strip inline script tags |
313 | responseText = responseText.replace(/<script(.|\s)*?\/script>/gi, ""); |
314 | |
315 | // create a div containing the optionally filtered response |
316 | el = $('<div />').append( |
317 | selector ? |
318 | // a lesson learned from the jQuery source: $(responseText) |
319 | // will not work well unless responseText is well-formed; |
320 | // appending to a div is more robust, and automagically |
321 | // removes the html/head/body outer tagging. |
322 | $('<div />').append(responseText).find(selector) |
323 | : |
324 | responseText |
325 | ); |
326 | |
327 | // afterpost callback |
328 | if (success && afterpost) { |
329 | afterpost(el, data_parent); |
330 | } |
331 | |
332 | myform = el.find(formtarget); |
333 | if (success && myform.length) { |
334 | ajax_parent.empty().append(el); |
335 | pb.fi_focus(ajax_parent); |
336 | |
337 | pb.add_ajax_load(myform); |
338 | // attach submit handler with the same options |
339 | myform.ajaxForm(options); |
340 | |
341 | // attach close to element id'd by closeselector |
342 | if (closeselector) { |
343 | el.find(closeselector).click(function (event) { |
344 | api.close(); |
345 | return false; |
346 | }); |
347 | } |
348 | $(document).trigger('formOverlayLoadSuccess', [this, myform, api, pb, ajax_parent]); |
349 | } else { |
350 | // there's no form in our new content or there's been an error |
351 | if (success) { |
352 | if (typeof(noform) === "function") { |
353 | // get action from callback |
354 | noform = noform(this); |
355 | } |
356 | } else { |
357 | noform = statusText; |
358 | } |
359 | |
360 | |
361 | switch (noform) { |
362 | case 'close': |
363 | api.close(); |
364 | break; |
365 | case 'reload': |
366 | api.close(); |
367 | pb.spinner.show(); |
368 | // location.reload results in a repost |
369 | // dialog in some browsers; very unlikely to |
370 | // be what we want. |
371 | location.replace(location.href); |
372 | break; |
373 | case 'redirect': |
374 | api.close(); |
375 | pb.spinner.show(); |
376 | target = pbo.redirect; |
377 | if (typeof(target) === "function") { |
378 | // get target from callback |
379 | target = target(this, responseText); |
380 | } |
381 | location.replace(target); |
382 | break; |
383 | default: |
384 | if (el.children()) { |
385 | // show what we've got |
386 | ajax_parent.empty().append(el); |
387 | } else { |
388 | api.close(); |
389 | } |
390 | } |
391 | $(document).trigger('formOverlayLoadFailure', [this, myform, api, pb, ajax_parent, noform]); |
392 | } |
393 | pb.spinner.hide(); |
394 | }; |
395 | // error and success callbacks are the same |
396 | options.error = options.success; |
397 | |
398 | pb.add_ajax_load(form); |
399 | form.ajaxForm(options); |
400 | }; |
401 | |
402 | |
403 | /****** |
404 | pb.ajax_click |
405 | Click handler for ajax sources. The job of this routine |
406 | is to do the ajax load of the overlay element, then |
407 | call the JQT overlay loader. |
408 | ******/ |
409 | pb.ajax_click = function (event) { |
410 | var ethis = $(this), |
411 | pbo, |
412 | content, |
413 | api, |
414 | src, |
415 | el, |
416 | selector, |
417 | formtarget, |
418 | closeselector, |
419 | sep; |
420 | |
421 | e = $.Event(); |
422 | e.type = "beforeAjaxClickHandled"; |
423 | $(document).trigger(e, [this, event]); |
424 | if (e.isDefaultPrevented()) { return; } |
425 | |
426 | pbo = ethis.data('pbo'); |
427 | |
428 | content = pb.create_content_div(pbo); |
429 | content.overlay(pbo.config); |
430 | api = content.overlay(); |
431 | src = pbo.src; |
432 | selector = pbo.selector; |
433 | formtarget = pbo.formselector; |
434 | closeselector = pbo.closeselector; |
435 | |
436 | pb.spinner.show(); |
437 | |
438 | // prevent double click warning for this form |
439 | $(this).find("input.submitting").removeClass('submitting'); |
440 | |
441 | el = $('<div class="pb-ajax" />'); |
442 | if (api.getConf().fixed) { |
443 | // don't let it be over 75% of the viewport's height |
444 | el.css('max-height', Math.floor($(window).height() * 0.75)); |
445 | } |
446 | content.append(el); |
447 | |
448 | // affix a random query argument to prevent |
449 | // loading from browser cache |
450 | sep = (src.indexOf('?') >= 0) ? '&': '?'; |
451 | src += sep + "ajax_load=" + (new Date().getTime()); |
452 | |
453 | // add selector, if any |
454 | if (selector) { |
455 | src += ' ' + selector; |
456 | } |
457 | |
458 | // set up callback to be used whenever new contents are loaded |
459 | // into the overlay, to prepare links and forms to stay within |
460 | // the overlay |
461 | el[0].handle_load_inside_overlay = function(responseText, errorText) { |
462 | var el = $(this); |
463 | |
464 | if (errorText === 'error') { |
465 | el.append(pb.ajax_error_recover(responseText, selector)); |
466 | } else if (!responseText.length) { |
467 | el.append(ajax_noresponse_message || 'No response from server.'); |
468 | } |
469 | |
470 | // a non-semantic div here will make sure we can |
471 | // do enough formatting. |
472 | el.wrapInner('<div />'); |
473 | |
474 | // add the submit handler if we've a formtarget |
475 | if (formtarget) { |
476 | var target = el.find(formtarget); |
477 | if (target.length > 0) { |
478 | pb.prep_ajax_form(target); |
479 | } |
480 | } |
481 | |
482 | // if a closeselector has been specified, tie it to the overlay's |
483 | // close method via closure |
484 | if (closeselector) { |
485 | el.find(closeselector).click(function (event) { |
486 | api.close(); |
487 | return false; |
488 | }); |
489 | } |
490 | |
491 | // This may be a complex form. |
492 | if ($.fn.ploneTabInit) { |
493 | el.ploneTabInit(); |
494 | } |
495 | |
496 | // remove element on close so that it doesn't congest the DOM |
497 | api.onClose = function () { |
498 | content.remove(); |
499 | }; |
500 | $(document).trigger('loadInsideOverlay', [this, responseText, errorText, api]); |
501 | } |
502 | |
503 | // and load the div |
504 | el.load(src, null, function (responseText, errorText) { |
505 | // post-process the overlay contents |
506 | el[0].handle_load_inside_overlay.apply(this, [responseText, errorText]); |
507 | |
508 | // Now, it's all ready to display; hide the |
509 | // spinner and call JQT overlay load. |
510 | pb.spinner.hide(); |
511 | api.load(); |
512 | |
513 | return true; |
514 | }); |
515 | |
516 | // don't do the default action |
517 | return false; |
518 | }; |
519 | |
520 | |
521 | /****** |
522 | pb.iframe |
523 | onBeforeLoad handler for iframe overlays. |
524 | |
525 | Note that the spinner is handled a little differently |
526 | so that we can keep it displayed while the iframe's |
527 | content is loading. |
528 | ******/ |
529 | pb.iframe = function () { |
530 | var content, pbo; |
531 | |
532 | pb.spinner.show(); |
533 | |
534 | content = this.getOverlay(); |
535 | pbo = this.getTrigger().data('pbo'); |
536 | |
537 | if (content.find('iframe').length === 0 && pbo.src) { |
538 | content.append( |
539 | '<iframe src="' + pbo.src + '" width="' + |
540 | content.width() + '" height="' + content.height() + |
541 | '" onload="pb.spinner.hide()"/>' |
542 | ); |
543 | } else { |
544 | pb.spinner.hide(); |
545 | } |
546 | return true; |
547 | }; |
548 | |
549 | // $('.newsImageContainer a') |
550 | // .prepOverlay({ |
551 | // subtype: 'image', |
552 | // urlmatch: '/image_view_fullscreen$', |
553 | // urlreplace: '_preview' |
554 | // }); |
555 | |
556 | }); |