/**
* Lightbox feature for RightJS
* http://rightjs.org/ui/lightbox
*
* Copyright (C) 2009-2010 Nikolay Nemshilov
*/
var Lightbox = RightJS.Lightbox = (function(document, RightJS) {
/**
* This module defines the basic widgets constructor
* it creates an abstract proxy with the common functionality
* which then we reuse and override in the actual widgets
*
* Copyright (C) 2010 Nikolay Nemshilov
*/
/**
* The filenames to include
*
* Copyright (C) 2010 Nikolay Nemshilov
*/
var R = RightJS,
$ = RightJS.$,
$$ = RightJS.$$,
$w = RightJS.$w,
$E = RightJS.$E,
$ext = RightJS.$ext,
Xhr = RightJS.Xhr,
Class = RightJS.Class,
Object = RightJS.Object,
Wrapper = RightJS.Wrapper,
Element = RightJS.Element,
Browser = RightJS.Browser;
// IE6 doesn't support position:fixed so it needs a crunch
Browser.IE6 = Browser.OLD && navigator.userAgent.indexOf("MSIE 6") > 0;
/**
* The widget units constructor
*
* @param String tag-name or Object methods
* @param Object methods
* @return Widget wrapper
*/
function Widget(tag_name, methods) {
if (!methods) {
methods = tag_name;
tag_name = 'DIV';
}
/**
* An Abstract Widget Unit
*
* Copyright (C) 2010 Nikolay Nemshilov
*/
var AbstractWidget = new RightJS.Wrapper(RightJS.Element.Wrappers[tag_name] || RightJS.Element, {
/**
* The common constructor
*
* @param Object options
* @param String optional tag name
* @return void
*/
initialize: function(key, options) {
this.key = key;
var args = [{'class': 'rui-' + key}];
// those two have different constructors
if (!(this instanceof RightJS.Input || this instanceof RightJS.Form)) {
args.unshift(tag_name);
}
this.$super.apply(this, args);
if (RightJS.isString(options)) {
options = RightJS.$(options);
}
// if the options is another element then
// try to dynamically rewrap it with our widget
if (options instanceof RightJS.Element) {
this._ = options._;
if ('$listeners' in options) {
options.$listeners = options.$listeners;
}
options = {};
}
this.setOptions(options, this);
return this;
},
// protected
/**
* Catches the options
*
* @param Object user-options
* @param Element element with contextual options
* @return void
*/
setOptions: function(options, element) {
element = element || this;
RightJS.Options.setOptions.call(this,
RightJS.Object.merge(options, eval("("+(
element.get('data-'+ this.key) || '{}'
)+")"))
);
return this;
}
});
/**
* Creating the actual widget class
*
*/
var Klass = new RightJS.Wrapper(AbstractWidget, methods);
// creating the widget related shortcuts
RightJS.Observer.createShortcuts(Klass.prototype, Klass.EVENTS || []);
return Klass;
}
/**
* A shared module to create textual spinners
*
* Copyright (C) 2010 Nikolay Nemshilov
*/
var Spinner = new RightJS.Wrapper(RightJS.Element, {
/**
* Constructor
*
* @param Number optional spinner size (4 by default)
* @return void
*/
initialize: function(size) {
this.$super('div', {'class': 'rui-spinner'});
this.dots = [];
for (var i=0; i < (size || 4); i++) {
this.dots.push(new RightJS.Element('div'));
}
this.dots[0].addClass('glowing');
this.insert(this.dots);
RightJS(this.shift).bind(this).periodical(300);
},
/**
* Shifts the spinner elements
*
* @return void
*/
shift: function() {
if (this.visible()) {
var dot = this.dots.pop();
this.dots.unshift(dot);
this.insert(dot, 'top');
}
}
});
/**
* The lightbox widget
*
* Copyright (C) 2009-2010 Nikolay Nemshilov
*/
var Lightbox = new Widget({
extend: {
version: '2.0.0',
EVENTS: $w('show hide load'),
Options: {
fxName: 'fade',
fxDuration: 100,
group: null, // a group marker
hideOnEsc: true,
hideOnOutClick: true,
showCloseButton: true,
cssRule: "a[data-lightbox]", // all lightbox links css-rule
// video links default size
mediaWidth: 425,
mediaHeight: 350
},
i18n: {
Close: 'Close',
Prev: 'Previous Image',
Next: 'Next Image'
},
// the supported image-urls regexp
Images: /\.(jpg|jpeg|gif|png|bmp)/,
// media content sources
Medias: [
[/(http:\/\/.*?youtube\.[a-z]+)\/watch\?v=([^&]+)/, '$1/v/$2', 'swf'],
[/(http:\/\/video.google.com)\/videoplay\?docid=([^&]+)/, '$1/googleplayer.swf?docId=$2', 'swf'],
[/(http:\/\/vimeo\.[a-z]+)\/([0-9]+).*?/, '$1/moogaloop.swf?clip_id=$2', 'swf']
]
},
/**
* basic constructor
*
* @param Object options override
* @param Element optional options holder
* @return void
*/
initialize: function(options, context) {
this
.$super('lightbox', {})
.setOptions(options, context)
.insert([
this.locker = new Locker(this.options),
this.dialog = new Dialog(this.options)
])
.on({
close: this._close,
next: this._next,
prev: this._prev
});
},
/**
* Extracting the rel="lightboux[groupname]" attributes
*
* @param Object options
* @param Element link with options
* @return Dialog this
*/
setOptions: function(options, context) {
this.$super(options, context);
if (context) {
var rel = context.get('rel');
if (rel && (rel = rel.match(/lightbox\[(.+?)\]/))) {
this.options.group = rel[1];
}
}
return this;
},
/**
* Sets the popup's title
*
* @param mixed string or element or somethin'
* @return Lighbox self
*/
setTitle: function(text) {
this.dialog.setTitle(text);
return this;
},
/**
* Shows the lightbox
*
* @param String/Array... content
* @return Lightbox this
*/
show: function(content) {
return this._showAnd(function() {
this.dialog.show(content, !content);
});
},
/**
* Hides the lightbox
*
* @return Lightbox this
*/
hide: function() {
Lightbox.current = null;
return this.$super(this.options.fxName, {
duration: this.options.fxDuration/3,
onFinish: R(function() {
this.fire('hide');
this.remove();
}).bind(this)
});
},
/**
* Loads up the data from url or a link
*
* @param String address or a link element
* @param Object Xhr options
* @return Lightbox this
*/
load: function(link, options) {
return this._showAnd(function() {
this.dialog.load(link, options);
});
},
/**
* Resizes the content block to the given size
*
* @param Hash size
* @return Lightbox this
*/
resize: function(size) {
this.dialog.resize(size);
return this;
},
// protected
// handles the 'close' event
_close: function(event) {
event.stop();
this.hide();
},
// handles the 'prev' event
_prev: function(event) {
event.stop();
Pager.prev();
},
// handles the 'next' event
_next: function(event) {
event.stop();
Pager.next();
},
// shows the lightbox element and then calls back
_showAnd: function(callback) {
if (Lightbox.current !== this) {
Lightbox.current = this;
// hidding all the hanging around lightboxes
$$('div.rui-lightbox').each('remove');
this.insertTo(document.body);
this.dialog.show('', true);
if (Browser.OLD) { // IE's get screwed by the transparency tricks
this.reposition();
Element.prototype.show.call(this);
callback.call(this);
} else {
this.setStyle('display:none');
Element.prototype.show.call(this, this.options.fxName, {
duration: this.options.fxDuration/2,
onFinish: R(function() {
callback.call(this);
this.fire('show');
}).bind(this)
});
}
} else {
callback.call(this);
}
return this;
},
// manually repositioning under IE6 browser
reposition: function() {
if (Browser.IE6) {
var win = $(window);
this.setStyle({
top: win.scrolls().y + 'px',
width: win.size().x + 'px',
height: win.size().y + 'px',
position: "absolute"
});
}
}
});
/**
* The class level interface
*
* @copyright (C) 2009 Nikolay Nemshilov
*/
Lightbox.extend({
hide: function() {
if (Lightbox.current) {
Lightbox.current.hide();
}
},
show: function() {
return this.inst('show', arguments);
},
load: function() {
return this.inst('load', arguments);
},
// private
inst: function(name, args) {
var inst = new Lightbox();
return inst[name].apply(inst, args);
}
});
/**
* Lightbox background locker element
*
* Copyright (C) 2010 Nikolay Nemshilov
*/
var Locker = new Wrapper(Element, {
initialize: function(options) {
this.$super('div', {'class': 'rui-lightbox-locker'});
if (options.hideOnOutClick) {
this.onClick('fire', 'close');
}
}
});
/**
* The dialog element wrapper
*
* Copyright (C) 2010 Nikolay Nemshilov
*/
var Dialog = new Wrapper(Element, {
/**
* Constructor
*
* @param Object options
* @return void
*/
initialize: function(options) {
var i18n = Lightbox.i18n;
this.options = options;
this.$super('div', {'class': 'rui-lightbox-dialog'});
// building up the
this.insert([
this.title = $E('div', {'class': 'rui-lightbox-title'}),
$E('div', {'class': 'rui-lightbox-body'}).insert(
$E('div', {'class': 'rui-lightbox-body-inner'}).insert([
this.locker = $E('div', {'class': 'rui-lightbox-body-locker'}).insert(new Spinner(4)),
this.scroller = $E('div', {'class': 'rui-lightbox-scroller'}).insert(
this.content = $E('div', {'class': 'rui-lightbox-content'})
)
])
),
$E('div', {'class': 'rui-lightbox-navigation'}).insert([
this.closeButton = $E('div', {'class': 'close', html: '×', title: i18n.Close}).onClick('fire', 'close'),
this.prevLink = $E('div', {'class': 'prev', html: '←', title: i18n.Prev}).onClick('fire', 'prev'),
this.nextLink = $E('div', {'class': 'next', html: '→', title: i18n.Next}).onClick('fire', 'next')
])
]);
// presetting the navigation state
this.prevLink.hide();
this.nextLink.hide();
if (!options.showCloseButton) {
this.closeButton.hide();
}
},
/**
* Sets the dialogue caption
*
* @param String title
* @return Dialog this
*/
setTitle: function(title) {
this.title.update(title||'');
},
/**
* Nicely resize the dialog box
*
* @param Object the end size
* @param Boolean use fx (false by default)
* @return Dialog this
*/
resize: function(end_size, with_fx) {
var win_size = this.parent().size(),
cur_size = this.scroller.size(),
cur_top = (win_size.y - this.size().y)/2,
dlg_diff = this.size().x - cur_size.x; // <- use for IE6 fixes
if (end_size) {
// getting the actual end-size
end_size = this.scroller.setStyle(end_size).size();
this.scroller.setStyle({
width: cur_size.x + 'px',
height: cur_size.y + 'px'
});
} else {
// using the content block size
end_size = this.content.size();
}
// checking the constraints
var threshold = 100; // px
if ((end_size.x + threshold) > win_size.x) { end_size.x = win_size.x - threshold; }
if ((end_size.y + threshold) > win_size.y) { end_size.y = win_size.y - threshold; }
// the actual resize and reposition
var end_top = (cur_top * 2 + cur_size.y - end_size.y) / 2;
var dialog = this._.style, content = this.scroller._.style;
if (RightJS.Fx && with_fx && (end_size.x != cur_size.x || end_size.y != cur_size.y)) {
$ext(new RightJS.Fx(this, {duration: this.options.fxDuration, transition: 'Lin'}), {
render: function(delta) {
content.width = (cur_size.x + (end_size.x - cur_size.x) * delta) + 'px';
content.height = (cur_size.y + (end_size.y - cur_size.y) * delta) + 'px';
dialog.top = (cur_top + (end_top - cur_top) * delta) + 'px';
if (Browser.IE6) {
dialog.width = (dlg_diff + cur_size.y + (end_size.y - cur_size.y) * delta) + 'px';
}
}
}).onFinish(R(this.unlock).bind(this)).start();
} else {
// no-fx direct assignment
content.width = end_size.x + 'px';
content.height = end_size.y + 'px';
dialog.top = end_top + 'px';
if (Browser.IE6) {
dialog.width = (dlg_diff + end_size.x) + 'px';
}
if (!this.request) { this.unlock(); }
}
return this;
},
/**
* Shows the content
*
* @param mixed content String/Element/Array and so one
* @return Dialog this
*/
show: function(content, no_fx) {
this.content.update(content || '');
this.resize(null, !no_fx);
},
/**
* Loads up the data from the link
*
* @param mixed String url address or a link element
* @param Object xhr-options
* @return void
*/
load: function(url, options) {
if (url instanceof Element) {
this.setTitle(url.get('title'));
url = url.get('href');
}
Pager.show(this, url);
this.lock().cancel();
// defined in the loader.js file
this.request = new Loader(url, options, R(function(content, no_fx) {
this.request = null;
this.show(content, no_fx);
}).bind(this));
return this.resize(); // the look might be changed for a media-type
},
/**
* Cancels a currently loading request
*
* @return Dialog this
*/
cancel: function() {
if (this.request) {
this.request.cancel();
}
return this;
},
/**
* Shows the loading lock
*
* @return Dialog this
*/
lock: function() {
this.locker.setStyle('opacity:1');
return this;
},
/**
* Hides the loading lock
*
* @return Dialog this
*/
unlock: function() {
this.locker.morph({opacity: 0}, {
duration: this.options.fxDuration * 2/3
});
return this;
}
});
/**
* Xhr/images/medias loading module
*
* Copyright (C) 2009-2010 Nikolay Nemshilov
*/
var Loader = new Class({
/**
* Constructor
*
* @param String url address
* @param Object Xhr options
* @param Function on-finish callback
*/
initialize: function(url, options, on_finish) {
// adjusting the dialog look for different media-types
if (this.isImage(url, on_finish)) {
Lightbox.current.addClass('rui-lightbox-image');
} else if (this.isMedia(url, on_finish)) {
Lightbox.current.addClass('rui-lightbox-media');
} else {
this.xhr = new Xhr(url,
Object.merge({method: 'get'}, options)
).onComplete(function() {
on_finish(this.text);
}).send();
}
},
/**
* Cancels the request
*
* @return Loader this
*/
cancel: function() {
if (this.xhr) {
this.xhr.cancel();
} else if (this.img) {
this.img.onload = function() {};
}
},
// protected
// tries to initialize it as an image loading
isImage: function(url, on_finish) {
if (url.match(Lightbox.Images)) {
var img = this.img = $E('img')._;
img.onload = function() {
on_finish(img);
};
img.src = url;
return true;
}
},
// tries to initialize it as a flash-element
isMedia: function(url, on_finish) {
var media = R(Lightbox.Medias).map(function(desc) {
return url.match(desc[0]) ? this.buildEmbed(
url.replace(desc[0], desc[1]), desc[2]) : null;
}, this).compact()[0];
if (media) {
on_finish(media, true);
return true;
}
},
// builds an embedded media block
buildEmbed: function(url, type) {
var media_types = {
swf: [
'D27CDB6E-AE6D-11cf-96B8-444553540000',
'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0',
'application/x-shockwave-flash'
]
},
options = Lightbox.current ? Lightbox.current.options : Lightbox.Options,
sizes = ' width="'+ options.mediaWidth + '" height="'+ options.mediaHeight + '"';
return '';
}
});
/**
* Processes the link-groups showing things in a single Lightbox
*
* Copyright (C) 2010 Nikolay Nemshilov
*/
var Pager = {
/**
* Checks and shows the pager links on the dialog
*
* @param Dialog dialog
* @param String url-address
* @return void
*/
show: function(dialog, url) {
if (dialog.options.group) {
this.dialog = dialog;
this.links = this.find(dialog.options.group);
this.link = this.links.first(function(link) {
return link.get('href') === url;
});
var index = this.links.indexOf(this.link), size = this.links.length;
dialog.prevLink[size && index > 0 ? 'show' : 'hide']();
dialog.nextLink[size && index < size - 1 ? 'show' : 'hide']();
} else {
this.dialog = null;
}
},
/**
* Shows the prev link
*
* @return void
*/
prev: function() {
if (this.dialog && !this.timer) {
var id = this.links.indexOf(this.link),
link = this.links[id - 1];
if (link) {
this.dialog.load(link);
this.timeout();
}
}
},
/**
* Shows the next link
*
* @return void
*/
next: function() {
if (this.dialog && !this.timer) {
var id = this.links.indexOf(this.link),
link = this.links[id + 1];
if (link) {
this.dialog.load(link);
this.timeout();
}
}
},
// private
// finding the links list
find: function(group) {
return $$('a').filter(function(link) {
var data = link.get('data-lightbox');
var rel = link.get('rel');
return (data && eval("("+ data + ")").group === group) ||
(rel && rel.indexOf('lightbox['+ group + ']') > -1);
});
},
// having a little nap to prevent ugly quick scrolling
timeout: function() {
this.timer = R(function() {
Pager.timer = null;
}).delay(300);
}
};
/**
* document level hooks
*
* Copyright (C) 2009-2010 Nikolay Nemshilov
*/
$(document).on({
/**
* Catches clicks on the target links
*
* @param Event click
* @return void
*/
click: function(event) {
var target = event.find(Lightbox.Options.cssRule) || event.find('a[rel^=lightbox]');
if (target) {
event.stop();
new Lightbox({}, target).load(target);
}
},
/**
* Catches the mousewheel event and tries to scroll
* the list of objects on the lightbox
*
* @param Event mousewheel
* @return void
*/
mousewheel: function(event) {
if (Lightbox.current) {
event.stop();
Lightbox.current.fire((event._.detail || -event._.wheelDelta) < 0 ? 'prev' : 'next');
}
},
/**
* Handles the navigation form a keyboard
*
* @param Event keydown
* @return void
*/
keydown: function(event) {
var lightbox = Lightbox.current, name = ({
27: 'close', // Esc
33: 'prev', // PageUp
37: 'prev', // Left
38: 'prev', // Up
39: 'next', // Right
40: 'next', // Down
34: 'next' // PageDown
})[event.keyCode];
if (lightbox && name) {
if (name !== 'close' || lightbox.options.hideOnEsc) {
event.stop();
lightbox.fire(name);
}
}
}
});
$(window).on({
resize: function() {
if (Lightbox.current) {
Lightbox.current.reposition();
Lightbox.current.dialog.resize();
}
},
scroll: function(event) {
if (Lightbox.current && Browser.IE6) {
Lightbox.current.reposition();
}
}
});
document.write("");
return Lightbox;
})(document, RightJS);