xref: /plugin/popupviewer/script.js (revision 9b88ded8dd5c2b2a9290d96095db6d87b7e0389e)
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		$(self.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
537		return;
538	};
539
540	this.runAJAX = function(callback, options, errorCallback, url) {
541
542		var trackLink = url;
543		if (typeof url == "undefined") {
544			url = DOKU_BASE + 'lib/exe/ajax.php';
545		}
546
547		var success = function (data) {
548
549			// Google Ping
550			if ( typeof googleanalytics_trackLink != "undefined" ) {
551				googleanalytics_trackLink(trackLink);
552			}
553			if ( typeof callback == "function" ) {
554    			callback(data);
555			}
556		};
557
558		jQuery('#popupviewer_content').load(url, options, function( response, status, xhr ) {
559
560			if ( status == "error" ) {
561				// Retry
562				errorCallback(success);
563			} else {
564				success(response);
565			}
566
567		} );
568
569	};
570
571	this.preg_replace_callback = function(pattern, callback, subject, limit) {
572		// Perform a regular expression search and replace using a callback
573		//
574		// discuss at: http://geekfg.net/
575		// + original by: Francois-Guillaume Ribreau (http://fgribreau)
576		// * example 1:
577		// preg_replace_callback("/(\\@[^\\s,\\.]*)/ig",function(matches){return
578		// matches[0].toLowerCase();},'#FollowFriday @FGRibreau @GeekFG',1);
579		// * returns 1: "#FollowFriday @fgribreau @GeekFG"
580		// * example 2:
581		// preg_replace_callback("/(\\@[^\\s,\\.]*)/ig",function(matches){return
582		// matches[0].toLowerCase();},'#FollowFriday @FGRibreau @GeekFG');
583		// * returns 2: "#FollowFriday @fgribreau @geekfg"
584
585		limit = !limit ? -1 : limit;
586
587		var _check = pattern.substr(0, 1), _flag = pattern.substr(pattern
588				.lastIndexOf(_check) + 1), _pattern = pattern.substr(1, pattern
589				.lastIndexOf(_check) - 1), reg = new RegExp(_pattern, _flag), rs = null, res = [], x = 0, list = [], depth = "", ret = subject;
590
591		String.prototype.repeat = function(num) {
592			return new Array(num + 1).join(this);
593		};
594
595		// This may generate urls like "../test/../test"
596		if ( !this.page ) { this.page = ""; }
597		depth = this.page.substr(0, this.page.lastIndexOf("/") + 1);
598
599		if (limit === -1) {
600			var tmp = [];
601
602			do {
603				tmp = reg.exec(subject);
604				if (tmp !== null) {
605					res.push(tmp);
606				}
607			} while (tmp !== null && _flag.indexOf('g') !== -1);
608		} else {
609			res.push(reg.exec(subject));
610		}
611
612		for (x = res.length - 1; x > -1; x--) {// explore match
613			if (!list[res[x][0]]) {
614				ret = ret.replace(new RegExp(res[x][0], "g"), callback(res[x],
615						depth));
616				list[res[x][0]] = true;
617			}
618		}
619		return ret;
620	};
621
622	this.init = function(event) {
623		if (!event) {
624			var event = window.event;
625		}
626		if (event) {
627			event.cancelBubble = true;
628			event.returnValue = false;
629			if (event.stopPropagation) {
630				event.stopPropagation();
631			}
632			if (event.preventDefault) {
633				event.preventDefault();
634			}
635		}
636		this.event = event;
637	};
638
639	this.removeOldViewer();
640	this.displayContent(showContent, isImage, width, height);
641};
642
643jQuery(function() {
644	jQuery(window).bind("message", function(event){
645
646		var data = event.data || event.originalEvent.data;
647		var source = event.source || event.originalEvent.source;
648		if (data != "getFrameContent") {
649			return;
650		}
651
652		try {
653			source.postMessage({
654				message : "frameContent",
655				body : jQuery('html').html()
656			}, "*");
657		} catch (e) {
658			alert("Fatal Exception! Could not load page via popupviewer.\n" + e);
659		}
660	});
661});
662
663var checkImageRoutine = function(inputImage) {
664
665	this.image = null;
666	this.counter = 500;
667	this.isFinished = false;
668
669	this.checkImages = function() {
670
671		var isOK = this.isImageOk();
672		if (!isOK && this.counter > 0) {
673			this.counter--;
674			return false;
675		}
676
677		if (isOK) {
678			this.isFinished = true;
679		}
680		return true;
681	};
682
683	this.isImageOk = function(img) {
684
685		if (this.isFinished) {
686			return true;
687		}
688
689		if (!img) {
690			img = this.image;
691		}
692		// During the onload event, IE correctly identifies any images
693		// that weren't downloaded as not complete. Others should too.
694		// Gecko-based browsers act like NS4 in that they imageflow this
695		// incorrectly: they always return true.
696		if (!img.complete) {
697			return false;
698		}
699
700		// However, they do have two very useful properties: naturalWidth
701		// and naturalHeight. These give the true size of the image. If
702		// it failed to load, either of these should be zero.
703		if (typeof img.naturalWidth != "undefined" && img.naturalWidth === 0) {
704			return false;
705		}
706
707		// No other way of checking: assume it's ok.
708		return true;
709	};
710
711	this.checkLoadImage = function(count, callback) {
712
713		if (!count || count === 0) {
714			if (callback && callback.error) {
715				callback.error();
716			}
717			return false;
718		}
719		if (!this.isImageOk()) {
720			var self = this;
721			setTimeout(function() {
722				self.checkLoadImage(count - 1, callback);
723			}, 100);
724			return;
725		}
726
727		if (callback && callback.finalize) {
728			callback.finalize();
729		}
730		return true;
731	};
732
733	this.finish = function() {
734		this.counter = 0;
735	};
736
737	this.image = inputImage;
738	this.image.onload = this.finish;
739	this.image.onabord = this.finish;
740};