xref: /plugin/popupviewer/script.js (revision b09edcf49979bc18e162665b6fe1e286f1479ea5)
1var popupviewer = function(showContent, isImage, width, height) {
2
3	if ( jQuery ) {
4		var $ = jQuery;
5	}
6
7	this.screenWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
8	this.screenHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
9	this.contentDiv = null;
10	this.controlDiv = null;
11	this.maxWidthFactor = 0.7;
12	this.maxHeightFactor = 0.8;
13	this.maxWidth = null;
14	this.maxHeight = null;
15	this.endWidth = 0;
16	this.endHeight = 0;
17	this.endMarginTop = 0;
18	this.endMarginLeft = 0;
19	this.isImage = false;
20	this.additionalContent = null;
21	this.additionalContentID = null;
22	this.page = null;
23	this.event = null;
24	this.wasError = false;
25	this.popupImageStack = null;
26
27	this.showContent = null;
28	this.isRTL = false;
29
30	var self = this;
31
32	this.getFirst = function() { /* To be implemented */
33		if (!this.popupImageStack) {
34			return false;
35		}
36		return this.popupImageStack[0].id == this.page;
37	};
38
39	this.getLast = function() { /* To be implemented */
40		if (!this.popupImageStack) {
41			return false;
42		}
43		return this.popupImageStack[this.popupImageStack.length - 1].id == this.page;
44	};
45
46	this.skipToImage = function(itemNr) {
47
48		var previous = null;
49		var elem = null;
50		for ( var item in this.popupImageStack) {
51
52			if ( !this.popupImageStack.hasOwnProperty(item) )
53			{
54				continue;
55			}
56
57			var check = this.popupImageStack[item];
58
59			// previous was inpoint
60			if (previous && previous.id == this.page) {
61				elem = check;
62				break;
63			}
64
65			// Found + must go
66			if (check.id == this.page && itemNr < 0) {
67				elem = previous;
68				break;
69			}
70
71			previous = check;
72			elem = check;
73		}
74
75		if (elem) {
76			this.dispatchClick(elem);
77		}
78	};
79
80	this.dispatchClick = function(elem) {
81		if (elem == null) {
82			return;
83		}
84
85		$(elem).trigger('click');
86	};
87
88	this.setContentSize = function(width, height, offsetHeight, offsetElement) {
89
90		if (!this.contentDiv || !this.controlDiv) {
91			return;
92		}
93
94		if (!width || width === 0) {
95			width = this.screenWidth * this.maxWidthFactor;
96		}
97
98		if (!height || height === 0) {
99			height = this.screenHeight * this.maxHeightFactor;
100		}
101
102		// If given, we want the optimal size
103		if ( offsetElement )
104		{
105    		try {
106        		// IE8 has isues
107        		offsetElement.style.display = 'fixed';
108    		} catch(e) {}
109
110    		offsetElement.style.visibility = 'hidden';
111    		offsetElement.style.width = '';
112    		offsetElement.style.height = '';
113
114    		height = offsetElement.offsetHeight;
115    		width = offsetElement.offsetWidth;
116
117    		try {
118        		// IE8 has isues
119        		offsetElement.style.display = '';
120    		} catch(e) {}
121    		offsetElement.style.visibility = '';
122		}
123
124		width = parseFloat(width);
125		height = parseFloat(height);
126		offsetHeight = typeof offsetHeight == "undefined" || isNaN(parseFloat(offsetHeight)) ? 0 : parseFloat(offsetHeight); // may be undefined
127		var ratio = width / height;
128
129		height += offsetHeight;
130
131		if (height > (this.screenHeight * 0.99) - 60) {
132			height = (this.screenHeight * 0.99) - 60;
133
134			if (this.isImage) { // If this is an image we will have to fix the size
135				width = (height - offsetHeight) * ratio;
136			} else {
137				width += 20; // For the scroller Bar that will apear;
138			}
139		}
140
141		if (width > (this.screenWidth * 0.99) - 40) {
142			width = (this.screenWidth * 0.99) - 40;
143
144			if (this.isImage) { // If Image is defined then we will have to fix it
145				height = (width / ratio) + offsetHeight;
146			}
147		}
148
149		this.endWidth = width + (this.isImage ? 0 : 24); // 24 Px for padding + Border if is Image
150		this.endHeight = height;
151
152		var xOffset = document.body.scrollLeft || document.documentElement.scrollLeft || window.pageXOffset || 0;
153		var yOffset = document.body.scrollTop || document.documentElement.scrollTop || window.pageYOffset || 0;
154
155		this.endMarginTop = (this.screenHeight - height) * 0.5 + yOffset;
156		if (this.endMarginTop < 5) {
157			this.endMarginTop = 5;
158		}
159
160		this.endMarginLeft = (this.screenWidth - width) * 0.5 + xOffset;
161		this.setSize();
162		if ( !$('#popupviewer_loader_div').size() > 0 ) this.addNextAndPrevious();
163	};
164
165	this.setSize = function() {
166
167		$(this.contentDiv).css({
168			'width' : this.endWidth + 'px',
169			'height' : !this.isImage ? this.endHeight + 'px' : 'auto'
170		});
171
172		$(this.controlDiv).css({
173			'top'   : this.endMarginTop + 'px',
174			'left'  : self.isRTL ? this.endMarginLeft + 'px' : 'auto',
175			'right' : !self.isRTL ? this.endMarginLeft + 'px' : 'auto'
176		});
177	};
178
179	this.addNextAndPrevious = function() {
180
181		// If not already defined, do so now
182		if (!this.popupImageStack) {
183			this.popupImageStack = $(document).find('img.popupimage');
184		}
185
186		if (this.popupImageStack && this.popupImageStack.length > 1) {
187
188			var previousImage = document.createElement('a');
189			previousImage.id = 'popupviewer_control_prevoiusImage';
190
191			var nextImage = document.createElement('a');
192			nextImage.id = 'popupviewer_control_nextImage';
193
194			var self = this;
195			var skipEvent = function(event) { /* To be implemented */
196
197				if (!event) {
198					var event = window.event;
199				}
200
201				var target = ((event.target) ? event.target : event.srcElement).id.indexOf("next") > 0 ? 1 : -1;
202				self.skipToImage(target);
203			};
204
205			// If this is not the last image - set inactive
206			if (!this.getLast()) {
207				$(nextImage).click(skipEvent);
208			} else {
209				nextImage.className = "inactive";
210			}
211
212			// If this is not the first image - set inactive
213			if (!this.getFirst()) {
214				$(previousImage).click(skipEvent);
215			} else {
216				previousImage.className = "inactive";
217			}
218
219			if ( $('#'+nextImage.id).size() > 0 ) { $('#'+nextImage.id).remove(); }
220			if ( $('#'+previousImage.id).size() > 0 ) { $('#'+previousImage.id).remove(); }
221
222			this.controlDiv.appendChild(nextImage);
223			this.controlDiv.appendChild(previousImage);
224		}
225	};
226
227	this.getIntValue = function(value) {
228		return parseInt(value.substr(0, value.indexOf('px')), 10);
229	};
230
231	this.buildViewerWithLoader = function() {
232
233		this.removeOldViewer();
234		this.contentDiv = document.createElement('div');
235		this.contentDiv.id = 'popupviewer_content';
236		this.contentDiv.className = 'isImage';
237
238		this.controlDiv = document.createElement('div');
239		this.controlDiv.id = 'popupviewer_control';
240
241		this.controlDiv.appendChild(this.contentDiv);
242
243		var loaderDiv = document.createElement('div');
244		loaderDiv.id = 'popupviewer_loader_div';
245
246		this.contentDiv.appendChild(loaderDiv);
247
248		var closeImage = document.createElement('a');
249		closeImage.id = 'popupviewer_control_closeImage';
250
251		this.controlDiv.appendChild(closeImage);
252
253		var sampleDiv = document.createElement('div');
254		sampleDiv.id = 'popupviewer';
255
256		var overlayDiv = document.createElement('div');
257		overlayDiv.id = 'popupviewer_overlay';
258
259		var arVersion = navigator.appVersion.split("MSIE");
260		var version = parseFloat(arVersion[1]);
261		if (!(version >= 5.0 && version < 7.0)) {
262			overlayDiv.style.position = 'fixed';
263		} else {
264			overlayDiv.style.height = (document.body.offsetHeight -1 ) + 'px';
265			overlayDiv.style.width = (document.body.offsetWidth -1 ) + 'px';
266		}
267
268		sampleDiv.appendChild(overlayDiv);
269
270		/* IE 6 Crasher */
271		sampleDiv.appendChild(this.controlDiv);
272
273		$(overlayDiv).click(self.removeOldViewer);
274		$(closeImage).click(self.removeOldViewer);
275		$(document).bind('keydown', self.globalEvent);
276
277		// window.scrollTo(0, 0);
278		document.getElementsByTagName('body')[0].style.overflow = 'hidden';
279		document.getElementsByTagName('body')[0].appendChild(sampleDiv);
280
281		this.setContentSize(210, 20);
282	};
283
284	this.removeOldViewer = function()
285	{
286		if ($('#popupviewer').size() > 0) {
287			$('#popupviewer').remove();
288			$(document).unbind('keydown', self.globalEvent);
289		}
290		document.getElementsByTagName('body')[0].style.overflow = 'auto';
291	};
292
293	this.displayContent = function(showContent, isImage, width, height) {
294
295		this.isImage = isImage;
296
297		if (!$('#popupviewer').size() > 0) {
298			this.buildViewerWithLoader();
299		}
300		if (!showContent || showContent === null) {
301			if (typeof (showContent) != 'undefined') {
302				this.setContentSize(width, height);
303			}
304			return this;
305		}
306
307		if (isImage) {
308
309			var img = new Image();
310			img.src = showContent;
311			img.className = "imageContent";
312
313			if (this.event) {
314				var elem = (this.event.target) ? this.event.target : this.event.srcElement;
315				this.page = elem.id;
316			}
317
318			var check = new checkImageRoutine(img);
319			var self = this;
320			var callback = {
321
322				image : img,
323				error : function() {
324					self.removeOldViewer();
325				},
326				finalize : function() {
327
328					// var height = this.image.height;
329					var selfCallback = this;
330
331					// self.setContentSize(this.image.width, height, true);
332					var callback = function(response) {
333
334						var container = document.createElement('div');
335						container.className = 'additionalContent dokuwiki';
336						container.innerHTML = response;
337
338						$('#popupviewer_loader_div').remove();
339						$('#popupviewer_content').append(selfCallback.image);
340						self.contentDiv.className = 'dokuwiki';
341						self.contentDiv.className = 'isImage';
342						$('#popupviewer_content').append(container);
343
344						self.setContentSize(selfCallback.image.offsetWidth,selfCallback.image.offsetHeight,container.offsetHeight);
345						$(selfCallback.image).css({
346							'width':self.endWidth + 'px',
347							'height':self.endHeight + 'px',
348						})
349					};
350
351					var errorCallback = function() {
352						$('#popupviewer_loader_div').remove();
353						$('#popupviewer_content').append(selfCallback.image);
354						self.contentDiv.className = 'dokuwiki';
355						self.contentDiv.className = 'isImage';
356
357						self.setContentSize(selfCallback.image.offsetWidth, selfCallback.image.offsetHeight);
358						$(selfCallback.image).css({
359							'width':self.endWidth + 'px',
360							'height':self.endHeight + 'px',
361						})
362
363					};
364
365					if (self.additionalContent) {
366						callback(self.additionalContent);
367					} else {
368						self.runAJAX(callback, {
369							'call' : '_popup_load_image_meta',
370							'id' : self.additionalContentID
371						}, errorCallback);
372					}
373
374				}
375			};
376
377			check.checkLoadImage(50, callback);
378		} else {
379			this.contentDiv.className = 'dokuwiki';
380			this.contentDiv.innerHTML = showContent;
381			this.setContentSize(width, height, null, this.contentDiv);
382		}
383	};
384
385	this.linkReplacer = function(matches, depth) {
386
387		var schema = matches[1];
388		var urlpart = matches[2];
389
390		if (urlpart.match(/^#(.*?)$/)) {
391			// ScrollToDiv
392			urlpart += "\" onclick=\"if(!event){var event=window.event;}if(event){event.cancelBubble=true;event.returnValue=false;}if(event&&event.stopPropagation){event.stopPropagation();}if(event&&event.preventDefault){event.preventDefault();}jQuery('#popupviewer_content').scrollTop=jQuery('#"
393					+ ((urlpart == "#") ? "popupviewer_content" : urlpart
394							.substr(1)) + "').offsetTop;return false;";
395		} else if (!urlpart.match(new RegExp("^(https?:\/\/|mailto:|"
396				+ escape(DOKU_BASE) + ")"))) {
397			urlpart = depth + urlpart;
398		}
399
400		return schema + '="' + urlpart + '"';
401	};
402
403	this.callback = function(data) {
404
405		/* Reset Init Events */
406		window.oninit = function() {
407		};
408
409		/* check for script to be executed */
410		var script = "";
411		if (typeof data == "string" && data !== '') {
412			data = data.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,
413					function() {
414						if (data !== null) {
415							script += arguments[1].replace(new RegExp("(<!--\/\/--><!\\[CDATA\\[\/\/><!--|\/\/--><!\\]\\]>)", "gi"), "") + '\n';
416						}
417						return '';
418					});
419
420		}
421
422		try {
423			data = self.preg_replace_callback( '/(href|src|action)="([^"]*)"/ig', self.linkReplacer, data);
424			self.displayContent(data, false, self.endWidth, self.endHeight);
425		} catch (e) {
426			alert(e);
427			return self.removeOldViewer();
428		}
429		try {
430			eval(script + "window.oninit();");
431		} catch (scriptE) {
432			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" + scriptE);
433		}
434	};
435
436	// lets try iframe on an error
437	// This relies on the postMessage function of newer browsers
438	// and needs: if ( typeof parent != 'undefined' && parent.postMessage) {
439	// parent.postMessage(document.body.scrollHeight, '*'); }
440	// on the onload function of the loaded pages body.
441	this.errorCallback = function(successaction) {
442
443		// Build Frame
444		var iframe = document.createElement("iframe");
445		iframe.id = "__contentIFrame";
446		iframe.name = "__contentIFrame";
447		iframe.setAttribute('scrolling', 'no');
448		iframe.style.display = "none";
449
450		var finished = false;
451		var messageFunction = function(event) {
452
453			finished = true;
454			var data = event.data || event.originalEvent.data;
455			// If this message does not come with what we want, discard it.
456			if ((typeof data).toLowerCase() == "string" || !data.message
457					|| data.message != 'frameContent') {
458				alert("Could not load page via popupviewer. The page responded with a wrong message.");
459				return;
460			}
461
462			successaction(data.body);
463
464			$(iframe).remove();
465
466			// Clear the window Event after we are done!
467			$(window).unbind("message", messageFunction);
468		};
469
470		// load event for the iframe to display the content
471		$(iframe).bind('load', function() {
472
473			// Check If we can send a postMessage
474			if (iframe.contentWindow.postMessage) {
475
476				// Register the Message Event for PostMessage receival
477				$(window).bind("message", messageFunction);
478
479				// Send a message
480				var message = "getFrameContent";
481				iframe.contentWindow.postMessage(message, "*");
482			}
483		});
484
485		window.setTimeout(function() {
486			if (!finished) {
487				$(iframe).remove();
488				alert("Could not load page via popupviewer. The page is not available.");
489			}
490		}, 30000);
491
492		iframe.src = self.page;
493		document.getElementsByTagName('body')[0].appendChild(iframe);
494	};
495
496	this.loadAndDisplayPage = function(page, width, height, id, params) {
497
498		if (this.event) {
499			var elem = (this.event.target) ? this.event.target : this.event.srcElement;
500			this.page = elem.href == page ? elem.getAttribute('href') : "";
501		}
502
503		this.endWidth = width;
504		this.endHeight = height;
505
506		var self = this;
507
508		// Set custom params
509		if ( (typeof params).toLowerCase() != "object" ) { params = {}; }
510		if ( !params.call ) { params.call = '_popup_load_file'; }
511		if ( !params.id ) { params.id = id; }
512		this.runAJAX(self.callback, params, self.errorCallback);
513	};
514
515	this.globalEvent = function(e) {
516
517		e = e||window.event;
518
519		if ( e.keyCode ) {
520			switch( e.keyCode ) {
521				case 39: // Right
522					if ( $('#popupviewer_control_nextImage').size() > 0 && !self.getLast() ) {
523						self.dispatchClick($('popupviewer_control_nextImage'));
524					}
525					break;
526				case 37: // Left
527					if ( $('#popupviewer_control_prevoiusImage').size() > 0 && !self.getFirst() ) {
528						self.dispatchClick($('#popupviewer_control_prevoiusImage'));
529					}
530					break;
531				case 27: // Escape
532					self.removeOldViewer();
533					break;
534			}
535		}
536		return;
537	};
538
539	this.runAJAX = function(callback, options, errorCallback, url) {
540
541		var trackLink = url;
542		if (typeof url == "undefined") {
543			url = DOKU_BASE + 'lib/exe/ajax.php';
544		}
545
546		var success = function (data) {
547
548			// Google Ping
549			if ( typeof googleanalytics_trackLink != "undefined" ) {
550				googleanalytics_trackLink(trackLink);
551			}
552			if ( typeof callback == "function" ) {
553    			callback(data);
554			}
555		};
556
557		$('#popupviewer_content').load(url, options, function( response, status, xhr ) {
558
559			if ( status == "error" ) {
560				// Retry
561				errorCallback(success);
562			} else {
563				success(response);
564			}
565
566		});
567
568	};
569
570	this.preg_replace_callback = function(pattern, callback, subject, limit) {
571		// Perform a regular expression search and replace using a callback
572		//
573		// discuss at: http://geekfg.net/
574		// + original by: Francois-Guillaume Ribreau (http://fgribreau)
575		// * example 1:
576		// preg_replace_callback("/(\\@[^\\s,\\.]*)/ig",function(matches){return
577		// matches[0].toLowerCase();},'#FollowFriday @FGRibreau @GeekFG',1);
578		// * returns 1: "#FollowFriday @fgribreau @GeekFG"
579		// * example 2:
580		// preg_replace_callback("/(\\@[^\\s,\\.]*)/ig",function(matches){return
581		// matches[0].toLowerCase();},'#FollowFriday @FGRibreau @GeekFG');
582		// * returns 2: "#FollowFriday @fgribreau @geekfg"
583
584		limit = !limit ? -1 : limit;
585
586		var _check = pattern.substr(0, 1), _flag = pattern.substr(pattern
587				.lastIndexOf(_check) + 1), _pattern = pattern.substr(1, pattern
588				.lastIndexOf(_check) - 1), reg = new RegExp(_pattern, _flag), rs = null, res = [], x = 0, list = [], depth = "", ret = subject;
589
590		String.prototype.repeat = function(num) {
591			return new Array(num + 1).join(this);
592		};
593
594		// This may generate urls like "../test/../test"
595		if ( !this.page ) { this.page = ""; }
596		depth = this.page.substr(0, this.page.lastIndexOf("/") + 1);
597
598		if (limit === -1) {
599			var tmp = [];
600
601			do {
602				tmp = reg.exec(subject);
603				if (tmp !== null) {
604					res.push(tmp);
605				}
606			} while (tmp !== null && _flag.indexOf('g') !== -1);
607		} else {
608			res.push(reg.exec(subject));
609		}
610
611		for (x = res.length - 1; x > -1; x--) {// explore match
612			if (!list[res[x][0]]) {
613				ret = ret.replace(new RegExp(res[x][0], "g"), callback(res[x],
614						depth));
615				list[res[x][0]] = true;
616			}
617		}
618		return ret;
619	};
620
621	this.init = function(event) {
622		if (!event) {
623			var event = window.event;
624		}
625		if (event) {
626			event.cancelBubble = true;
627			event.returnValue = false;
628			if (event.stopPropagation) {
629				event.stopPropagation();
630			}
631			if (event.preventDefault) {
632				event.preventDefault();
633			}
634		}
635		this.event = event;
636	};
637
638	this.removeOldViewer();
639	this.displayContent(showContent, isImage, width, height);
640};
641
642(function($){
643	$(window).bind("message", function(event){
644
645		var data = event.data || event.originalEvent.data;
646		var source = event.source || event.originalEvent.source;
647		if (data != "getFrameContent") {
648			return;
649		}
650
651		try {
652			source.postMessage({
653				message : "frameContent",
654				body : jQuery('html').html()
655			}, "*");
656		} catch (e) {
657			alert("Fatal Exception! Could not load page via popupviewer.\n" + e);
658		}
659	});
660})(jQuery);
661
662var checkImageRoutine = function(inputImage) {
663
664	this.image = null;
665	this.counter = 500;
666	this.isFinished = false;
667
668	this.checkImages = function() {
669
670		var isOK = this.isImageOk();
671		if (!isOK && this.counter > 0) {
672			this.counter--;
673			return false;
674		}
675
676		if (isOK) {
677			this.isFinished = true;
678		}
679		return true;
680	};
681
682	this.isImageOk = function(img) {
683
684		if (this.isFinished) {
685			return true;
686		}
687
688		if (!img) {
689			img = this.image;
690		}
691		// During the onload event, IE correctly identifies any images
692		// that weren't downloaded as not complete. Others should too.
693		// Gecko-based browsers act like NS4 in that they imageflow this
694		// incorrectly: they always return true.
695		if (!img.complete) {
696			return false;
697		}
698
699		// However, they do have two very useful properties: naturalWidth
700		// and naturalHeight. These give the true size of the image. If
701		// it failed to load, either of these should be zero.
702		if (typeof img.naturalWidth != "undefined" && img.naturalWidth === 0) {
703			return false;
704		}
705
706		// No other way of checking: assume it's ok.
707		return true;
708	};
709
710	this.checkLoadImage = function(count, callback) {
711
712		if (!count || count === 0) {
713			if (callback && callback.error) {
714				callback.error();
715			}
716			return false;
717		}
718		if (!this.isImageOk()) {
719			var self = this;
720			setTimeout(function() {
721				self.checkLoadImage(count - 1, callback);
722			}, 100);
723			return;
724		}
725
726		if (callback && callback.finalize) {
727			callback.finalize();
728		}
729		return true;
730	};
731
732	this.finish = function() {
733		this.counter = 0;
734	};
735
736	this.image = inputImage;
737	this.image.onload = this.finish;
738	this.image.onabord = this.finish;
739};