Project

General

Profile

1
/**
2
 * SqueezeBox - Expandable Lightbox
3
 *
4
 * Allows to open various content as modal,
5
 * centered and animated box.
6
 *
7
 * Dependencies: MooTools 1.2
8
 *
9
 * Inspired by
10
 *  ... Lokesh Dhakar	- The original Lightbox v2
11
 *
12
 * @version		1.1 rc4
13
 *
14
 * @license		MIT-style license
15
 * @author		Harald Kirschner <mail [at] digitarald.de>
16
 * @copyright	Author
17
 */
18

    
19
var SqueezeBox = {
20

    
21
	presets: {
22
		onOpen: $empty,
23
		onClose: $empty,
24
		onUpdate: $empty,
25
		onResize: $empty,
26
		onMove: $empty,
27
		onShow: $empty,
28
		onHide: $empty,
29
		size: {x: 600, y: 450},
30
		sizeLoading: {x: 200, y: 150},
31
		marginInner: {x: 20, y: 20},
32
		marginImage: {x: 50, y: 75},
33
		handler: false,
34
		target: null,
35
		closable: true,
36
		closeBtn: true,
37
		zIndex: 65555,
38
		overlayOpacity: 0.7,
39
		classWindow: '',
40
		classOverlay: '',
41
		overlayFx: {},
42
		resizeFx: {},
43
		contentFx: {},
44
		parse: false, // 'rel'
45
		parseSecure: false,
46
		shadow: true,
47
		document: null,
48
		ajaxOptions: {}
49
	},
50

    
51
	initialize: function(presets) {
52
		if (this.options) return this;
53

    
54
		this.presets = $merge(this.presets, presets);
55
		this.doc = this.presets.document || document;
56
		this.options = {};
57
		this.setOptions(this.presets).build();
58
		this.bound = {
59
			window: this.reposition.bind(this, [null]),
60
			scroll: this.checkTarget.bind(this),
61
			close: this.close.bind(this),
62
			key: this.onKey.bind(this)
63
		};
64
		this.isOpen = this.isLoading = false;
65
		return this;
66
	},
67

    
68
	build: function() {
69
		this.overlay = new Element('div', {
70
			id: 'sbox-overlay',
71
			styles: {display: 'none', zIndex: this.options.zIndex}
72
		});
73
		this.win = new Element('div', {
74
			id: 'sbox-window',
75
			styles: {display: 'none', zIndex: this.options.zIndex + 2}
76
		});
77
		if (this.options.shadow) {
78
			if (Browser.Engine.webkit420) {
79
				this.win.setStyle('-webkit-box-shadow', '0 0 10px rgba(0, 0, 0, 0.7)');
80
			} else if (!Browser.Engine.trident4) {
81
				var shadow = new Element('div', {'class': 'sbox-bg-wrap'}).inject(this.win);
82
				var relay = function(e) {
83
					this.overlay.fireEvent('click', [e]);
84
				}.bind(this);
85
				['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'].each(function(dir) {
86
					new Element('div', {'class': 'sbox-bg sbox-bg-' + dir}).inject(shadow).addEvent('click', relay);
87
				});
88
			}
89
		}
90
		this.content = new Element('div', {id: 'sbox-content'}).inject(this.win);
91
		this.closeBtn = new Element('a', {id: 'sbox-btn-close', href: '#'}).inject(this.win);
92
		this.fx = {
93
			overlay: new Fx.Tween(this.overlay, $merge({
94
				property: 'opacity',
95
				onStart: Events.prototype.clearChain,
96
				duration: 250,
97
				link: 'cancel'
98
			}, this.options.overlayFx)).set(0),
99
			win: new Fx.Morph(this.win, $merge({
100
				onStart: Events.prototype.clearChain,
101
				unit: 'px',
102
				duration: 750,
103
				transition: Fx.Transitions.Quint.easeOut,
104
				link: 'cancel',
105
				unit: 'px'
106
			}, this.options.resizeFx)),
107
			content: new Fx.Tween(this.content, $merge({
108
				property: 'opacity',
109
				duration: 250,
110
				link: 'cancel'
111
			}, this.options.contentFx)).set(0)
112
		};
113
		$(this.doc.body).adopt(this.overlay, this.win);
114
	},
115

    
116
	assign: function(to, options) {
117
		return ($(to) || $$(to)).addEvent('click', function() {
118
			return !SqueezeBox.fromElement(this, options);
119
		});
120
	},
121
	
122
	open: function(subject, options) {
123
		this.initialize();
124

    
125
		if (this.element != null) this.trash();
126
		this.element = $(subject) || false;
127
		
128
		this.setOptions($merge(this.presets, options || {}));
129
		
130
		if (this.element && this.options.parse) {
131
			var obj = this.element.getProperty(this.options.parse);
132
			if (obj && (obj = JSON.decode(obj, this.options.parseSecure))) this.setOptions(obj);
133
		}
134
		this.url = ((this.element) ? (this.element.get('href')) : subject) || this.options.url || '';
135

    
136
		this.assignOptions();
137
		
138
		var handler = handler || this.options.handler;
139
		if (handler) return this.setContent(handler, this.parsers[handler].call(this, true));
140
		var ret = false;
141
		return this.parsers.some(function(parser, key) {
142
			var content = parser.call(this);
143
			if (content) {
144
				ret = this.setContent(key, content);
145
				return true;
146
			}
147
			return false;
148
		}, this);
149
	},
150
	
151
	fromElement: function(from, options) {
152
		return this.open(from, options);
153
	},
154

    
155
	assignOptions: function() {
156
		this.overlay.set('class', this.options.classOverlay);
157
		this.win.set('class', this.options.classWindow);
158
		if (Browser.Engine.trident4) this.win.addClass('sbox-window-ie6');
159
	},
160

    
161
	close: function(e) {
162
		var stoppable = ($type(e) == 'event');
163
		if (stoppable) e.stop();
164
		if (!this.isOpen || (stoppable && !$lambda(this.options.closable).call(this, e))) return this;
165
		this.fx.overlay.start(0).chain(this.toggleOverlay.bind(this));
166
		this.win.setStyle('display', 'none');
167
		this.fireEvent('onClose', [this.content]);
168
		this.trash();
169
		this.toggleListeners();
170
		this.isOpen = false;
171
		return this;
172
	},
173

    
174
	trash: function() {
175
		this.element = this.asset = null;
176
		this.content.empty();
177
		this.options = {};
178
		this.removeEvents().setOptions(this.presets).callChain();
179
	},
180

    
181
	onError: function() {
182
		this.asset = null;
183
		this.setContent('string', this.options.errorMsg || 'An error occurred');
184
	},
185

    
186
	setContent: function(handler, content) {
187
		if (!this.handlers[handler]) return false;
188
		this.content.className = 'sbox-content-' + handler;
189
		this.applyTimer = this.applyContent.delay(this.fx.overlay.options.duration, this, this.handlers[handler].call(this, content));
190
		if (this.overlay.retrieve('opacity')) return this;
191
		this.toggleOverlay(true);
192
		this.fx.overlay.start(this.options.overlayOpacity);
193
		return this.reposition();
194
	},
195

    
196
	applyContent: function(content, size) {
197
		if (!this.isOpen && !this.applyTimer) return;
198
		this.applyTimer = $clear(this.applyTimer);
199
		this.hideContent();
200
		if (!content) {
201
			this.toggleLoading(true);
202
		} else {
203
			if (this.isLoading) this.toggleLoading(false);
204
			this.fireEvent('onUpdate', [this.content], 20);
205
		}
206
		if (content) {
207
			if (['string', 'array'].contains($type(content))) this.content.set('html', content);
208
			else if (!this.content.hasChild(content)) this.content.adopt(content);
209
		}
210
		this.callChain();
211
		if (!this.isOpen) {
212
			this.toggleListeners(true);
213
			this.resize(size, true);
214
			this.isOpen = true;
215
			this.fireEvent('onOpen', [this.content]);
216
		} else {
217
			this.resize(size);
218
		}
219
	},
220

    
221
	resize: function(size, instantly) {
222
		this.showTimer = $clear(this.showTimer || null);
223
		var box = this.doc.getSize(), scroll = this.doc.getScroll();
224
		this.size = $merge((this.isLoading) ? this.options.sizeLoading : this.options.size, size);
225
		var to = {
226
			width: this.size.x,
227
			height: this.size.y,
228
			left: (scroll.x + (box.x - this.size.x - this.options.marginInner.x) / 2).toInt(),
229
			top: (scroll.y + (box.y - this.size.y - this.options.marginInner.y) / 2).toInt()
230
		};
231
		this.hideContent();
232
		if (!instantly) {
233
			this.fx.win.start(to).chain(this.showContent.bind(this));
234
		} else {
235
			this.win.setStyles(to).setStyle('display', '');
236
			this.showTimer = this.showContent.delay(50, this);
237
		}
238
		return this.reposition();
239
	},
240

    
241
	toggleListeners: function(state) {
242
		var fn = (state) ? 'addEvent' : 'removeEvent';
243
		this.closeBtn[fn]('click', this.bound.close);
244
		this.overlay[fn]('click', this.bound.close);
245
		this.doc[fn]('keydown', this.bound.key)[fn]('mousewheel', this.bound.scroll);
246
		this.doc.getWindow()[fn]('resize', this.bound.window)[fn]('scroll', this.bound.window);
247
	},
248

    
249
	toggleLoading: function(state) {
250
		this.isLoading = state;
251
		this.win[(state) ? 'addClass' : 'removeClass']('sbox-loading');
252
		if (state) this.fireEvent('onLoading', [this.win]);
253
	},
254

    
255
	toggleOverlay: function(state) {
256
		var full = this.doc.getSize().x;
257
		this.overlay.setStyle('display', (state) ? '' : 'none');
258
		this.doc.body[(state) ? 'addClass' : 'removeClass']('body-overlayed');
259
		if (state) {
260
			this.scrollOffset = this.doc.getWindow().getSize().x - full;
261
			this.doc.body.setStyle('margin-right', this.scrollOffset);
262
		} else {
263
			this.doc.body.setStyle('margin-right', '');
264
		}
265
	},
266

    
267
	showContent: function() {
268
		if (this.content.get('opacity')) this.fireEvent('onShow', [this.win]);
269
		this.fx.content.start(1);
270
	},
271

    
272
	hideContent: function() {
273
		if (!this.content.get('opacity')) this.fireEvent('onHide', [this.win]);
274
		this.fx.content.cancel().set(0);
275
	},
276

    
277
	onKey: function(e) {
278
		switch (e.key) {
279
			case 'esc': this.close(e);
280
			case 'up': case 'down': return false;
281
		}
282
	},
283

    
284
	checkTarget: function(e) {
285
		return this.content.hasChild(e.target);
286
	},
287

    
288
	reposition: function() {
289
		var size = this.doc.getSize(), scroll = this.doc.getScroll(), ssize = this.doc.getScrollSize();
290
		this.overlay.setStyles({
291
			width: ssize.x + 'px',
292
			height: ssize.y + 'px'
293
		});
294
		this.win.setStyles({
295
			left: (scroll.x + (size.x - this.win.offsetWidth) / 2 - this.scrollOffset).toInt() + 'px',
296
			top: (scroll.y + (size.y - this.win.offsetHeight) / 2).toInt() + 'px'
297
		});
298
		return this.fireEvent('onMove', [this.overlay, this.win]);
299
	},
300

    
301
	removeEvents: function(type){
302
		if (!this.$events) return this;
303
		if (!type) this.$events = null;
304
		else if (this.$events[type]) this.$events[type] = null;
305
		return this;
306
	},
307

    
308
	extend: function(properties) {
309
		return $extend(this, properties);
310
	},
311

    
312
	handlers: new Hash(),
313

    
314
	parsers: new Hash()
315

    
316
};
317

    
318
SqueezeBox.extend(new Events($empty)).extend(new Options($empty)).extend(new Chain($empty));
319

    
320
SqueezeBox.parsers.extend({
321

    
322
	image: function(preset) {
323
		return (preset || (/\.(?:jpg|png|gif)$/i).test(this.url)) ? this.url : false;
324
	},
325

    
326
	clone: function(preset) {
327
		if ($(this.options.target)) return $(this.options.target);
328
		if (this.element && !this.element.parentNode) return this.element;
329
		var bits = this.url.match(/#([\w-]+)$/);
330
		return (bits) ? $(bits[1]) : (preset ? this.element : false);
331
	},
332

    
333
	ajax: function(preset) {
334
		return (preset || (this.url && !(/^(?:javascript|#)/i).test(this.url))) ? this.url : false;
335
	},
336

    
337
	iframe: function(preset) {
338
		return (preset || this.url) ? this.url : false;
339
	},
340

    
341
	string: function(preset) {
342
		return true;
343
	}
344
});
345

    
346
SqueezeBox.handlers.extend({
347

    
348
	image: function(url) {
349
		var size, tmp = new Image();
350
		this.asset = null;
351
		tmp.onload = tmp.onabort = tmp.onerror = (function() {
352
			tmp.onload = tmp.onabort = tmp.onerror = null;
353
			if (!tmp.width) {
354
				this.onError.delay(10, this);
355
				return;
356
			}
357
			var box = this.doc.getSize();
358
			box.x -= this.options.marginImage.x;
359
			box.y -= this.options.marginImage.y;
360
			size = {x: tmp.width, y: tmp.height};
361
			for (var i = 2; i--;) {
362
				if (size.x > box.x) {
363
					size.y *= box.x / size.x;
364
					size.x = box.x;
365
				} else if (size.y > box.y) {
366
					size.x *= box.y / size.y;
367
					size.y = box.y;
368
				}
369
			}
370
			size.x = size.x.toInt();
371
			size.y = size.y.toInt();
372
			this.asset = $(tmp);
373
			tmp = null;
374
			this.asset.width = size.x;
375
			this.asset.height = size.y;
376
			this.applyContent(this.asset, size);
377
		}).bind(this);
378
		tmp.src = url;
379
		if (tmp && tmp.onload && tmp.complete) tmp.onload();
380
		return (this.asset) ? [this.asset, size] : null;
381
	},
382

    
383
	clone: function(el) {
384
		if (el) return el.clone();
385
		return this.onError();
386
	},
387

    
388
	adopt: function(el) {
389
		if (el) return el;
390
		return this.onError();
391
	},
392

    
393
	ajax: function(url) {
394
		var options = this.options.ajaxOptions || {};
395
		this.asset = new Request.HTML($merge({
396
			method: 'get',
397
			evalScripts: false
398
		}, this.options.ajaxOptions)).addEvents({
399
			onSuccess: function(resp) {
400
				this.applyContent(resp);
401
				if (options.evalScripts !== null && !options.evalScripts) $exec(this.asset.response.javascript);
402
				this.fireEvent('onAjax', [resp, this.asset]);
403
				this.asset = null;
404
			}.bind(this),
405
			onFailure: this.onError.bind(this)
406
		});
407
		this.asset.send.delay(10, this.asset, [{url: url}]);
408
	},
409

    
410
	iframe: function(url) {
411
		this.asset = new Element('iframe', $merge({
412
			src: url,
413
			frameBorder: 0,
414
			width: this.options.size.x,
415
			height: this.options.size.y
416
		}, this.options.iframeOptions));
417
		if (this.options.iframePreload) {
418
			this.asset.addEvent('load', function() {
419
				this.applyContent(this.asset.setStyle('display', ''));
420
			}.bind(this));
421
			this.asset.setStyle('display', 'none').inject(this.content);
422
			return false;
423
		}
424
		return this.asset;
425
	},
426

    
427
	string: function(str) {
428
		return str;
429
	}
430

    
431
});
432

    
433
SqueezeBox.handlers.url = SqueezeBox.handlers.ajax;
434
SqueezeBox.parsers.url = SqueezeBox.parsers.ajax;
435
SqueezeBox.parsers.adopt = SqueezeBox.parsers.clone;
(2-2/32)