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