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