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