1(function($){ 2 3 var popupviewer = function() { 4 }; 5 6 /* singleton */ 7 var instance = null; 8 $.popupviewer = function() { 9 return instance || (instance = new popupviewer()); 10 }; 11 12 // Static functions 13 (function(_){ 14 15 var viewer = null; 16 var content = null; 17 var additionalContent = null; 18 var BASE_URL = DOKU_BASE + 'lib/exe/ajax.php'; 19 var viewerIsFixed = false; 20 var next = null; 21 var previous = null; 22 var internal = {}; 23 24 _.popupImageStack = null; 25 26 internal.log = function(message) { 27 console.log(message); 28 }; 29 30 _.showViewer = function() { 31 32 if ( viewer == null ) { 33 34 viewer = $('<div id="popupviewer"/>').click(_.hideViewer).appendTo('body'); 35 content = $('<div class="content"/>').click(function(e){e.stopPropagation()}); 36 content.current = $(); 37 38 additionalContent = $('<div class="additionalContent dokuwiki"/>'); 39 viewerIsFixed = viewer.css('position'); 40 41 $('<div class="controls"/>'). 42 append(content). 43 append(additionalContent). 44 append(previous = $('<a class="previous"/>').click({'direction': -1}, _.skipImageInDirection)). 45 append(next = $('<a class="next"/>').click({'direction': 1}, _.skipImageInDirection)). 46 append($('<a class="close"/>').addClass('visible').click(_.hideViewer)). 47 appendTo(viewer); 48 49 $(document).keydown(internal.globalKeyHandler); 50 51 } 52 53 content.empty(); 54 additionalContent.empty(); 55 $('body').css('overflow', 'hidden'); 56 viewer.show(); 57 return _; 58 }; 59 60 _.hideViewer = function(e, finalFunction) { 61 if ( viewer != null ) { 62 $('body').css('overflow', 'auto'); 63 64 additionalContent.animate({ 65 opacity: 0, 66 height: 0 67 }); 68 69 content.animate({ 70 width : 208, 71 height : 13, 72 }).parent('.controls').animate({ 73 top : '50%', 74 left : '50%', 75 'margin-left' : -104 76 }).parent('#popupviewer').animate({ 77 opacity: finalFunction ? 1 : 0 78 }, function(){ 79 viewer.hide(); 80 81 content.empty(); 82 additionalContent.empty(); 83 content.current = null; 84 85 additionalContent.css({ 86 opacity: 1, 87 height: '' 88 }); 89 90 content.css({ 91 width : '', 92 height : '', 93 }).parent('.controls').css({ 94 top : '', 95 left : '', 96 'margin-left' : '' 97 }).parent('#popupviewer').css({ 98 opacity : 1 99 }); 100 101 if ( typeof finalFunction == 'function' ) { 102 finalFunction(e); 103 } 104 }); 105 } 106 107 return _; 108 }; 109 110 internal.globalKeyHandler = function(e) { 111 112 if ( !viewer.is(":visible") ) return; 113 114 switch(e.keyCode) { 115 case 39: // Right 116 e.stopPropagation(); 117 next.click(); 118 break; 119 case 37: // Left 120 e.stopPropagation(); 121 previous.click(); 122 break; 123 case 27: // Escape 124 e.stopPropagation(); 125 _.hideViewer(); 126 break; 127 } 128 }; 129 130 _.presentViewerWithContent = function(e, popupData) { 131 132 popupData = popupData || this.popupData || e.target.popupData; // Either as param or from object 133 134 /* 135 popupData = { 136 isImage: boolean, 137 call: ajax_handler, 138 src: URL, 139 id: alternate_wiki_page, 140 width: width_of_window, 141 height: height_of_window 142 } 143 */ 144 145 if ( !popupData ) { return; } 146 147 e && e.preventDefault(); 148 _.showViewer(); 149 150 content.current = $(this); 151 152 internal.log(popupData); 153 154 if ( popupData.isImage ) { 155 156 // Load image routine 157 internal.log("loading an image"); 158 popupData.call = popupData.call || '_popup_load_image_meta'; 159 $(new Image()).attr('src', popupData.src || this.href).waitForImages(function(){ 160 161 var image = $(this); 162 163 var wrapper = $('<div/>').load(BASE_URL, popupData, function() { 164 165 // Force size for the moment 166 content.css({ 167 width: content.width(), 168 height: content.height(), 169 overflow: 'hidden' 170 }) 171 172 content.append(image); 173 content.popupData = jQuery.extend(true, {}, popupData); 174 175 additionalContent.html(wrapper.html()); 176 _.resizePopup(popupData.width, popupData.height, additionalContent.innerHeight(), image); 177 }); 178 }); 179 180 } else { 181 182 popupData.call = popupData.call || '_popup_load_file'; 183 popupData.src = popupData.src || BASE_URL; 184 var wrapper = $('<div/>').load(popupData.src, popupData, function(response, status, xhr) { 185 186 var success = function(node) 187 { 188 node.find('div.dokuwiki,body').first().waitForImages({ 189 finished: function() { 190 191 // Force size for the moment 192 content.css({ 193 width: content.width(), 194 height: content.height() 195 }) 196 197 content.html(this); 198 199 // Check for Javascript to execute 200 var script = ""; 201 node.find('popupscript'). 202 each(function() { 203 script += $.parseJSON((this.innerHTML || this.innerText)); 204 }) 205 206 var newContext = "jQuery.noConflict(); containerContext = this; ___ = function( selector, context ){return new jQuery.fn.init(selector,context||containerContext);}; ___.fn = ___.prototype = jQuery.fn;jQuery.extend( ___, jQuery );jQuery = ___;\n" 207 208 if ( script.length > 0 ) { 209 var randomID = Math.ceil(Math.random()*1000000); 210 content.attr('id', randomID); 211 212 var newContext = "jQuery.noConflict(); containerContext = this; ___ = function( selector, context ){return new jQuery.fn.init(selector,context||containerContext);}; ___.fn = ___.prototype = jQuery.fn;jQuery.extend( ___, jQuery );jQuery = ___;\n" 213 214 try{ 215 $.globalEval("try{\n(function(){\n"+newContext+script+"\n}).call(jQuery('div#"+randomID+"').get(0));\n}catch(e){}\n//"); 216 } catch (e) { 217 internal.log("Exception!"); 218 internal.log(e); 219 } 220 } 221 222 _.resizePopup(popupData.width, popupData.height, null, content, true); 223 224 }, waitForAll: true}); 225 } 226 227 228 if ( status == "error") { 229 // Go for an iframe 230 var finished = false; 231 var iframe = null; 232 233 var messageFunction = function(event) { 234 235 finished = true; 236 var data = event.data || event.originalEvent.data; 237 // If this message does not come with what we want, discard it. 238 if ((typeof data).toLowerCase() == "string" || !data.message 239 || data.message != 'frameContent') { 240 alert("Could not load page via popupviewer. The page responded with a wrong message."); 241 return; 242 } 243 244 iframe.remove(); 245 246 // Clear the window Event after we are done! 247 $(window).unbind("message", messageFunction); 248 249 success($(data.body)); 250 }; 251 252 var iframe = $('<iframe/>').load(function(){ 253 254 var frame = this; 255 if ( frame.contentWindow.postMessage ) { 256 257 // Register the Message Event for PostMessage receival 258 $(window).bind("message", messageFunction); 259 260 // Send a message 261 var message = "getFrameContent"; 262 frame.contentWindow.postMessage(message, "*"); 263 } 264 265 }).hide().attr('src', internal.getCurrentLocation() ).appendTo('body'); 266 267 window.setTimeout(function() { 268 if (!finished) { 269 iframe.remove(); 270 alert("Could not load page via popupviewer. The page is not available."); 271 } 272 }, 30000); 273 274 } else { 275 success(wrapper) 276 } 277 278 }); 279 } 280 }; 281 282 /* has to be called via popupscript in page if needed. */ 283 _.propagateClickHandler = function(node) { 284 node.find('a[href],form[action]'). 285 each(function(){ 286 // Replace all event handler 287 288 var element = $(this); 289 290 urlpart = element.attr('href') || element.attr('action') || ""; 291 if ( urlpart.match(new RegExp("^#.*?$")) ) { 292 // Scroll to anchor 293 element.click(function(){ 294 content.get(0).scrollTop( urlpart == '#' ? 0 : $(urlpart).offset().top); 295 }); 296 } 297 298 if ( this.getAttribute('popupviewerdata') ) { 299 this.popupData = $.parseJSON(this.getAttribute('popupviewerdata')); 300 this.removeAttribute('popupviewerdata'); 301 } else { 302 this.popupData = jQuery.extend(true, {}, popupData); 303 this.popupData.src = urlpart; 304 delete(this.popupData.id); // or it will always load this file. 305 } 306 307 $(this).bind('click', function(e){ 308 e.stopPropagation(); e.preventDefault(); 309 _.hideViewer(e, _.presentViewerWithContent); 310 }); 311 }); 312 }; 313 314 internal.getCurrentLocation = function() { 315 return content.current.attr('href') || content.current.attr('src') || content.current.attr('action'); 316 }; 317 318 internal.optimalSize = function(offsetElement) { 319 320 var prevWidth = content.width(); 321 var prevHeight = content.height(); 322 323 offsetElement.css({width:'', height: ''}); 324 325 width = offsetElement.width(); 326 height = offsetElement.height(); 327 328 // Reset to previous size so the whole thing will animate from the middle 329 offsetElement.css({width:prevWidth, height: prevHeight}); 330 331 return {width: width, height: height}; 332 } 333 334 _.resizePopup = function(width, height, additionalHeight, offsetElement, isPageContent) { 335 336 if ( offsetElement && !width && !height) { 337 var optimalSize = internal.optimalSize(offsetElement); 338 width = optimalSize.width; 339 height = optimalSize.height; 340 } 341 342 width = parseInt(width) || ($(window).width() * 0.7); 343 height = parseInt(height) || ($(window).height() * 0.8); 344 345 var ratio = width / height; 346 var maxHeight = ( $(window).height() * 0.99 ) - 60; 347 var maxWidth = ( $(window).width() * 0.99 ) - 40; 348 349 additionalHeight = additionalHeight || 0; 350 height += additionalHeight; 351 352 if ( height > maxHeight ) { 353 height = maxHeight; 354 if ( !isPageContent ) { // If this is an image we will have to fix the size 355 width = (height - additionalHeight) * ratio; 356 } else { 357 width += 20; // For the scroller Bar that will apear; 358 } 359 } 360 361 if ( width > maxWidth ) { 362 width = maxWidth; 363 if ( !isPageContent ) { // If this is an image we will have to fix the size 364 height = width / ratio + additionalHeight; 365 } 366 } 367 368 var xOffset = viewerIsFixed ? 0 : $(document).scrollLeft() || 0; 369 var yOffset = viewerIsFixed ? 0 : $(document).scrollTop() || 0; 370 371 yOffset = Math.max(($(window).height() - height) * 0.5 + yOffset, 5); 372 xOffset += ($(window).width() - width) * 0.5; 373 374 internal.log(width + " " + height); 375 internal.log(xOffset + " " + yOffset); 376 377 if ( !isPageContent && offsetElement.is('img') ) { 378 379 offsetElement.animate({ 380 width : width, 381 height : height - additionalHeight 382 }); 383 384 content.css({ 385 width : '', 386 height : '', 387 overflow: '' 388 }); 389 390 } else { 391 content.animate({ 392 width : width, 393 height : isPageContent ? height : 'auto', 394 }); 395 } 396 397 content.parent().animate({ 398 top : yOffset, 399 left : xOffset, 400 'margin-left' : 0 401 }); 402 403 if ( isPageContent ) { 404 content.removeClass('isImage'); 405 } else { 406 content.addClass('isImage'); 407 } 408 409 _.handleNextAndPrevious(!isPageContent); 410 return _; 411 }; 412 413 _.skipImageInDirection = function(e) 414 { 415 e.stopPropagation(); 416 417 if ( !$(this).is(':visible') ) { return; } 418 419 var skipTo = $.inArray(content.current.get(0), _.popupImageStack) + e.data.direction; 420 skipTo = Math.min(_.popupImageStack.length-1, Math.max(skipTo, 0)); 421 422 internal.log("skipping " + (e.data.direction < 0 ? 'previous' : 'next') + ' ' + skipTo ); 423 return _.skipToImage(skipTo, e.data.direction); 424 }; 425 426 _.skipToImage = function(skipTo, inDirection) 427 { 428 if ( !$(_.popupImageStack[skipTo]).is(content.current) ) { 429 _.hideViewer(null, function() { 430 // Deliver extra functionality to clicked item. 431 var nextItem = _.popupImageStack[skipTo]; 432 (nextItem.popupData && nextItem.popupData.click && nextItem.popupData.click(skipTo, inDirection)) || $(nextItem).click(); 433 }); 434 } 435 436 return _; 437 } 438 439 _.isFirst = function() { 440 return _.popupImageStack.first().is(content.current); 441 } 442 443 _.isLast = function() { 444 return _.popupImageStack.last().is(content.current); 445 } 446 447 _.handleNextAndPrevious = function(currentIsImage) { 448 449 if ( currentIsImage && _.popupImageStack && _.popupImageStack.length > 1) { 450 451 if ( _.isFirst() ) { 452 previous.addClass('inactive'); 453 } else { 454 previous.removeClass('inactive'); 455 } 456 457 if ( _.isLast() ) { 458 next.addClass('inactive'); 459 } else { 460 next.removeClass('inactive'); 461 } 462 463 next.addClass('visible'); 464 previous.addClass('visible'); 465 } else { 466 next.removeClass('visible'); 467 previous.removeClass('visible'); 468 } 469 470 return _; 471 }; 472 473 _.init = function(popupImageStack) { 474 475 _.popupImageStack = $(popupImageStack || 'a[popupviewerdata]').each(function(){ 476 this.popupData = this.popupData || $.parseJSON(this.getAttribute('popupviewerdata')); 477 if (this.removeAttribute) this.removeAttribute('popupviewerdata'); 478 $(this).unbind('click').click(_.presentViewerWithContent); 479 }).filter(function(){ 480 // Only images allowed in Stack. 481 return this.popupData.isImage; 482 }); 483 484 return _; 485 }; 486 487 })(popupviewer.prototype); 488 489 // Namespace all events. 490 var eventNamespace = 'waitForImages'; 491 492 // CSS properties which contain references to images. 493 $.waitForImages = { 494 hasImageProperties: ['backgroundImage', 'listStyleImage', 'borderImage', 'borderCornerImage', 'cursor'] 495 }; 496 497 // Custom selector to find `img` elements that have a valid `src` attribute and have not already loaded. 498 $.expr[':'].uncached = function (obj) { 499 // Ensure we are dealing with an `img` element with a valid `src` attribute. 500 if (!$(obj).is('img[src!=""]')) { 501 return false; 502 } 503 504 // Firefox's `complete` property will always be `true` even if the image has not been downloaded. 505 // Doing it this way works in Firefox. 506 var img = new Image(); 507 img.src = obj.src; 508 return !img.complete; 509 }; 510 511 $.fn.waitForImages = function (finishedCallback, eachCallback, waitForAll) { 512 513 var allImgsLength = 0; 514 var allImgsLoaded = 0; 515 516 // Handle options object. 517 if ($.isPlainObject(arguments[0])) { 518 waitForAll = arguments[0].waitForAll; 519 eachCallback = arguments[0].each; 520 // This must be last as arguments[0] 521 // is aliased with finishedCallback. 522 finishedCallback = arguments[0].finished; 523 } 524 525 // Handle missing callbacks. 526 finishedCallback = finishedCallback || $.noop; 527 eachCallback = eachCallback || $.noop; 528 529 // Convert waitForAll to Boolean 530 waitForAll = !! waitForAll; 531 532 // Ensure callbacks are functions. 533 if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) { 534 throw new TypeError('An invalid callback was supplied.'); 535 } 536 537 return this.each(function () { 538 // Build a list of all imgs, dependent on what images will be considered. 539 var obj = $(this); 540 var allImgs = []; 541 // CSS properties which may contain an image. 542 var hasImgProperties = $.waitForImages.hasImageProperties || []; 543 // To match `url()` references. 544 // Spec: http://www.w3.org/TR/CSS2/syndata.html#value-def-uri 545 var matchUrl = /url\(\s*(['"]?)(.*?)\1\s*\)/g; 546 547 if (waitForAll) { 548 549 // Get all elements (including the original), as any one of them could have a background image. 550 obj.find('*').addBack().each(function () { 551 var element = $(this); 552 553 // If an `img` element, add it. But keep iterating in case it has a background image too. 554 if (element.is('img:uncached')) { 555 allImgs.push({ 556 src: element.attr('src'), 557 element: element[0] 558 }); 559 } 560 561 $.each(hasImgProperties, function (i, property) { 562 var propertyValue = element.css(property); 563 var match; 564 565 // If it doesn't contain this property, skip. 566 if (!propertyValue) { 567 return true; 568 } 569 570 // Get all url() of this element. 571 while (match = matchUrl.exec(propertyValue)) { 572 allImgs.push({ 573 src: match[2], 574 element: element[0] 575 }); 576 } 577 }); 578 }); 579 } else { 580 // For images only, the task is simpler. 581 obj.find('img:uncached') 582 .each(function () { 583 allImgs.push({ 584 src: this.src, 585 element: this 586 }); 587 }); 588 } 589 590 allImgsLength = allImgs.length; 591 allImgsLoaded = 0; 592 593 // If no images found, don't bother. 594 if (allImgsLength === 0) { 595 finishedCallback.call(obj[0]); 596 } 597 598 $.each(allImgs, function (i, img) { 599 600 var image = new Image(); 601 602 // Handle the image loading and error with the same callback. 603 $(image).on('load.' + eventNamespace + ' error.' + eventNamespace, function (event) { 604 allImgsLoaded++; 605 606 // If an error occurred with loading the image, set the third argument accordingly. 607 eachCallback.call(img.element, allImgsLoaded, allImgsLength, event.type == 'load'); 608 609 if (allImgsLoaded == allImgsLength) { 610 finishedCallback.call(obj[0]); 611 return false; 612 } 613 614 }); 615 616 image.src = img.src; 617 }); 618 }); 619 }; 620 621 $(function(){ 622 $.popupviewer().init(); 623 }); 624 625})(jQuery); 626 627 628/* Loading the content for locally exported content */ 629(function($){ 630 $(window).bind("message", function(event){ 631 632 var data = event.data || event.originalEvent.data; 633 var source = event.source || event.originalEvent.source; 634 if (data != "getFrameContent") { 635 return; 636 } 637 638 try { 639 source.postMessage({ 640 message : "frameContent", 641 body : jQuery('html').html() 642 }, "*"); 643 } catch (e) { 644 alert("Fatal Exception! Could not load page via popupviewer.\n" + e); 645 } 646 }); 647})(jQuery);