__PackagedJavaScriptFile = '/33cfeda57f832a09112cf1a864e6757187cfeff7-350.cache.js';
/*  Prototype JavaScript framework, version 1.6.0.2
*  (c) 2005-2008 Sam Stephenson
*
*  Prototype is freely distributable under the terms of an MIT-style license.
*  For details, see the Prototype web site: http://www.prototypejs.org/
*
*--------------------------------------------------------------------------*/
var Prototype = {
Version: '1.6.0.2',
Browser: {
IE:     !!(window.attachEvent && !window.opera),
Opera:  !!window.opera,
WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
},
BrowserFeatures: {
XPath: !!document.evaluate,
ElementExtensions: !!window.HTMLElement,
SpecificElementExtensions:
document.createElement('div').__proto__ &&
document.createElement('div').__proto__ !==
document.createElement('form').__proto__
},
ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
emptyFunction: function() { },
K: function(x) { return x }
};
if (Prototype.Browser.MobileSafari)
Prototype.BrowserFeatures.SpecificElementExtensions = false;
/* Based on Alex Arnell's inheritance implementation. */
var Class = {
create: function() {
var parent = null, properties = $A(arguments);
if (Object.isFunction(properties[0]))
parent = properties.shift();
function klass() {
this.initialize.apply(this, arguments);
}
Object.extend(klass, Class.Methods);
klass.superclass = parent;
klass.subclasses = [];
if (parent) {
var subclass = function() { };
subclass.prototype = parent.prototype;
klass.prototype = new subclass;
parent.subclasses.push(klass);
}
for (var i = 0; i < properties.length; i++)
klass.addMethods(properties[i]);
if (!klass.prototype.initialize)
klass.prototype.initialize = Prototype.emptyFunction;
klass.prototype.constructor = klass;
return klass;
}
};
Class.Methods = {
addMethods: function(source) {
var ancestor   = this.superclass && this.superclass.prototype;
var properties = Object.keys(source);
if (!Object.keys({ toString: true }).length)
properties.push("toString", "valueOf");
for (var i = 0, length = properties.length; i < length; i++) {
var property = properties[i], value = source[property];
if (ancestor && Object.isFunction(value) &&
value.argumentNames().first() == "$super") {
var method = value, value = Object.extend((function(m) {
return function() { return ancestor[m].apply(this, arguments) };
})(property).wrap(method), {
valueOf:  function() { return method },
toString: function() { return method.toString() }
});
}
this.prototype[property] = value;
}
return this;
}
};
var Abstract = { };
Object.extend = function(destination, source) {
for (var property in source)
destination[property] = source[property];
return destination;
};
Object.extend(Object, {
inspect: function(object) {
try {
if (Object.isUndefined(object)) return 'undefined';
if (object === null) return 'null';
return object.inspect ? object.inspect() : String(object);
} catch (e) {
if (e instanceof RangeError) return '...';
throw e;
}
},
toJSON: function(object) {
var type = typeof object;
switch (type) {
case 'undefined':
case 'function':
case 'unknown': return;
case 'boolean': return object.toString();
}
if (object === null) return 'null';
if (object.toJSON) return object.toJSON();
if (Object.isElement(object)) return;
var results = [];
for (var property in object) {
var value = Object.toJSON(object[property]);
if (!Object.isUndefined(value))
results.push(property.toJSON() + ': ' + value);
}
return '{' + results.join(', ') + '}';
},
toQueryString: function(object) {
return $H(object).toQueryString();
},
toHTML: function(object) {
return object && object.toHTML ? object.toHTML() : String.interpret(object);
},
keys: function(object) {
var keys = [];
for (var property in object)
keys.push(property);
return keys;
},
values: function(object) {
var values = [];
for (var property in object)
values.push(object[property]);
return values;
},
clone: function(object) {
return Object.extend({ }, object);
},
isElement: function(object) {
return object && object.nodeType == 1;
},
isArray: function(object) {
return object != null && typeof object == "object" &&
'splice' in object && 'join' in object;
},
isHash: function(object) {
return object instanceof Hash;
},
isFunction: function(object) {
return typeof object == "function";
},
isString: function(object) {
return typeof object == "string";
},
isNumber: function(object) {
return typeof object == "number";
},
isUndefined: function(object) {
return typeof object == "undefined";
}
});
Object.extend(Function.prototype, {
argumentNames: function() {
var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
return names.length == 1 && !names[0] ? [] : names;
},
bind: function() {
if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
var __method = this, args = $A(arguments), object = args.shift();
return function() {
return __method.apply(object, args.concat($A(arguments)));
}
},
bindAsEventListener: function() {
var __method = this, args = $A(arguments), object = args.shift();
return function(event) {
return __method.apply(object, [event || window.event].concat(args));
}
},
curry: function() {
if (!arguments.length) return this;
var __method = this, args = $A(arguments);
return function() {
return __method.apply(this, args.concat($A(arguments)));
}
},
delay: function() {
var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
return window.setTimeout(function() {
return __method.apply(__method, args);
}, timeout);
},
wrap: function(wrapper) {
var __method = this;
return function() {
return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
}
},
methodize: function() {
if (this._methodized) return this._methodized;
var __method = this;
return this._methodized = function() {
return __method.apply(null, [this].concat($A(arguments)));
};
}
});
Function.prototype.defer = Function.prototype.delay.curry(0.01);
Date.prototype.toJSON = function() {
return '"' + this.getUTCFullYear() + '-' +
(this.getUTCMonth() + 1).toPaddedString(2) + '-' +
this.getUTCDate().toPaddedString(2) + 'T' +
this.getUTCHours().toPaddedString(2) + ':' +
this.getUTCMinutes().toPaddedString(2) + ':' +
this.getUTCSeconds().toPaddedString(2) + 'Z"';
};
var Try = {
these: function() {
var returnValue;
for (var i = 0, length = arguments.length; i < length; i++) {
var lambda = arguments[i];
try {
returnValue = lambda();
break;
} catch (e) { }
}
return returnValue;
}
};
RegExp.prototype.match = RegExp.prototype.test;
RegExp.escape = function(str) {
return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};
/*--------------------------------------------------------------------------*/
var PeriodicalExecuter = Class.create({
initialize: function(callback, frequency) {
this.callback = callback;
this.frequency = frequency;
this.currentlyExecuting = false;
this.registerCallback();
},
registerCallback: function() {
this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
},
execute: function() {
this.callback(this);
},
stop: function() {
if (!this.timer) return;
clearInterval(this.timer);
this.timer = null;
},
onTimerEvent: function() {
if (!this.currentlyExecuting) {
try {
this.currentlyExecuting = true;
this.execute();
} finally {
this.currentlyExecuting = false;
}
}
}
});
Object.extend(String, {
interpret: function(value) {
return value == null ? '' : String(value);
},
specialChar: {
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'\\': '\\\\'
}
});
Object.extend(String.prototype, {
gsub: function(pattern, replacement) {
var result = '', source = this, match;
replacement = arguments.callee.prepareReplacement(replacement);
while (source.length > 0) {
if (match = source.match(pattern)) {
result += source.slice(0, match.index);
result += String.interpret(replacement(match));
source  = source.slice(match.index + match[0].length);
} else {
result += source, source = '';
}
}
return result;
},
sub: function(pattern, replacement, count) {
replacement = this.gsub.prepareReplacement(replacement);
count = Object.isUndefined(count) ? 1 : count;
return this.gsub(pattern, function(match) {
if (--count < 0) return match[0];
return replacement(match);
});
},
scan: function(pattern, iterator) {
this.gsub(pattern, iterator);
return String(this);
},
truncate: function(length, truncation) {
length = length || 30;
truncation = Object.isUndefined(truncation) ? '...' : truncation;
return this.length > length ?
this.slice(0, length - truncation.length) + truncation : String(this);
},
strip: function() {
return this.replace(/^\s+/, '').replace(/\s+$/, '');
},
stripTags: function() {
return this.replace(/<\/?[^>]+>/gi, '');
},
stripScripts: function() {
return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
},
extractScripts: function() {
var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
return (this.match(matchAll) || []).map(function(scriptTag) {
return (scriptTag.match(matchOne) || ['', ''])[1];
});
},
evalScripts: function() {
return this.extractScripts().map(function(script) { return eval(script) });
},
escapeHTML: function() {
var self = arguments.callee;
self.text.data = this;
return self.div.innerHTML;
},
unescapeHTML: function() {
var div = new Element('div');
div.innerHTML = this.stripTags();
return div.childNodes[0] ? (div.childNodes.length > 1 ?
$A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
div.childNodes[0].nodeValue) : '';
},
toQueryParams: function(separator) {
var match = this.strip().match(/([^?#]*)(#.*)?$/);
if (!match) return { };
return match[1].split(separator || '&').inject({ }, function(hash, pair) {
if ((pair = pair.split('='))[0]) {
var key = decodeURIComponent(pair.shift());
var value = pair.length > 1 ? pair.join('=') : pair[0];
if (value != undefined) value = decodeURIComponent(value);
if (key in hash) {
if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
hash[key].push(value);
}
else hash[key] = value;
}
return hash;
});
},
toArray: function() {
return this.split('');
},
succ: function() {
return this.slice(0, this.length - 1) +
String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
},
times: function(count) {
return count < 1 ? '' : new Array(count + 1).join(this);
},
camelize: function() {
var parts = this.split('-'), len = parts.length;
if (len == 1) return parts[0];
var camelized = this.charAt(0) == '-'
? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
: parts[0];
for (var i = 1; i < len; i++)
camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
return camelized;
},
capitalize: function() {
return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
},
underscore: function() {
return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
},
dasherize: function() {
return this.gsub(/_/,'-');
},
inspect: function(useDoubleQuotes) {
var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
var character = String.specialChar[match[0]];
return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
});
if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
return "'" + escapedString.replace(/'/g, '\\\'') + "'";
},
toJSON: function() {
return this.inspect(true);
},
unfilterJSON: function(filter) {
return this.sub(filter || Prototype.JSONFilter, '#{1}');
},
isJSON: function() {
var str = this;
if (str.blank()) return false;
str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
},
evalJSON: function(sanitize) {
var json = this.unfilterJSON();
try {
if (!sanitize || json.isJSON()) return eval('(' + json + ')');
} catch (e) { }
throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
},
include: function(pattern) {
return this.indexOf(pattern) > -1;
},
startsWith: function(pattern) {
return this.indexOf(pattern) === 0;
},
endsWith: function(pattern) {
var d = this.length - pattern.length;
return d >= 0 && this.lastIndexOf(pattern) === d;
},
empty: function() {
return this == '';
},
blank: function() {
return /^\s*$/.test(this);
},
interpolate: function(object, pattern) {
return new Template(this, pattern).evaluate(object);
}
});
if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
escapeHTML: function() {
return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
},
unescapeHTML: function() {
return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
}
});
String.prototype.gsub.prepareReplacement = function(replacement) {
if (Object.isFunction(replacement)) return replacement;
var template = new Template(replacement);
return function(match) { return template.evaluate(match) };
};
String.prototype.parseQuery = String.prototype.toQueryParams;
Object.extend(String.prototype.escapeHTML, {
div:  document.createElement('div'),
text: document.createTextNode('')
});
with (String.prototype.escapeHTML) div.appendChild(text);
var Template = Class.create({
initialize: function(template, pattern) {
this.template = template.toString();
this.pattern = pattern || Template.Pattern;
},
evaluate: function(object) {
if (Object.isFunction(object.toTemplateReplacements))
object = object.toTemplateReplacements();
return this.template.gsub(this.pattern, function(match) {
if (object == null) return '';
var before = match[1] || '';
if (before == '\\') return match[2];
var ctx = object, expr = match[3];
var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
match = pattern.exec(expr);
if (match == null) return before;
while (match != null) {
var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
ctx = ctx[comp];
if (null == ctx || '' == match[3]) break;
expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
match = pattern.exec(expr);
}
return before + String.interpret(ctx);
});
}
});
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
var $break = { };
var Enumerable = {
each: function(iterator, context) {
var index = 0;
iterator = iterator.bind(context);
try {
this._each(function(value) {
iterator(value, index++);
});
} catch (e) {
if (e != $break) throw e;
}
return this;
},
eachSlice: function(number, iterator, context) {
iterator = iterator ? iterator.bind(context) : Prototype.K;
var index = -number, slices = [], array = this.toArray();
while ((index += number) < array.length)
slices.push(array.slice(index, index+number));
return slices.collect(iterator, context);
},
all: function(iterator, context) {
iterator = iterator ? iterator.bind(context) : Prototype.K;
var result = true;
this.each(function(value, index) {
result = result && !!iterator(value, index);
if (!result) throw $break;
});
return result;
},
any: function(iterator, context) {
iterator = iterator ? iterator.bind(context) : Prototype.K;
var result = false;
this.each(function(value, index) {
if (result = !!iterator(value, index))
throw $break;
});
return result;
},
collect: function(iterator, context) {
iterator = iterator ? iterator.bind(context) : Prototype.K;
var results = [];
this.each(function(value, index) {
results.push(iterator(value, index));
});
return results;
},
detect: function(iterator, context) {
iterator = iterator.bind(context);
var result;
this.each(function(value, index) {
if (iterator(value, index)) {
result = value;
throw $break;
}
});
return result;
},
findAll: function(iterator, context) {
iterator = iterator.bind(context);
var results = [];
this.each(function(value, index) {
if (iterator(value, index))
results.push(value);
});
return results;
},
grep: function(filter, iterator, context) {
iterator = iterator ? iterator.bind(context) : Prototype.K;
var results = [];
if (Object.isString(filter))
filter = new RegExp(filter);
this.each(function(value, index) {
if (filter.match(value))
results.push(iterator(value, index));
});
return results;
},
include: function(object) {
if (Object.isFunction(this.indexOf))
if (this.indexOf(object) != -1) return true;
var found = false;
this.each(function(value) {
if (value == object) {
found = true;
throw $break;
}
});
return found;
},
inGroupsOf: function(number, fillWith) {
fillWith = Object.isUndefined(fillWith) ? null : fillWith;
return this.eachSlice(number, function(slice) {
while(slice.length < number) slice.push(fillWith);
return slice;
});
},
inject: function(memo, iterator, context) {
iterator = iterator.bind(context);
this.each(function(value, index) {
memo = iterator(memo, value, index);
});
return memo;
},
invoke: function(method) {
var args = $A(arguments).slice(1);
return this.map(function(value) {
return value[method].apply(value, args);
});
},
max: function(iterator, context) {
iterator = iterator ? iterator.bind(context) : Prototype.K;
var result;
this.each(function(value, index) {
value = iterator(value, index);
if (result == null || value >= result)
result = value;
});
return result;
},
min: function(iterator, context) {
iterator = iterator ? iterator.bind(context) : Prototype.K;
var result;
this.each(function(value, index) {
value = iterator(value, index);
if (result == null || value < result)
result = value;
});
return result;
},
partition: function(iterator, context) {
iterator = iterator ? iterator.bind(context) : Prototype.K;
var trues = [], falses = [];
this.each(function(value, index) {
(iterator(value, index) ?
trues : falses).push(value);
});
return [trues, falses];
},
pluck: function(property) {
var results = [];
this.each(function(value) {
results.push(value[property]);
});
return results;
},
reject: function(iterator, context) {
iterator = iterator.bind(context);
var results = [];
this.each(function(value, index) {
if (!iterator(value, index))
results.push(value);
});
return results;
},
sortBy: function(iterator, context) {
iterator = iterator.bind(context);
return this.map(function(value, index) {
return {value: value, criteria: iterator(value, index)};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
}).pluck('value');
},
toArray: function() {
return this.map();
},
zip: function() {
var iterator = Prototype.K, args = $A(arguments);
if (Object.isFunction(args.last()))
iterator = args.pop();
var collections = [this].concat(args).map($A);
return this.map(function(value, index) {
return iterator(collections.pluck(index));
});
},
size: function() {
return this.toArray().length;
},
inspect: function() {
return '#<Enumerable:' + this.toArray().inspect() + '>';
}
};
Object.extend(Enumerable, {
map:     Enumerable.collect,
find:    Enumerable.detect,
select:  Enumerable.findAll,
filter:  Enumerable.findAll,
member:  Enumerable.include,
entries: Enumerable.toArray,
every:   Enumerable.all,
some:    Enumerable.any
});
function $A(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
}
if (Prototype.Browser.WebKit) {
$A = function(iterable) {
if (!iterable) return [];
if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
iterable.toArray) return iterable.toArray();
var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
};
}
Array.from = $A;
Object.extend(Array.prototype, Enumerable);
if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
Object.extend(Array.prototype, {
_each: function(iterator) {
for (var i = 0, length = this.length; i < length; i++)
iterator(this[i]);
},
clear: function() {
this.length = 0;
return this;
},
first: function() {
return this[0];
},
last: function() {
return this[this.length - 1];
},
compact: function() {
return this.select(function(value) {
return value != null;
});
},
flatten: function() {
return this.inject([], function(array, value) {
return array.concat(Object.isArray(value) ?
value.flatten() : [value]);
});
},
without: function() {
var values = $A(arguments);
return this.select(function(value) {
return !values.include(value);
});
},
reverse: function(inline) {
return (inline !== false ? this : this.toArray())._reverse();
},
reduce: function() {
return this.length > 1 ? this : this[0];
},
uniq: function(sorted) {
return this.inject([], function(array, value, index) {
if (0 == index || (sorted ? array.last() != value : !array.include(value)))
array.push(value);
return array;
});
},
intersect: function(array) {
return this.uniq().findAll(function(item) {
return array.detect(function(value) { return item === value });
});
},
clone: function() {
return [].concat(this);
},
size: function() {
return this.length;
},
inspect: function() {
return '[' + this.map(Object.inspect).join(', ') + ']';
},
toJSON: function() {
var results = [];
this.each(function(object) {
var value = Object.toJSON(object);
if (!Object.isUndefined(value)) results.push(value);
});
return '[' + results.join(', ') + ']';
}
});
// use native browser JS 1.6 implementation if available
if (Object.isFunction(Array.prototype.forEach))
Array.prototype._each = Array.prototype.forEach;
if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
i || (i = 0);
var length = this.length;
if (i < 0) i = length + i;
for (; i < length; i++)
if (this[i] === item) return i;
return -1;
};
if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
var n = this.slice(0, i).reverse().indexOf(item);
return (n < 0) ? n : i - n - 1;
};
Array.prototype.toArray = Array.prototype.clone;
function $w(string) {
if (!Object.isString(string)) return [];
string = string.strip();
return string ? string.split(/\s+/) : [];
}
if (Prototype.Browser.Opera){
Array.prototype.concat = function() {
var array = [];
for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
for (var i = 0, length = arguments.length; i < length; i++) {
if (Object.isArray(arguments[i])) {
for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
array.push(arguments[i][j]);
} else {
array.push(arguments[i]);
}
}
return array;
};
}
Object.extend(Number.prototype, {
toColorPart: function() {
return this.toPaddedString(2, 16);
},
succ: function() {
return this + 1;
},
times: function(iterator) {
$R(0, this, true).each(iterator);
return this;
},
toPaddedString: function(length, radix) {
var string = this.toString(radix || 10);
return '0'.times(length - string.length) + string;
},
toJSON: function() {
return isFinite(this) ? this.toString() : 'null';
}
});
$w('abs round ceil floor').each(function(method){
Number.prototype[method] = Math[method].methodize();
});
function $H(object) {
return new Hash(object);
};
var Hash = Class.create(Enumerable, (function() {
function toQueryPair(key, value) {
if (Object.isUndefined(value)) return key;
return key + '=' + encodeURIComponent(String.interpret(value));
}
return {
initialize: function(object) {
this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
},
_each: function(iterator) {
for (var key in this._object) {
var value = this._object[key], pair = [key, value];
pair.key = key;
pair.value = value;
iterator(pair);
}
},
set: function(key, value) {
return this._object[key] = value;
},
get: function(key) {
return this._object[key];
},
unset: function(key) {
var value = this._object[key];
delete this._object[key];
return value;
},
toObject: function() {
return Object.clone(this._object);
},
keys: function() {
return this.pluck('key');
},
values: function() {
return this.pluck('value');
},
index: function(value) {
var match = this.detect(function(pair) {
return pair.value === value;
});
return match && match.key;
},
merge: function(object) {
return this.clone().update(object);
},
update: function(object) {
return new Hash(object).inject(this, function(result, pair) {
result.set(pair.key, pair.value);
return result;
});
},
toQueryString: function() {
return this.map(function(pair) {
var key = encodeURIComponent(pair.key), values = pair.value;
if (values && typeof values == 'object') {
if (Object.isArray(values))
return values.map(toQueryPair.curry(key)).join('&');
}
return toQueryPair(key, values);
}).join('&');
},
inspect: function() {
return '#<Hash:{' + this.map(function(pair) {
return pair.map(Object.inspect).join(': ');
}).join(', ') + '}>';
},
toJSON: function() {
return Object.toJSON(this.toObject());
},
clone: function() {
return new Hash(this);
}
}
})());
Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
Hash.from = $H;
var ObjectRange = Class.create(Enumerable, {
initialize: function(start, end, exclusive) {
this.start = start;
this.end = end;
this.exclusive = exclusive;
},
_each: function(iterator) {
var value = this.start;
while (this.include(value)) {
iterator(value);
value = value.succ();
}
},
include: function(value) {
if (value < this.start)
return false;
if (this.exclusive)
return value < this.end;
return value <= this.end;
}
});
var $R = function(start, end, exclusive) {
return new ObjectRange(start, end, exclusive);
};
var Ajax = {
getTransport: function() {
return Try.these(
function() {return new XMLHttpRequest()},
function() {return new ActiveXObject('Msxml2.XMLHTTP')},
function() {return new ActiveXObject('Microsoft.XMLHTTP')}
) || false;
},
activeRequestCount: 0
};
Ajax.Responders = {
responders: [],
_each: function(iterator) {
this.responders._each(iterator);
},
register: function(responder) {
if (!this.include(responder))
this.responders.push(responder);
},
unregister: function(responder) {
this.responders = this.responders.without(responder);
},
dispatch: function(callback, request, transport, json) {
this.each(function(responder) {
if (Object.isFunction(responder[callback])) {
try {
responder[callback].apply(responder, [request, transport, json]);
} catch (e) { }
}
});
}
};
Object.extend(Ajax.Responders, Enumerable);
Ajax.Responders.register({
onCreate:   function() { Ajax.activeRequestCount++ },
onComplete: function() { Ajax.activeRequestCount-- }
});
Ajax.Base = Class.create({
initialize: function(options) {
this.options = {
method:       'post',
asynchronous: true,
contentType:  'application/x-www-form-urlencoded',
encoding:     'UTF-8',
parameters:   '',
evalJSON:     true,
evalJS:       true
};
Object.extend(this.options, options || { });
this.options.method = this.options.method.toLowerCase();
if (Object.isString(this.options.parameters))
this.options.parameters = this.options.parameters.toQueryParams();
else if (Object.isHash(this.options.parameters))
this.options.parameters = this.options.parameters.toObject();
}
});
Ajax.Request = Class.create(Ajax.Base, {
_complete: false,
initialize: function($super, url, options) {
$super(options);
this.transport = Ajax.getTransport();
this.request(url);
},
request: function(url) {
this.url = url;
this.method = this.options.method;
var params = Object.clone(this.options.parameters);
if (!['get', 'post'].include(this.method)) {
// simulate other verbs over post
params['_method'] = this.method;
this.method = 'post';
}
this.parameters = params;
if (params = Object.toQueryString(params)) {
// when GET, append parameters to URL
if (this.method == 'get')
this.url += (this.url.include('?') ? '&' : '?') + params;
else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
params += '&_=';
}
try {
var response = new Ajax.Response(this);
if (this.options.onCreate) this.options.onCreate(response);
Ajax.Responders.dispatch('onCreate', this, response);
this.transport.open(this.method.toUpperCase(), this.url,
this.options.asynchronous);
if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
this.transport.onreadystatechange = this.onStateChange.bind(this);
this.setRequestHeaders();
this.body = this.method == 'post' ? (this.options.postBody || params) : null;
this.transport.send(this.body);
/* Force Firefox to handle ready state 4 for synchronous requests */
if (!this.options.asynchronous && this.transport.overrideMimeType)
this.onStateChange();
}
catch (e) {
this.dispatchException(e);
}
},
onStateChange: function() {
var readyState = this.transport.readyState;
if (readyState > 1 && !((readyState == 4) && this._complete))
this.respondToReadyState(this.transport.readyState);
},
setRequestHeaders: function() {
var headers = {
'X-Requested-With': 'XMLHttpRequest',
'X-Prototype-Version': Prototype.Version,
'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
};
if (this.method == 'post') {
headers['Content-type'] = this.options.contentType +
(this.options.encoding ? '; charset=' + this.options.encoding : '');
/* Force "Connection: close" for older Mozilla browsers to work
* around a bug where XMLHttpRequest sends an incorrect
* Content-length header. See Mozilla Bugzilla #246651.
*/
if (this.transport.overrideMimeType &&
(navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
headers['Connection'] = 'close';
}
// user-defined headers
if (typeof this.options.requestHeaders == 'object') {
var extras = this.options.requestHeaders;
if (Object.isFunction(extras.push))
for (var i = 0, length = extras.length; i < length; i += 2)
headers[extras[i]] = extras[i+1];
else
$H(extras).each(function(pair) { headers[pair.key] = pair.value });
}
for (var name in headers)
this.transport.setRequestHeader(name, headers[name]);
},
success: function() {
var status = this.getStatus();
return !status || (status >= 200 && status < 300);
},
getStatus: function() {
try {
return this.transport.status || 0;
} catch (e) { return 0 }
},
respondToReadyState: function(readyState) {
var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
if (state == 'Complete') {
try {
this._complete = true;
(this.options['on' + response.status]
|| this.options['on' + (this.success() ? 'Success' : 'Failure')]
|| Prototype.emptyFunction)(response, response.headerJSON);
} catch (e) {
this.dispatchException(e);
}
var contentType = response.getHeader('Content-type');
if (this.options.evalJS == 'force'
|| (this.options.evalJS && this.isSameOrigin() && contentType
&& contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
this.evalResponse();
}
try {
(this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
} catch (e) {
this.dispatchException(e);
}
if (state == 'Complete') {
// avoid memory leak in MSIE: clean up
this.transport.onreadystatechange = Prototype.emptyFunction;
}
},
isSameOrigin: function() {
var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
protocol: location.protocol,
domain: document.domain,
port: location.port ? ':' + location.port : ''
}));
},
getHeader: function(name) {
try {
return this.transport.getResponseHeader(name) || null;
} catch (e) { return null }
},
evalResponse: function() {
try {
return eval((this.transport.responseText || '').unfilterJSON());
} catch (e) {
this.dispatchException(e);
}
},
dispatchException: function(exception) {
(this.options.onException || Prototype.emptyFunction)(this, exception);
Ajax.Responders.dispatch('onException', this, exception);
}
});
Ajax.Request.Events =
['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
Ajax.Response = Class.create({
initialize: function(request){
this.request = request;
var transport  = this.transport  = request.transport,
readyState = this.readyState = transport.readyState;
if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
this.status       = this.getStatus();
this.statusText   = this.getStatusText();
this.responseText = String.interpret(transport.responseText);
this.headerJSON   = this._getHeaderJSON();
}
if(readyState == 4) {
var xml = transport.responseXML;
this.responseXML  = Object.isUndefined(xml) ? null : xml;
this.responseJSON = this._getResponseJSON();
}
},
status:      0,
statusText: '',
getStatus: Ajax.Request.prototype.getStatus,
getStatusText: function() {
try {
return this.transport.statusText || '';
} catch (e) { return '' }
},
getHeader: Ajax.Request.prototype.getHeader,
getAllHeaders: function() {
try {
return this.getAllResponseHeaders();
} catch (e) { return null }
},
getResponseHeader: function(name) {
return this.transport.getResponseHeader(name);
},
getAllResponseHeaders: function() {
return this.transport.getAllResponseHeaders();
},
_getHeaderJSON: function() {
var json = this.getHeader('X-JSON');
if (!json) return null;
json = decodeURIComponent(escape(json));
try {
return json.evalJSON(this.request.options.sanitizeJSON ||
!this.request.isSameOrigin());
} catch (e) {
this.request.dispatchException(e);
}
},
_getResponseJSON: function() {
var options = this.request.options;
if (!options.evalJSON || (options.evalJSON != 'force' &&
!(this.getHeader('Content-type') || '').include('application/json')) ||
this.responseText.blank())
return null;
try {
return this.responseText.evalJSON(options.sanitizeJSON ||
!this.request.isSameOrigin());
} catch (e) {
this.request.dispatchException(e);
}
}
});
Ajax.Updater = Class.create(Ajax.Request, {
initialize: function($super, container, url, options) {
this.container = {
success: (container.success || container),
failure: (container.failure || (container.success ? null : container))
};
options = Object.clone(options);
var onComplete = options.onComplete;
options.onComplete = (function(response, json) {
this.updateContent(response.responseText);
if (Object.isFunction(onComplete)) onComplete(response, json);
}).bind(this);
$super(url, options);
},
updateContent: function(responseText) {
var receiver = this.container[this.success() ? 'success' : 'failure'],
options = this.options;
if (!options.evalScripts) responseText = responseText.stripScripts();
if (receiver = $(receiver)) {
if (options.insertion) {
if (Object.isString(options.insertion)) {
var insertion = { }; insertion[options.insertion] = responseText;
receiver.insert(insertion);
}
else options.insertion(receiver, responseText);
}
else receiver.update(responseText);
}
}
});
Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
initialize: function($super, container, url, options) {
$super(options);
this.onComplete = this.options.onComplete;
this.frequency = (this.options.frequency || 2);
this.decay = (this.options.decay || 1);
this.updater = { };
this.container = container;
this.url = url;
this.start();
},
start: function() {
this.options.onComplete = this.updateComplete.bind(this);
this.onTimerEvent();
},
stop: function() {
this.updater.options.onComplete = undefined;
clearTimeout(this.timer);
(this.onComplete || Prototype.emptyFunction).apply(this, arguments);
},
updateComplete: function(response) {
if (this.options.decay) {
this.decay = (response.responseText == this.lastText ?
this.decay * this.options.decay : 1);
this.lastText = response.responseText;
}
this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
},
onTimerEvent: function() {
this.updater = new Ajax.Updater(this.container, this.url, this.options);
}
});
function $(element) {
if (arguments.length > 1) {
for (var i = 0, elements = [], length = arguments.length; i < length; i++)
elements.push($(arguments[i]));
return elements;
}
if (Object.isString(element))
element = document.getElementById(element);
return Element.extend(element);
}
if (Prototype.BrowserFeatures.XPath) {
document._getElementsByXPath = function(expression, parentElement) {
var results = [];
var query = document.evaluate(expression, $(parentElement) || document,
null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i = 0, length = query.snapshotLength; i < length; i++)
results.push(Element.extend(query.snapshotItem(i)));
return results;
};
}
/*--------------------------------------------------------------------------*/
if (!window.Node) var Node = { };
if (!Node.ELEMENT_NODE) {
// DOM level 2 ECMAScript Language Binding
Object.extend(Node, {
ELEMENT_NODE: 1,
ATTRIBUTE_NODE: 2,
TEXT_NODE: 3,
CDATA_SECTION_NODE: 4,
ENTITY_REFERENCE_NODE: 5,
ENTITY_NODE: 6,
PROCESSING_INSTRUCTION_NODE: 7,
COMMENT_NODE: 8,
DOCUMENT_NODE: 9,
DOCUMENT_TYPE_NODE: 10,
DOCUMENT_FRAGMENT_NODE: 11,
NOTATION_NODE: 12
});
}
(function() {
var element = this.Element;
this.Element = function(tagName, attributes) {
attributes = attributes || { };
tagName = tagName.toLowerCase();
var cache = Element.cache;
if (Prototype.Browser.IE && attributes.name) {
tagName = '<' + tagName + ' name="' + attributes.name + '">';
delete attributes.name;
return Element.writeAttribute(document.createElement(tagName), attributes);
}
if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
};
Object.extend(this.Element, element || { });
}).call(window);
Element.cache = { };
Element.Methods = {
visible: function(element) {
return $(element).style.display != 'none';
},
toggle: function(element) {
element = $(element);
Element[Element.visible(element) ? 'hide' : 'show'](element);
return element;
},
hide: function(element) {
$(element).style.display = 'none';
return element;
},
show: function(element) {
$(element).style.display = '';
return element;
},
remove: function(element) {
element = $(element);
element.parentNode.removeChild(element);
return element;
},
update: function(element, content) {
element = $(element);
if (content && content.toElement) content = content.toElement();
if (Object.isElement(content)) return element.update().insert(content);
content = Object.toHTML(content);
element.innerHTML = content.stripScripts();
content.evalScripts.bind(content).defer();
return element;
},
replace: function(element, content) {
element = $(element);
if (content && content.toElement) content = content.toElement();
else if (!Object.isElement(content)) {
content = Object.toHTML(content);
var range = element.ownerDocument.createRange();
range.selectNode(element);
content.evalScripts.bind(content).defer();
content = range.createContextualFragment(content.stripScripts());
}
element.parentNode.replaceChild(content, element);
return element;
},
insert: function(element, insertions) {
element = $(element);
if (Object.isString(insertions) || Object.isNumber(insertions) ||
Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
insertions = {bottom:insertions};
var content, insert, tagName, childNodes;
for (var position in insertions) {
content  = insertions[position];
position = position.toLowerCase();
insert = Element._insertionTranslations[position];
if (content && content.toElement) content = content.toElement();
if (Object.isElement(content)) {
insert(element, content);
continue;
}
content = Object.toHTML(content);
tagName = ((position == 'before' || position == 'after')
? element.parentNode : element).tagName.toUpperCase();
childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
if (position == 'top' || position == 'after') childNodes.reverse();
childNodes.each(insert.curry(element));
content.evalScripts.bind(content).defer();
}
return element;
},
wrap: function(element, wrapper, attributes) {
element = $(element);
if (Object.isElement(wrapper))
$(wrapper).writeAttribute(attributes || { });
else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
else wrapper = new Element('div', wrapper);
if (element.parentNode)
element.parentNode.replaceChild(wrapper, element);
wrapper.appendChild(element);
return wrapper;
},
inspect: function(element) {
element = $(element);
var result = '<' + element.tagName.toLowerCase();
$H({'id': 'id', 'className': 'class'}).each(function(pair) {
var property = pair.first(), attribute = pair.last();
var value = (element[property] || '').toString();
if (value) result += ' ' + attribute + '=' + value.inspect(true);
});
return result + '>';
},
recursivelyCollect: function(element, property) {
element = $(element);
var elements = [];
while (element = element[property])
if (element.nodeType == 1)
elements.push(Element.extend(element));
return elements;
},
ancestors: function(element) {
return $(element).recursivelyCollect('parentNode');
},
descendants: function(element) {
return $(element).select("*");
},
firstDescendant: function(element) {
element = $(element).firstChild;
while (element && element.nodeType != 1) element = element.nextSibling;
return $(element);
},
immediateDescendants: function(element) {
if (!(element = $(element).firstChild)) return [];
while (element && element.nodeType != 1) element = element.nextSibling;
if (element) return [element].concat($(element).nextSiblings());
return [];
},
previousSiblings: function(element) {
return $(element).recursivelyCollect('previousSibling');
},
nextSiblings: function(element) {
return $(element).recursivelyCollect('nextSibling');
},
siblings: function(element) {
element = $(element);
return element.previousSiblings().reverse().concat(element.nextSiblings());
},
match: function(element, selector) {
if (Object.isString(selector))
selector = new Selector(selector);
return selector.match($(element));
},
up: function(element, expression, index) {
element = $(element);
if (arguments.length == 1) return $(element.parentNode);
var ancestors = element.ancestors();
return Object.isNumber(expression) ? ancestors[expression] :
Selector.findElement(ancestors, expression, index);
},
down: function(element, expression, index) {
element = $(element);
if (arguments.length == 1) return element.firstDescendant();
return Object.isNumber(expression) ? element.descendants()[expression] :
element.select(expression)[index || 0];
},
previous: function(element, expression, index) {
element = $(element);
if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
var previousSiblings = element.previousSiblings();
return Object.isNumber(expression) ? previousSiblings[expression] :
Selector.findElement(previousSiblings, expression, index);
},
next: function(element, expression, index) {
element = $(element);
if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
var nextSiblings = element.nextSiblings();
return Object.isNumber(expression) ? nextSiblings[expression] :
Selector.findElement(nextSiblings, expression, index);
},
select: function() {
var args = $A(arguments), element = $(args.shift());
return Selector.findChildElements(element, args);
},
adjacent: function() {
var args = $A(arguments), element = $(args.shift());
return Selector.findChildElements(element.parentNode, args).without(element);
},
identify: function(element) {
element = $(element);
var id = element.readAttribute('id'), self = arguments.callee;
if (id) return id;
do { id = 'anonymous_element_' + self.counter++ } while ($(id));
element.writeAttribute('id', id);
return id;
},
readAttribute: function(element, name) {
element = $(element);
if (Prototype.Browser.IE) {
var t = Element._attributeTranslations.read;
if (t.values[name]) return t.values[name](element, name);
if (t.names[name]) name = t.names[name];
if (name.include(':')) {
return (!element.attributes || !element.attributes[name]) ? null :
element.attributes[name].value;
}
}
return element.getAttribute(name);
},
writeAttribute: function(element, name, value) {
element = $(element);
var attributes = { }, t = Element._attributeTranslations.write;
if (typeof name == 'object') attributes = name;
else attributes[name] = Object.isUndefined(value) ? true : value;
for (var attr in attributes) {
name = t.names[attr] || attr;
value = attributes[attr];
if (t.values[attr]) name = t.values[attr](element, value);
if (value === false || value === null)
element.removeAttribute(name);
else if (value === true)
element.setAttribute(name, name);
else element.setAttribute(name, value);
}
return element;
},
getHeight: function(element) {
return $(element).getDimensions().height;
},
getWidth: function(element) {
return $(element).getDimensions().width;
},
classNames: function(element) {
return new Element.ClassNames(element);
},
hasClassName: function(element, className) {
if (!(element = $(element))) return;
var elementClassName = element.className;
return (elementClassName.length > 0 && (elementClassName == className ||
new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
},
addClassName: function(element, className) {
if (!(element = $(element))) return;
if (!element.hasClassName(className))
element.className += (element.className ? ' ' : '') + className;
return element;
},
removeClassName: function(element, className) {
if (!(element = $(element))) return;
element.className = element.className.replace(
new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
return element;
},
toggleClassName: function(element, className) {
if (!(element = $(element))) return;
return element[element.hasClassName(className) ?
'removeClassName' : 'addClassName'](className);
},
// removes whitespace-only text node children
cleanWhitespace: function(element) {
element = $(element);
var node = element.firstChild;
while (node) {
var nextNode = node.nextSibling;
if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
element.removeChild(node);
node = nextNode;
}
return element;
},
empty: function(element) {
return $(element).innerHTML.blank();
},
descendantOf: function(element, ancestor) {
element = $(element), ancestor = $(ancestor);
var originalAncestor = ancestor;
if (element.compareDocumentPosition)
return (element.compareDocumentPosition(ancestor) & 8) === 8;
if (element.sourceIndex && !Prototype.Browser.Opera) {
var e = element.sourceIndex, a = ancestor.sourceIndex,
nextAncestor = ancestor.nextSibling;
if (!nextAncestor) {
do { ancestor = ancestor.parentNode; }
while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
}
if (nextAncestor && nextAncestor.sourceIndex)
return (e > a && e < nextAncestor.sourceIndex);
}
while (element = element.parentNode)
if (element == originalAncestor) return true;
return false;
},
scrollTo: function(element) {
element = $(element);
var pos = element.cumulativeOffset();
window.scrollTo(pos[0], pos[1]);
return element;
},
getStyle: function(element, style) {
element = $(element);
style = style == 'float' ? 'cssFloat' : style.camelize();
var value = element.style[style];
if (!value) {
var css = document.defaultView.getComputedStyle(element, null);
value = css ? css[style] : null;
}
if (style == 'opacity') return value ? parseFloat(value) : 1.0;
return value == 'auto' ? null : value;
},
getOpacity: function(element) {
return $(element).getStyle('opacity');
},
setStyle: function(element, styles) {
element = $(element);
var elementStyle = element.style, match;
if (Object.isString(styles)) {
element.style.cssText += ';' + styles;
return styles.include('opacity') ?
element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
}
for (var property in styles)
if (property == 'opacity') element.setOpacity(styles[property]);
else
elementStyle[(property == 'float' || property == 'cssFloat') ?
(Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
property] = styles[property];
return element;
},
setOpacity: function(element, value) {
element = $(element);
element.style.opacity = (value == 1 || value === '') ? '' :
(value < 0.00001) ? 0 : value;
return element;
},
getDimensions: function(element) {
element = $(element);
var display = $(element).getStyle('display');
if (display != 'none' && display != null) // Safari bug
return {width: element.offsetWidth, height: element.offsetHeight};
// All *Width and *Height properties give 0 on elements with display none,
// so enable the element temporarily
var els = element.style;
var originalVisibility = els.visibility;
var originalPosition = els.position;
var originalDisplay = els.display;
els.visibility = 'hidden';
els.position = 'absolute';
els.display = 'block';
var originalWidth = element.clientWidth;
var originalHeight = element.clientHeight;
els.display = originalDisplay;
els.position = originalPosition;
els.visibility = originalVisibility;
return {width: originalWidth, height: originalHeight};
},
makePositioned: function(element) {
element = $(element);
var pos = Element.getStyle(element, 'position');
if (pos == 'static' || !pos) {
element._madePositioned = true;
element.style.position = 'relative';
// Opera returns the offset relative to the positioning context, when an
// element is position relative but top and left have not been defined
if (window.opera) {
element.style.top = 0;
element.style.left = 0;
}
}
return element;
},
undoPositioned: function(element) {
element = $(element);
if (element._madePositioned) {
element._madePositioned = undefined;
element.style.position =
element.style.top =
element.style.left =
element.style.bottom =
element.style.right = '';
}
return element;
},
makeClipping: function(element) {
element = $(element);
if (element._overflow) return element;
element._overflow = Element.getStyle(element, 'overflow') || 'auto';
if (element._overflow !== 'hidden')
element.style.overflow = 'hidden';
return element;
},
undoClipping: function(element) {
element = $(element);
if (!element._overflow) return element;
element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
element._overflow = null;
return element;
},
cumulativeOffset: function(element) {
var valueT = 0, valueL = 0;
do {
valueT += element.offsetTop  || 0;
valueL += element.offsetLeft || 0;
element = element.offsetParent;
} while (element);
return Element._returnOffset(valueL, valueT);
},
positionedOffset: function(element) {
var valueT = 0, valueL = 0;
do {
valueT += element.offsetTop  || 0;
valueL += element.offsetLeft || 0;
element = element.offsetParent;
if (element) {
if (element.tagName == 'BODY') break;
var p = Element.getStyle(element, 'position');
if (p !== 'static') break;
}
} while (element);
return Element._returnOffset(valueL, valueT);
},
absolutize: function(element) {
element = $(element);
if (element.getStyle('position') == 'absolute') return;
// Position.prepare(); // To be done manually by Scripty when it needs it.
var offsets = element.positionedOffset();
var top     = offsets[1];
var left    = offsets[0];
var width   = element.clientWidth;
var height  = element.clientHeight;
element._originalLeft   = left - parseFloat(element.style.left  || 0);
element._originalTop    = top  - parseFloat(element.style.top || 0);
element._originalWidth  = element.style.width;
element._originalHeight = element.style.height;
element.style.position = 'absolute';
element.style.top    = top + 'px';
element.style.left   = left + 'px';
element.style.width  = width + 'px';
element.style.height = height + 'px';
return element;
},
relativize: function(element) {
element = $(element);
if (element.getStyle('position') == 'relative') return;
// Position.prepare(); // To be done manually by Scripty when it needs it.
element.style.position = 'relative';
var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
element.style.top    = top + 'px';
element.style.left   = left + 'px';
element.style.height = element._originalHeight;
element.style.width  = element._originalWidth;
return element;
},
cumulativeScrollOffset: function(element) {
var valueT = 0, valueL = 0;
do {
valueT += element.scrollTop  || 0;
valueL += element.scrollLeft || 0;
element = element.parentNode;
} while (element);
return Element._returnOffset(valueL, valueT);
},
getOffsetParent: function(element) {
if (element.offsetParent) return $(element.offsetParent);
if (element == document.body) return $(element);
while ((element = element.parentNode) && element != document.body)
if (Element.getStyle(element, 'position') != 'static')
return $(element);
return $(document.body);
},
viewportOffset: function(forElement) {
var valueT = 0, valueL = 0;
var element = forElement;
do {
valueT += element.offsetTop  || 0;
valueL += element.offsetLeft || 0;
// Safari fix
if (element.offsetParent == document.body &&
Element.getStyle(element, 'position') == 'absolute') break;
} while (element = element.offsetParent);
element = forElement;
do {
if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
valueT -= element.scrollTop  || 0;
valueL -= element.scrollLeft || 0;
}
} while (element = element.parentNode);
return Element._returnOffset(valueL, valueT);
},
clonePosition: function(element, source) {
var options = Object.extend({
setLeft:    true,
setTop:     true,
setWidth:   true,
setHeight:  true,
offsetTop:  0,
offsetLeft: 0
}, arguments[2] || { });
// find page position of source
source = $(source);
var p = source.viewportOffset();
// find coordinate system to use
element = $(element);
var delta = [0, 0];
var parent = null;
// delta [0,0] will do fine with position: fixed elements,
// position:absolute needs offsetParent deltas
if (Element.getStyle(element, 'position') == 'absolute') {
parent = element.getOffsetParent();
delta = parent.viewportOffset();
}
// correct by body offsets (fixes Safari)
if (parent == document.body) {
delta[0] -= document.body.offsetLeft;
delta[1] -= document.body.offsetTop;
}
// set position
if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
if (options.setHeight) element.style.height = source.offsetHeight + 'px';
return element;
}
};
Element.Methods.identify.counter = 1;
Object.extend(Element.Methods, {
getElementsBySelector: Element.Methods.select,
childElements: Element.Methods.immediateDescendants
});
Element._attributeTranslations = {
write: {
names: {
className: 'class',
htmlFor:   'for'
},
values: { }
}
};
if (Prototype.Browser.Opera) {
Element.Methods.getStyle = Element.Methods.getStyle.wrap(
function(proceed, element, style) {
switch (style) {
case 'left': case 'top': case 'right': case 'bottom':
if (proceed(element, 'position') === 'static') return null;
case 'height': case 'width':
// returns '0px' for hidden elements; we want it to return null
if (!Element.visible(element)) return null;
// returns the border-box dimensions rather than the content-box
// dimensions, so we subtract padding and borders from the value
var dim = parseInt(proceed(element, style), 10);
if (dim !== element['offset' + style.capitalize()])
return dim + 'px';
var properties;
if (style === 'height') {
properties = ['border-top-width', 'padding-top',
'padding-bottom', 'border-bottom-width'];
}
else {
properties = ['border-left-width', 'padding-left',
'padding-right', 'border-right-width'];
}
return properties.inject(dim, function(memo, property) {
var val = proceed(element, property);
return val === null ? memo : memo - parseInt(val, 10);
}) + 'px';
default: return proceed(element, style);
}
}
);
Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
function(proceed, element, attribute) {
if (attribute === 'title') return element.title;
return proceed(element, attribute);
}
);
}
else if (Prototype.Browser.IE) {
// IE doesn't report offsets correctly for static elements, so we change them
// to "relative" to get the values, then change them back.
Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
function(proceed, element) {
element = $(element);
var position = element.getStyle('position');
if (position !== 'static') return proceed(element);
element.setStyle({ position: 'relative' });
var value = proceed(element);
element.setStyle({ position: position });
return value;
}
);
$w('positionedOffset viewportOffset').each(function(method) {
Element.Methods[method] = Element.Methods[method].wrap(
function(proceed, element) {
element = $(element);
var position = element.getStyle('position');
if (position !== 'static') return proceed(element);
// Trigger hasLayout on the offset parent so that IE6 reports
// accurate offsetTop and offsetLeft values for position: fixed.
var offsetParent = element.getOffsetParent();
if (offsetParent && offsetParent.getStyle('position') === 'fixed')
offsetParent.setStyle({ zoom: 1 });
element.setStyle({ position: 'relative' });
var value = proceed(element);
element.setStyle({ position: position });
return value;
}
);
});
Element.Methods.getStyle = function(element, style) {
element = $(element);
style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
var value = element.style[style];
if (!value && element.currentStyle) value = element.currentStyle[style];
if (style == 'opacity') {
if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
if (value[1]) return parseFloat(value[1]) / 100;
return 1.0;
}
if (value == 'auto') {
if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
return element['offset' + style.capitalize()] + 'px';
return null;
}
return value;
};
Element.Methods.setOpacity = function(element, value) {
function stripAlpha(filter){
return filter.replace(/alpha\([^\)]*\)/gi,'');
}
element = $(element);
var currentStyle = element.currentStyle;
if ((currentStyle && !currentStyle.hasLayout) ||
(!currentStyle && element.style.zoom == 'normal'))
element.style.zoom = 1;
var filter = element.getStyle('filter'), style = element.style;
if (value == 1 || value === '') {
(filter = stripAlpha(filter)) ?
style.filter = filter : style.removeAttribute('filter');
return element;
} else if (value < 0.00001) value = 0;
style.filter = stripAlpha(filter) +
'alpha(opacity=' + (value * 100) + ')';
return element;
};
Element._attributeTranslations = {
read: {
names: {
'class': 'className',
'for':   'htmlFor'
},
values: {
_getAttr: function(element, attribute) {
return element.getAttribute(attribute, 2);
},
_getAttrNode: function(element, attribute) {
var node = element.getAttributeNode(attribute);
return node ? node.value : "";
},
_getEv: function(element, attribute) {
attribute = element.getAttribute(attribute);
return attribute ? attribute.toString().slice(23, -2) : null;
},
_flag: function(element, attribute) {
return $(element).hasAttribute(attribute) ? attribute : null;
},
style: function(element) {
return element.style.cssText.toLowerCase();
},
title: function(element) {
return element.title;
}
}
}
};
Element._attributeTranslations.write = {
names: Object.extend({
cellpadding: 'cellPadding',
cellspacing: 'cellSpacing'
}, Element._attributeTranslations.read.names),
values: {
checked: function(element, value) {
element.checked = !!value;
},
style: function(element, value) {
element.style.cssText = value ? value : '';
}
}
};
Element._attributeTranslations.has = {};
$w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
'encType maxLength readOnly longDesc').each(function(attr) {
Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
Element._attributeTranslations.has[attr.toLowerCase()] = attr;
});
(function(v) {
Object.extend(v, {
href:        v._getAttr,
src:         v._getAttr,
type:        v._getAttr,
action:      v._getAttrNode,
disabled:    v._flag,
checked:     v._flag,
readonly:    v._flag,
multiple:    v._flag,
onload:      v._getEv,
onunload:    v._getEv,
onclick:     v._getEv,
ondblclick:  v._getEv,
onmousedown: v._getEv,
onmouseup:   v._getEv,
onmouseover: v._getEv,
onmousemove: v._getEv,
onmouseout:  v._getEv,
onfocus:     v._getEv,
onblur:      v._getEv,
onkeypress:  v._getEv,
onkeydown:   v._getEv,
onkeyup:     v._getEv,
onsubmit:    v._getEv,
onreset:     v._getEv,
onselect:    v._getEv,
onchange:    v._getEv
});
})(Element._attributeTranslations.read.values);
}
else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
Element.Methods.setOpacity = function(element, value) {
element = $(element);
element.style.opacity = (value == 1) ? 0.999999 :
(value === '') ? '' : (value < 0.00001) ? 0 : value;
return element;
};
}
else if (Prototype.Browser.WebKit) {
Element.Methods.setOpacity = function(element, value) {
element = $(element);
element.style.opacity = (value == 1 || value === '') ? '' :
(value < 0.00001) ? 0 : value;
if (value == 1)
if(element.tagName == 'IMG' && element.width) {
element.width++; element.width--;
} else try {
var n = document.createTextNode(' ');
element.appendChild(n);
element.removeChild(n);
} catch (e) { }
return element;
};
// Safari returns margins on body which is incorrect if the child is absolutely
// positioned.  For performance reasons, redefine Element#cumulativeOffset for
// KHTML/WebKit only.
Element.Methods.cumulativeOffset = function(element) {
var valueT = 0, valueL = 0;
do {
valueT += element.offsetTop  || 0;
valueL += element.offsetLeft || 0;
if (element.offsetParent == document.body)
if (Element.getStyle(element, 'position') == 'absolute') break;
element = element.offsetParent;
} while (element);
return Element._returnOffset(valueL, valueT);
};
}
if (Prototype.Browser.IE || Prototype.Browser.Opera) {
// IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
Element.Methods.update = function(element, content) {
element = $(element);
if (content && content.toElement) content = content.toElement();
if (Object.isElement(content)) return element.update().insert(content);
content = Object.toHTML(content);
var tagName = element.tagName.toUpperCase();
if (tagName in Element._insertionTranslations.tags) {
$A(element.childNodes).each(function(node) { element.removeChild(node) });
Element._getContentFromAnonymousElement(tagName, content.stripScripts())
.each(function(node) { element.appendChild(node) });
}
else element.innerHTML = content.stripScripts();
content.evalScripts.bind(content).defer();
return element;
};
}
if ('outerHTML' in document.createElement('div')) {
Element.Methods.replace = function(element, content) {
element = $(element);
if (content && content.toElement) content = content.toElement();
if (Object.isElement(content)) {
element.parentNode.replaceChild(content, element);
return element;
}
content = Object.toHTML(content);
var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
if (Element._insertionTranslations.tags[tagName]) {
var nextSibling = element.next();
var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
parent.removeChild(element);
if (nextSibling)
fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
else
fragments.each(function(node) { parent.appendChild(node) });
}
else element.outerHTML = content.stripScripts();
content.evalScripts.bind(content).defer();
return element;
};
}
Element._returnOffset = function(l, t) {
var result = [l, t];
result.left = l;
result.top = t;
return result;
};
Element._getContentFromAnonymousElement = function(tagName, html) {
var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
if (t) {
div.innerHTML = t[0] + html + t[1];
t[2].times(function() { div = div.firstChild });
} else div.innerHTML = html;
return $A(div.childNodes);
};
Element._insertionTranslations = {
before: function(element, node) {
element.parentNode.insertBefore(node, element);
},
top: function(element, node) {
element.insertBefore(node, element.firstChild);
},
bottom: function(element, node) {
element.appendChild(node);
},
after: function(element, node) {
element.parentNode.insertBefore(node, element.nextSibling);
},
tags: {
TABLE:  ['<table>',                '</table>',                   1],
TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
SELECT: ['<select>',               '</select>',                  1]
}
};
(function() {
Object.extend(this.tags, {
THEAD: this.tags.TBODY,
TFOOT: this.tags.TBODY,
TH:    this.tags.TD
});
}).call(Element._insertionTranslations);
Element.Methods.Simulated = {
hasAttribute: function(element, attribute) {
attribute = Element._attributeTranslations.has[attribute] || attribute;
var node = $(element).getAttributeNode(attribute);
return node && node.specified;
}
};
Element.Methods.ByTag = { };
Object.extend(Element, Element.Methods);
if (!Prototype.BrowserFeatures.ElementExtensions &&
document.createElement('div').__proto__) {
window.HTMLElement = { };
window.HTMLElement.prototype = document.createElement('div').__proto__;
Prototype.BrowserFeatures.ElementExtensions = true;
}
Element.extend = (function() {
if (Prototype.BrowserFeatures.SpecificElementExtensions)
return Prototype.K;
var Methods = { }, ByTag = Element.Methods.ByTag;
var extend = Object.extend(function(element) {
if (!element || element._extendedByPrototype ||
element.nodeType != 1 || element == window) return element;
var methods = Object.clone(Methods),
tagName = element.tagName, property, value;
// extend methods for specific tags
if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
for (property in methods) {
value = methods[property];
if (Object.isFunction(value) && !(property in element))
element[property] = value.methodize();
}
element._extendedByPrototype = Prototype.emptyFunction;
return element;
}, {
refresh: function() {
// extend methods for all tags (Safari doesn't need this)
if (!Prototype.BrowserFeatures.ElementExtensions) {
Object.extend(Methods, Element.Methods);
Object.extend(Methods, Element.Methods.Simulated);
}
}
});
extend.refresh();
return extend;
})();
Element.hasAttribute = function(element, attribute) {
if (element.hasAttribute) return element.hasAttribute(attribute);
return Element.Methods.Simulated.hasAttribute(element, attribute);
};
Element.addMethods = function(methods) {
var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
if (!methods) {
Object.extend(Form, Form.Methods);
Object.extend(Form.Element, Form.Element.Methods);
Object.extend(Element.Methods.ByTag, {
"FORM":     Object.clone(Form.Methods),
"INPUT":    Object.clone(Form.Element.Methods),
"SELECT":   Object.clone(Form.Element.Methods),
"TEXTAREA": Object.clone(Form.Element.Methods)
});
}
if (arguments.length == 2) {
var tagName = methods;
methods = arguments[1];
}
if (!tagName) Object.extend(Element.Methods, methods || { });
else {
if (Object.isArray(tagName)) tagName.each(extend);
else extend(tagName);
}
function extend(tagName) {
tagName = tagName.toUpperCase();
if (!Element.Methods.ByTag[tagName])
Element.Methods.ByTag[tagName] = { };
Object.extend(Element.Methods.ByTag[tagName], methods);
}
function copy(methods, destination, onlyIfAbsent) {
onlyIfAbsent = onlyIfAbsent || false;
for (var property in methods) {
var value = methods[property];
if (!Object.isFunction(value)) continue;
if (!onlyIfAbsent || !(property in destination))
destination[property] = value.methodize();
}
}
function findDOMClass(tagName) {
var klass;
var trans = {
"OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
"FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
"DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
"H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
"INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
"TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
"TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
"TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
"FrameSet", "IFRAME": "IFrame"
};
if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
if (window[klass]) return window[klass];
klass = 'HTML' + tagName + 'Element';
if (window[klass]) return window[klass];
klass = 'HTML' + tagName.capitalize() + 'Element';
if (window[klass]) return window[klass];
window[klass] = { };
window[klass].prototype = document.createElement(tagName).__proto__;
return window[klass];
}
if (F.ElementExtensions) {
copy(Element.Methods, HTMLElement.prototype);
copy(Element.Methods.Simulated, HTMLElement.prototype, true);
}
if (F.SpecificElementExtensions) {
for (var tag in Element.Methods.ByTag) {
var klass = findDOMClass(tag);
if (Object.isUndefined(klass)) continue;
copy(T[tag], klass.prototype);
}
}
Object.extend(Element, Element.Methods);
delete Element.ByTag;
if (Element.extend.refresh) Element.extend.refresh();
Element.cache = { };
};
document.viewport = {
getDimensions: function() {
var dimensions = { };
var B = Prototype.Browser;
$w('width height').each(function(d) {
var D = d.capitalize();
dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :
(B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];
});
return dimensions;
},
getWidth: function() {
return this.getDimensions().width;
},
getHeight: function() {
return this.getDimensions().height;
},
getScrollOffsets: function() {
return Element._returnOffset(
window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
}
};
/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
* part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
* license.  Please see http://www.yui-ext.com/ for more information. */
var Selector = Class.create({
initialize: function(expression) {
this.expression = expression.strip();
this.compileMatcher();
},
shouldUseXPath: function() {
if (!Prototype.BrowserFeatures.XPath) return false;
var e = this.expression;
// Safari 3 chokes on :*-of-type and :empty
if (Prototype.Browser.WebKit &&
(e.include("-of-type") || e.include(":empty")))
return false;
// XPath can't do namespaced attributes, nor can it read
// the "checked" property from DOM nodes
if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
return false;
return true;
},
compileMatcher: function() {
if (this.shouldUseXPath())
return this.compileXPathMatcher();
var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
c = Selector.criteria, le, p, m;
if (Selector._cache[e]) {
this.matcher = Selector._cache[e];
return;
}
this.matcher = ["this.matcher = function(root) {",
"var r = root, h = Selector.handlers, c = false, n;"];
while (e && le != e && (/\S/).test(e)) {
le = e;
for (var i in ps) {
p = ps[i];
if (m = e.match(p)) {
this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
new Template(c[i]).evaluate(m));
e = e.replace(m[0], '');
break;
}
}
}
this.matcher.push("return h.unique(n);\n}");
eval(this.matcher.join('\n'));
Selector._cache[this.expression] = this.matcher;
},
compileXPathMatcher: function() {
var e = this.expression, ps = Selector.patterns,
x = Selector.xpath, le, m;
if (Selector._cache[e]) {
this.xpath = Selector._cache[e]; return;
}
this.matcher = ['.//*'];
while (e && le != e && (/\S/).test(e)) {
le = e;
for (var i in ps) {
if (m = e.match(ps[i])) {
this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
new Template(x[i]).evaluate(m));
e = e.replace(m[0], '');
break;
}
}
}
this.xpath = this.matcher.join('');
Selector._cache[this.expression] = this.xpath;
},
findElements: function(root) {
root = root || document;
if (this.xpath) return document._getElementsByXPath(this.xpath, root);
return this.matcher(root);
},
match: function(element) {
this.tokens = [];
var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
var le, p, m;
while (e && le !== e && (/\S/).test(e)) {
le = e;
for (var i in ps) {
p = ps[i];
if (m = e.match(p)) {
// use the Selector.assertions methods unless the selector
// is too complex.
if (as[i]) {
this.tokens.push([i, Object.clone(m)]);
e = e.replace(m[0], '');
} else {
// reluctantly do a document-wide search
// and look for a match in the array
return this.findElements(document).include(element);
}
}
}
}
var match = true, name, matches;
for (var i = 0, token; token = this.tokens[i]; i++) {
name = token[0], matches = token[1];
if (!Selector.assertions[name](element, matches)) {
match = false; break;
}
}
return match;
},
toString: function() {
return this.expression;
},
inspect: function() {
return "#<Selector:" + this.expression.inspect() + ">";
}
});
Object.extend(Selector, {
_cache: { },
xpath: {
descendant:   "//*",
child:        "/*",
adjacent:     "/following-sibling::*[1]",
laterSibling: '/following-sibling::*',
tagName:      function(m) {
if (m[1] == '*') return '';
return "[local-name()='" + m[1].toLowerCase() +
"' or local-name()='" + m[1].toUpperCase() + "']";
},
className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
id:           "[@id='#{1}']",
attrPresence: function(m) {
m[1] = m[1].toLowerCase();
return new Template("[@#{1}]").evaluate(m);
},
attr: function(m) {
m[1] = m[1].toLowerCase();
m[3] = m[5] || m[6];
return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
},
pseudo: function(m) {
var h = Selector.xpath.pseudos[m[1]];
if (!h) return '';
if (Object.isFunction(h)) return h(m);
return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
},
operators: {
'=':  "[@#{1}='#{3}']",
'!=': "[@#{1}!='#{3}']",
'^=': "[starts-with(@#{1}, '#{3}')]",
'$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
'*=': "[contains(@#{1}, '#{3}')]",
'~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
'|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
},
pseudos: {
'first-child': '[not(preceding-sibling::*)]',
'last-child':  '[not(following-sibling::*)]',
'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
'checked':     "[@checked]",
'disabled':    "[@disabled]",
'enabled':     "[not(@disabled)]",
'not': function(m) {
var e = m[6], p = Selector.patterns,
x = Selector.xpath, le, v;
var exclusion = [];
while (e && le != e && (/\S/).test(e)) {
le = e;
for (var i in p) {
if (m = e.match(p[i])) {
v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
exclusion.push("(" + v.substring(1, v.length - 1) + ")");
e = e.replace(m[0], '');
break;
}
}
}
return "[not(" + exclusion.join(" and ") + ")]";
},
'nth-child':      function(m) {
return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
},
'nth-last-child': function(m) {
return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
},
'nth-of-type':    function(m) {
return Selector.xpath.pseudos.nth("position() ", m);
},
'nth-last-of-type': function(m) {
return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
},
'first-of-type':  function(m) {
m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
},
'last-of-type':   function(m) {
m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
},
'only-of-type':   function(m) {
var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
},
nth: function(fragment, m) {
var mm, formula = m[6], predicate;
if (formula == 'even') formula = '2n+0';
if (formula == 'odd')  formula = '2n+1';
if (mm = formula.match(/^(\d+)$/)) // digit only
return '[' + fragment + "= " + mm[1] + ']';
if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
if (mm[1] == "-") mm[1] = -1;
var a = mm[1] ? Number(mm[1]) : 1;
var b = mm[2] ? Number(mm[2]) : 0;
predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
"((#{fragment} - #{b}) div #{a} >= 0)]";
return new Template(predicate).evaluate({
fragment: fragment, a: a, b: b });
}
}
}
},
criteria: {
tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
className:    'n = h.className(n, r, "#{1}", c);    c = false;',
id:           'n = h.id(n, r, "#{1}", c);           c = false;',
attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
attr: function(m) {
m[3] = (m[5] || m[6]);
return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
},
pseudo: function(m) {
if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
},
descendant:   'c = "descendant";',
child:        'c = "child";',
adjacent:     'c = "adjacent";',
laterSibling: 'c = "laterSibling";'
},
patterns: {
// combinators must be listed first
// (and descendant needs to be last combinator)
laterSibling: /^\s*~\s*/,
child:        /^\s*>\s*/,
adjacent:     /^\s*\+\s*/,
descendant:   /^\s/,
// selectors follow
tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
id:           /^#([\w\-\*]+)(\b|$)/,
className:    /^\.([\w\-\*]+)(\b|$)/,
pseudo:
/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
attrPresence: /^\[([\w]+)\]/,
attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
},
// for Selector.match and Element#match
assertions: {
tagName: function(element, matches) {
return matches[1].toUpperCase() == element.tagName.toUpperCase();
},
className: function(element, matches) {
return Element.hasClassName(element, matches[1]);
},
id: function(element, matches) {
return element.id === matches[1];
},
attrPresence: function(element, matches) {
return Element.hasAttribute(element, matches[1]);
},
attr: function(element, matches) {
var nodeValue = Element.readAttribute(element, matches[1]);
return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
}
},
handlers: {
// UTILITY FUNCTIONS
// joins two collections
concat: function(a, b) {
for (var i = 0, node; node = b[i]; i++)
a.push(node);
return a;
},
// marks an array of nodes for counting
mark: function(nodes) {
var _true = Prototype.emptyFunction;
for (var i = 0, node; node = nodes[i]; i++)
node._countedByPrototype = _true;
return nodes;
},
unmark: function(nodes) {
for (var i = 0, node; node = nodes[i]; i++)
node._countedByPrototype = undefined;
return nodes;
},
// mark each child node with its position (for nth calls)
// "ofType" flag indicates whether we're indexing for nth-of-type
// rather than nth-child
index: function(parentNode, reverse, ofType) {
parentNode._countedByPrototype = Prototype.emptyFunction;
if (reverse) {
for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
var node = nodes[i];
if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
}
} else {
for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
}
},
// filters out duplicates and extends all nodes
unique: function(nodes) {
if (nodes.length == 0) return nodes;
var results = [], n;
for (var i = 0, l = nodes.length; i < l; i++)
if (!(n = nodes[i])._countedByPrototype) {
n._countedByPrototype = Prototype.emptyFunction;
results.push(Element.extend(n));
}
return Selector.handlers.unmark(results);
},
// COMBINATOR FUNCTIONS
descendant: function(nodes) {
var h = Selector.handlers;
for (var i = 0, results = [], node; node = nodes[i]; i++)
h.concat(results, node.getElementsByTagName('*'));
return results;
},
child: function(nodes) {
var h = Selector.handlers;
for (var i = 0, results = [], node; node = nodes[i]; i++) {
for (var j = 0, child; child = node.childNodes[j]; j++)
if (child.nodeType == 1 && child.tagName != '!') results.push(child);
}
return results;
},
adjacent: function(nodes) {
for (var i = 0, results = [], node; node = nodes[i]; i++) {
var next = this.nextElementSibling(node);
if (next) results.push(next);
}
return results;
},
laterSibling: function(nodes) {
var h = Selector.handlers;
for (var i = 0, results = [], node; node = nodes[i]; i++)
h.concat(results, Element.nextSiblings(node));
return results;
},
nextElementSibling: function(node) {
while (node = node.nextSibling)
if (node.nodeType == 1) return node;
return null;
},
previousElementSibling: function(node) {
while (node = node.previousSibling)
if (node.nodeType == 1) return node;
return null;
},
// TOKEN FUNCTIONS
tagName: function(nodes, root, tagName, combinator) {
var uTagName = tagName.toUpperCase();
var results = [], h = Selector.handlers;
if (nodes) {
if (combinator) {
// fastlane for ordinary descendant combinators
if (combinator == "descendant") {
for (var i = 0, node; node = nodes[i]; i++)
h.concat(results, node.getElementsByTagName(tagName));
return results;
} else nodes = this[combinator](nodes);
if (tagName == "*") return nodes;
}
for (var i = 0, node; node = nodes[i]; i++)
if (node.tagName.toUpperCase() === uTagName) results.push(node);
return results;
} else return root.getElementsByTagName(tagName);
},
id: function(nodes, root, id, combinator) {
var targetNode = $(id), h = Selector.handlers;
if (!targetNode) return [];
if (!nodes && root == document) return [targetNode];
if (nodes) {
if (combinator) {
if (combinator == 'child') {
for (var i = 0, node; node = nodes[i]; i++)
if (targetNode.parentNode == node) return [targetNode];
} else if (combinator == 'descendant') {
for (var i = 0, node; node = nodes[i]; i++)
if (Element.descendantOf(targetNode, node)) return [targetNode];
} else if (combinator == 'adjacent') {
for (var i = 0, node; node = nodes[i]; i++)
if (Selector.handlers.previousElementSibling(targetNode) == node)
return [targetNode];
} else nodes = h[combinator](nodes);
}
for (var i = 0, node; node = nodes[i]; i++)
if (node == targetNode) return [targetNode];
return [];
}
return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
},
className: function(nodes, root, className, combinator) {
if (nodes && combinator) nodes = this[combinator](nodes);
return Selector.handlers.byClassName(nodes, root, className);
},
byClassName: function(nodes, root, className) {
if (!nodes) nodes = Selector.handlers.descendant([root]);
var needle = ' ' + className + ' ';
for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
nodeClassName = node.className;
if (nodeClassName.length == 0) continue;
if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
results.push(node);
}
return results;
},
attrPresence: function(nodes, root, attr, combinator) {
if (!nodes) nodes = root.getElementsByTagName("*");
if (nodes && combinator) nodes = this[combinator](nodes);
var results = [];
for (var i = 0, node; node = nodes[i]; i++)
if (Element.hasAttribute(node, attr)) results.push(node);
return results;
},
attr: function(nodes, root, attr, value, operator, combinator) {
if (!nodes) nodes = root.getElementsByTagName("*");
if (nodes && combinator) nodes = this[combinator](nodes);
var handler = Selector.operators[operator], results = [];
for (var i = 0, node; node = nodes[i]; i++) {
var nodeValue = Element.readAttribute(node, attr);
if (nodeValue === null) continue;
if (handler(nodeValue, value)) results.push(node);
}
return results;
},
pseudo: function(nodes, name, value, root, combinator) {
if (nodes && combinator) nodes = this[combinator](nodes);
if (!nodes) nodes = root.getElementsByTagName("*");
return Selector.pseudos[name](nodes, value, root);
}
},
pseudos: {
'first-child': function(nodes, value, root) {
for (var i = 0, results = [], node; node = nodes[i]; i++) {
if (Selector.handlers.previousElementSibling(node)) continue;
results.push(node);
}
return results;
},
'last-child': function(nodes, value, root) {
for (var i = 0, results = [], node; node = nodes[i]; i++) {
if (Selector.handlers.nextElementSibling(node)) continue;
results.push(node);
}
return results;
},
'only-child': function(nodes, value, root) {
var h = Selector.handlers;
for (var i = 0, results = [], node; node = nodes[i]; i++)
if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
results.push(node);
return results;
},
'nth-child':        function(nodes, formula, root) {
return Selector.pseudos.nth(nodes, formula, root);
},
'nth-last-child':   function(nodes, formula, root) {
return Selector.pseudos.nth(nodes, formula, root, true);
},
'nth-of-type':      function(nodes, formula, root) {
return Selector.pseudos.nth(nodes, formula, root, false, true);
},
'nth-last-of-type': function(nodes, formula, root) {
return Selector.pseudos.nth(nodes, formula, root, true, true);
},
'first-of-type':    function(nodes, formula, root) {
return Selector.pseudos.nth(nodes, "1", root, false, true);
},
'last-of-type':     function(nodes, formula, root) {
return Selector.pseudos.nth(nodes, "1", root, true, true);
},
'only-of-type':     function(nodes, formula, root) {
var p = Selector.pseudos;
return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
},
// handles the an+b logic
getIndices: function(a, b, total) {
if (a == 0) return b > 0 ? [b] : [];
return $R(1, total).inject([], function(memo, i) {
if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
return memo;
});
},
// handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
nth: function(nodes, formula, root, reverse, ofType) {
if (nodes.length == 0) return [];
if (formula == 'even') formula = '2n+0';
if (formula == 'odd')  formula = '2n+1';
var h = Selector.handlers, results = [], indexed = [], m;
h.mark(nodes);
for (var i = 0, node; node = nodes[i]; i++) {
if (!node.parentNode._countedByPrototype) {
h.index(node.parentNode, reverse, ofType);
indexed.push(node.parentNode);
}
}
if (formula.match(/^\d+$/)) { // just a number
formula = Number(formula);
for (var i = 0, node; node = nodes[i]; i++)
if (node.nodeIndex == formula) results.push(node);
} else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
if (m[1] == "-") m[1] = -1;
var a = m[1] ? Number(m[1]) : 1;
var b = m[2] ? Number(m[2]) : 0;
var indices = Selector.pseudos.getIndices(a, b, nodes.length);
for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
for (var j = 0; j < l; j++)
if (node.nodeIndex == indices[j]) results.push(node);
}
}
h.unmark(nodes);
h.unmark(indexed);
return results;
},
'empty': function(nodes, value, root) {
for (var i = 0, results = [], node; node = nodes[i]; i++) {
// IE treats comments as element nodes
if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
results.push(node);
}
return results;
},
'not': function(nodes, selector, root) {
var h = Selector.handlers, selectorType, m;
var exclusions = new Selector(selector).findElements(root);
h.mark(exclusions);
for (var i = 0, results = [], node; node = nodes[i]; i++)
if (!node._countedByPrototype) results.push(node);
h.unmark(exclusions);
return results;
},
'enabled': function(nodes, value, root) {
for (var i = 0, results = [], node; node = nodes[i]; i++)
if (!node.disabled) results.push(node);
return results;
},
'disabled': function(nodes, value, root) {
for (var i = 0, results = [], node; node = nodes[i]; i++)
if (node.disabled) results.push(node);
return results;
},
'checked': function(nodes, value, root) {
for (var i = 0, results = [], node; node = nodes[i]; i++)
if (node.checked) results.push(node);
return results;
}
},
operators: {
'=':  function(nv, v) { return nv == v; },
'!=': function(nv, v) { return nv != v; },
'^=': function(nv, v) { return nv.startsWith(v); },
'$=': function(nv, v) { return nv.endsWith(v); },
'*=': function(nv, v) { return nv.include(v); },
'~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
'|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
},
split: function(expression) {
var expressions = [];
expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
expressions.push(m[1].strip());
});
return expressions;
},
matchElements: function(elements, expression) {
var matches = $$(expression), h = Selector.handlers;
h.mark(matches);
for (var i = 0, results = [], element; element = elements[i]; i++)
if (element._countedByPrototype) results.push(element);
h.unmark(matches);
return results;
},
findElement: function(elements, expression, index) {
if (Object.isNumber(expression)) {
index = expression; expression = false;
}
return Selector.matchElements(elements, expression || '*')[index || 0];
},
findChildElements: function(element, expressions) {
expressions = Selector.split(expressions.join(','));
var results = [], h = Selector.handlers;
for (var i = 0, l = expressions.length, selector; i < l; i++) {
selector = new Selector(expressions[i].strip());
h.concat(results, selector.findElements(element));
}
return (l > 1) ? h.unique(results) : results;
}
});
if (Prototype.Browser.IE) {
Object.extend(Selector.handlers, {
// IE returns comment nodes on getElementsByTagName("*").
// Filter them out.
concat: function(a, b) {
for (var i = 0, node; node = b[i]; i++)
if (node.tagName !== "!") a.push(node);
return a;
},
// IE improperly serializes _countedByPrototype in (inner|outer)HTML.
unmark: function(nodes) {
for (var i = 0, node; node = nodes[i]; i++)
node.removeAttribute('_countedByPrototype');
return nodes;
}
});
}
function $$() {
return Selector.findChildElements(document, $A(arguments));
}
var Form = {
reset: function(form) {
$(form).reset();
return form;
},
serializeElements: function(elements, options) {
if (typeof options != 'object') options = { hash: !!options };
else if (Object.isUndefined(options.hash)) options.hash = true;
var key, value, submitted = false, submit = options.submit;
var data = elements.inject({ }, function(result, element) {
if (!element.disabled && element.name) {
key = element.name; value = $(element).getValue();
if (value != null && (element.type != 'submit' || (!submitted &&
submit !== false && (!submit || key == submit) && (submitted = true)))) {
if (key in result) {
// a key is already present; construct an array of values
if (!Object.isArray(result[key])) result[key] = [result[key]];
result[key].push(value);
}
else result[key] = value;
}
}
return result;
});
return options.hash ? data : Object.toQueryString(data);
}
};
Form.Methods = {
serialize: function(form, options) {
return Form.serializeElements(Form.getElements(form), options);
},
getElements: function(form) {
return $A($(form).getElementsByTagName('*')).inject([],
function(elements, child) {
if (Form.Element.Serializers[child.tagName.toLowerCase()])
elements.push(Element.extend(child));
return elements;
}
);
},
getInputs: function(form, typeName, name) {
form = $(form);
var inputs = form.getElementsByTagName('input');
if (!typeName && !name) return $A(inputs).map(Element.extend);
for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
var input = inputs[i];
if ((typeName && input.type != typeName) || (name && input.name != name))
continue;
matchingInputs.push(Element.extend(input));
}
return matchingInputs;
},
disable: function(form) {
form = $(form);
Form.getElements(form).invoke('disable');
return form;
},
enable: function(form) {
form = $(form);
Form.getElements(form).invoke('enable');
return form;
},
findFirstElement: function(form) {
var elements = $(form).getElements().findAll(function(element) {
return 'hidden' != element.type && !element.disabled;
});
var firstByIndex = elements.findAll(function(element) {
return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
}).sortBy(function(element) { return element.tabIndex }).first();
return firstByIndex ? firstByIndex : elements.find(function(element) {
return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
});
},
focusFirstElement: function(form) {
form = $(form);
form.findFirstElement().activate();
return form;
},
request: function(form, options) {
form = $(form), options = Object.clone(options || { });
var params = options.parameters, action = form.readAttribute('action') || '';
if (action.blank()) action = window.location.href;
options.parameters = form.serialize(true);
if (params) {
if (Object.isString(params)) params = params.toQueryParams();
Object.extend(options.parameters, params);
}
if (form.hasAttribute('method') && !options.method)
options.method = form.method;
return new Ajax.Request(action, options);
}
};
/*--------------------------------------------------------------------------*/
Form.Element = {
focus: function(element) {
$(element).focus();
return element;
},
select: function(element) {
$(element).select();
return element;
}
};
Form.Element.Methods = {
serialize: function(element) {
element = $(element);
if (!element.disabled && element.name) {
var value = element.getValue();
if (value != undefined) {
var pair = { };
pair[element.name] = value;
return Object.toQueryString(pair);
}
}
return '';
},
getValue: function(element) {
element = $(element);
var method = element.tagName.toLowerCase();
return Form.Element.Serializers[method](element);
},
setValue: function(element, value) {
element = $(element);
var method = element.tagName.toLowerCase();
Form.Element.Serializers[method](element, value);
return element;
},
clear: function(element) {
$(element).value = '';
return element;
},
present: function(element) {
return $(element).value != '';
},
activate: function(element) {
element = $(element);
try {
element.focus();
if (element.select && (element.tagName.toLowerCase() != 'input' ||
!['button', 'reset', 'submit'].include(element.type)))
element.select();
} catch (e) { }
return element;
},
disable: function(element) {
element = $(element);
element.blur();
element.disabled = true;
return element;
},
enable: function(element) {
element = $(element);
element.disabled = false;
return element;
}
};
/*--------------------------------------------------------------------------*/
var Field = Form.Element;
var $F = Form.Element.Methods.getValue;
/*--------------------------------------------------------------------------*/
Form.Element.Serializers = {
input: function(element, value) {
switch (element.type.toLowerCase()) {
case 'checkbox':
case 'radio':
return Form.Element.Serializers.inputSelector(element, value);
default:
return Form.Element.Serializers.textarea(element, value);
}
},
inputSelector: function(element, value) {
if (Object.isUndefined(value)) return element.checked ? element.value : null;
else element.checked = !!value;
},
textarea: function(element, value) {
if (Object.isUndefined(value)) return element.value;
else element.value = value;
},
select: function(element, index) {
if (Object.isUndefined(index))
return this[element.type == 'select-one' ?
'selectOne' : 'selectMany'](element);
else {
var opt, value, single = !Object.isArray(index);
for (var i = 0, length = element.length; i < length; i++) {
opt = element.options[i];
value = this.optionValue(opt);
if (single) {
if (value == index) {
opt.selected = true;
return;
}
}
else opt.selected = index.include(value);
}
}
},
selectOne: function(element) {
var index = element.selectedIndex;
return index >= 0 ? this.optionValue(element.options[index]) : null;
},
selectMany: function(element) {
var values, length = element.length;
if (!length) return null;
for (var i = 0, values = []; i < length; i++) {
var opt = element.options[i];
if (opt.selected) values.push(this.optionValue(opt));
}
return values;
},
optionValue: function(opt) {
// extend element because hasAttribute may not be native
return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
}
};
/*--------------------------------------------------------------------------*/
Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
initialize: function($super, element, frequency, callback) {
$super(callback, frequency);
this.element   = $(element);
this.lastValue = this.getValue();
},
execute: function() {
var value = this.getValue();
if (Object.isString(this.lastValue) && Object.isString(value) ?
this.lastValue != value : String(this.lastValue) != String(value)) {
this.callback(this.element, value);
this.lastValue = value;
}
}
});
Form.Element.Observer = Class.create(Abstract.TimedObserver, {
getValue: function() {
return Form.Element.getValue(this.element);
}
});
Form.Observer = Class.create(Abstract.TimedObserver, {
getValue: function() {
return Form.serialize(this.element);
}
});
/*--------------------------------------------------------------------------*/
Abstract.EventObserver = Class.create({
initialize: function(element, callback) {
this.element  = $(element);
this.callback = callback;
this.lastValue = this.getValue();
if (this.element.tagName.toLowerCase() == 'form')
this.registerFormCallbacks();
else
this.registerCallback(this.element);
},
onElementEvent: function() {
var value = this.getValue();
if (this.lastValue != value) {
this.callback(this.element, value);
this.lastValue = value;
}
},
registerFormCallbacks: function() {
Form.getElements(this.element).each(this.registerCallback, this);
},
registerCallback: function(element) {
if (element.type) {
switch (element.type.toLowerCase()) {
case 'checkbox':
case 'radio':
Event.observe(element, 'click', this.onElementEvent.bind(this));
break;
default:
Event.observe(element, 'change', this.onElementEvent.bind(this));
break;
}
}
}
});
Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
getValue: function() {
return Form.Element.getValue(this.element);
}
});
Form.EventObserver = Class.create(Abstract.EventObserver, {
getValue: function() {
return Form.serialize(this.element);
}
});
if (!window.Event) var Event = { };
Object.extend(Event, {
KEY_BACKSPACE: 8,
KEY_TAB:       9,
KEY_RETURN:   13,
KEY_ESC:      27,
KEY_LEFT:     37,
KEY_UP:       38,
KEY_RIGHT:    39,
KEY_DOWN:     40,
KEY_DELETE:   46,
KEY_HOME:     36,
KEY_END:      35,
KEY_PAGEUP:   33,
KEY_PAGEDOWN: 34,
KEY_INSERT:   45,
cache: { },
relatedTarget: function(event) {
var element;
switch(event.type) {
case 'mouseover': element = event.fromElement; break;
case 'mouseout':  element = event.toElement;   break;
default: return null;
}
return Element.extend(element);
}
});
Event.Methods = (function() {
var isButton;
if (Prototype.Browser.IE) {
var buttonMap = { 0: 1, 1: 4, 2: 2 };
isButton = function(event, code) {
return event.button == buttonMap[code];
};
} else if (Prototype.Browser.WebKit) {
isButton = function(event, code) {
switch (code) {
case 0: return event.which == 1 && !event.metaKey;
case 1: return event.which == 1 && event.metaKey;
default: return false;
}
};
} else {
isButton = function(event, code) {
return event.which ? (event.which === code + 1) : (event.button === code);
};
}
return {
isLeftClick:   function(event) { return isButton(event, 0) },
isMiddleClick: function(event) { return isButton(event, 1) },
isRightClick:  function(event) { return isButton(event, 2) },
element: function(event) {
var node = Event.extend(event).target;
return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
},
findElement: function(event, expression) {
var element = Event.element(event);
if (!expression) return element;
var elements = [element].concat(element.ancestors());
return Selector.findElement(elements, expression, 0);
},
pointer: function(event) {
return {
x: event.pageX || (event.clientX +
(document.documentElement.scrollLeft || document.body.scrollLeft)),
y: event.pageY || (event.clientY +
(document.documentElement.scrollTop || document.body.scrollTop))
};
},
pointerX: function(event) { return Event.pointer(event).x },
pointerY: function(event) { return Event.pointer(event).y },
stop: function(event) {
Event.extend(event);
event.preventDefault();
event.stopPropagation();
event.stopped = true;
}
};
})();
Event.extend = (function() {
var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
m[name] = Event.Methods[name].methodize();
return m;
});
if (Prototype.Browser.IE) {
Object.extend(methods, {
stopPropagation: function() { this.cancelBubble = true },
preventDefault:  function() { this.returnValue = false },
inspect: function() { return "[object Event]" }
});
return function(event) {
if (!event) return false;
if (event._extendedByPrototype) return event;
event._extendedByPrototype = Prototype.emptyFunction;
var pointer = Event.pointer(event);
Object.extend(event, {
target: event.srcElement,
relatedTarget: Event.relatedTarget(event),
pageX:  pointer.x,
pageY:  pointer.y
});
return Object.extend(event, methods);
};
} else {
Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
Object.extend(Event.prototype, methods);
return Prototype.K;
}
})();
Object.extend(Event, (function() {
var cache = Event.cache;
function getEventID(element) {
if (element._prototypeEventID) return element._prototypeEventID[0];
arguments.callee.id = arguments.callee.id || 1;
return element._prototypeEventID = [++arguments.callee.id];
}
function getDOMEventName(eventName) {
if (eventName && eventName.include(':')) return "dataavailable";
return eventName;
}
function getCacheForID(id) {
return cache[id] = cache[id] || { };
}
function getWrappersForEventName(id, eventName) {
var c = getCacheForID(id);
return c[eventName] = c[eventName] || [];
}
function createWrapper(element, eventName, handler) {
var id = getEventID(element);
var c = getWrappersForEventName(id, eventName);
if (c.pluck("handler").include(handler)) return false;
var wrapper = function(event) {
if (!Event || !Event.extend ||
(event.eventName && event.eventName != eventName))
return false;
Event.extend(event);
handler.call(element, event);
};
wrapper.handler = handler;
c.push(wrapper);
return wrapper;
}
function findWrapper(id, eventName, handler) {
var c = getWrappersForEventName(id, eventName);
return c.find(function(wrapper) { return wrapper.handler == handler });
}
function destroyWrapper(id, eventName, handler) {
var c = getCacheForID(id);
if (!c[eventName]) return false;
c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
}
function destroyCache() {
for (var id in cache)
for (var eventName in cache[id])
cache[id][eventName] = null;
}
if (window.attachEvent) {
window.attachEvent("onunload", destroyCache);
}
return {
observe: function(element, eventName, handler) {
element = $(element);
var name = getDOMEventName(eventName);
var wrapper = createWrapper(element, eventName, handler);
if (!wrapper) return element;
if (element.addEventListener) {
element.addEventListener(name, wrapper, false);
} else {
element.attachEvent("on" + name, wrapper);
}
return element;
},
stopObserving: function(element, eventName, handler) {
element = $(element);
var id = getEventID(element), name = getDOMEventName(eventName);
if (!handler && eventName) {
getWrappersForEventName(id, eventName).each(function(wrapper) {
element.stopObserving(eventName, wrapper.handler);
});
return element;
} else if (!eventName) {
Object.keys(getCacheForID(id)).each(function(eventName) {
element.stopObserving(eventName);
});
return element;
}
var wrapper = findWrapper(id, eventName, handler);
if (!wrapper) return element;
if (element.removeEventListener) {
element.removeEventListener(name, wrapper, false);
} else {
element.detachEvent("on" + name, wrapper);
}
destroyWrapper(id, eventName, handler);
return element;
},
fire: function(element, eventName, memo) {
element = $(element);
if (element == document && document.createEvent && !element.dispatchEvent)
element = document.documentElement;
var event;
if (document.createEvent) {
event = document.createEvent("HTMLEvents");
event.initEvent("dataavailable", true, true);
} else {
event = document.createEventObject();
event.eventType = "ondataavailable";
}
event.eventName = eventName;
event.memo = memo || { };
if (document.createEvent) {
element.dispatchEvent(event);
} else {
element.fireEvent(event.eventType, event);
}
return Event.extend(event);
}
};
})());
Object.extend(Event, Event.Methods);
Element.addMethods({
fire:          Event.fire,
observe:       Event.observe,
stopObserving: Event.stopObserving
});
Object.extend(document, {
fire:          Element.Methods.fire.methodize(),
observe:       Element.Methods.observe.methodize(),
stopObserving: Element.Methods.stopObserving.methodize(),
loaded:        false
});
(function() {
/* Support for the DOMContentLoaded event is based on work by Dan Webb,
Matthias Miller, Dean Edwards and John Resig. */
var timer;
function fireContentLoadedEvent() {
if (document.loaded) return;
if (timer) window.clearInterval(timer);
document.fire("dom:loaded");
document.loaded = true;
}
if (document.addEventListener) {
if (Prototype.Browser.WebKit) {
timer = window.setInterval(function() {
if (/loaded|complete/.test(document.readyState))
fireContentLoadedEvent();
}, 0);
Event.observe(window, "load", fireContentLoadedEvent);
} else {
document.addEventListener("DOMContentLoaded",
fireContentLoadedEvent, false);
}
} else {
document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
$("__onDOMContentLoaded").onreadystatechange = function() {
if (this.readyState == "complete") {
this.onreadystatechange = null;
fireContentLoadedEvent();
}
};
}
})();
/*------------------------------- DEPRECATED -------------------------------*/
Hash.toQueryString = Object.toQueryString;
var Toggle = { display: Element.toggle };
Element.Methods.childOf = Element.Methods.descendantOf;
var Insertion = {
Before: function(element, content) {
return Element.insert(element, {before:content});
},
Top: function(element, content) {
return Element.insert(element, {top:content});
},
Bottom: function(element, content) {
return Element.insert(element, {bottom:content});
},
After: function(element, content) {
return Element.insert(element, {after:content});
}
};
var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
// This should be moved to script.aculo.us; notice the deprecated methods
// further below, that map to the newer Element methods.
var Position = {
// set to true if needed, warning: firefox performance problems
// NOT neeeded for page scrolling, only if draggable contained in
// scrollable elements
includeScrollOffsets: false,
// must be called before calling withinIncludingScrolloffset, every time the
// page is scrolled
prepare: function() {
this.deltaX =  window.pageXOffset
|| document.documentElement.scrollLeft
|| document.body.scrollLeft
|| 0;
this.deltaY =  window.pageYOffset
|| document.documentElement.scrollTop
|| document.body.scrollTop
|| 0;
},
// caches x/y coordinate pair to use with overlap
within: function(element, x, y) {
if (this.includeScrollOffsets)
return this.withinIncludingScrolloffsets(element, x, y);
this.xcomp = x;
this.ycomp = y;
this.offset = Element.cumulativeOffset(element);
return (y >= this.offset[1] &&
y <  this.offset[1] + element.offsetHeight &&
x >= this.offset[0] &&
x <  this.offset[0] + element.offsetWidth);
},
withinIncludingScrolloffsets: function(element, x, y) {
var offsetcache = Element.cumulativeScrollOffset(element);
this.xcomp = x + offsetcache[0] - this.deltaX;
this.ycomp = y + offsetcache[1] - this.deltaY;
this.offset = Element.cumulativeOffset(element);
return (this.ycomp >= this.offset[1] &&
this.ycomp <  this.offset[1] + element.offsetHeight &&
this.xcomp >= this.offset[0] &&
this.xcomp <  this.offset[0] + element.offsetWidth);
},
// within must be called directly before
overlap: function(mode, element) {
if (!mode) return 0;
if (mode == 'vertical')
return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
element.offsetHeight;
if (mode == 'horizontal')
return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
element.offsetWidth;
},
// Deprecation layer -- use newer Element methods now (1.5.2).
cumulativeOffset: Element.Methods.cumulativeOffset,
positionedOffset: Element.Methods.positionedOffset,
absolutize: function(element) {
Position.prepare();
return Element.absolutize(element);
},
relativize: function(element) {
Position.prepare();
return Element.relativize(element);
},
realOffset: Element.Methods.cumulativeScrollOffset,
offsetParent: Element.Methods.getOffsetParent,
page: Element.Methods.viewportOffset,
clone: function(source, target, options) {
options = options || { };
return Element.clonePosition(target, source, options);
}
};
/*--------------------------------------------------------------------------*/
if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
function iter(name) {
return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
}
instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
function(element, className) {
className = className.toString().strip();
var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
} : function(element, className) {
className = className.toString().strip();
var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
if (!classNames && !className) return elements;
var nodes = $(element).getElementsByTagName('*');
className = ' ' + className + ' ';
for (var i = 0, child, cn; child = nodes[i]; i++) {
if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
(classNames && classNames.all(function(name) {
return !name.toString().blank() && cn.include(' ' + name + ' ');
}))))
elements.push(Element.extend(child));
}
return elements;
};
return function(className, parentElement) {
return $(parentElement || document.body).getElementsByClassName(className);
};
}(Element.Methods);
/*--------------------------------------------------------------------------*/
Element.ClassNames = Class.create();
Element.ClassNames.prototype = {
initialize: function(element) {
this.element = $(element);
},
_each: function(iterator) {
this.element.className.split(/\s+/).select(function(name) {
return name.length > 0;
})._each(iterator);
},
set: function(className) {
this.element.className = className;
},
add: function(classNameToAdd) {
if (this.include(classNameToAdd)) return;
this.set($A(this).concat(classNameToAdd).join(' '));
},
remove: function(classNameToRemove) {
if (!this.include(classNameToRemove)) return;
this.set($A(this).without(classNameToRemove).join(' '));
},
toString: function() {
return $A(this).join(' ');
}
};
Object.extend(Element.ClassNames.prototype, Enumerable);
/*--------------------------------------------------------------------------*/
Element.addMethods();
// script.aculo.us scriptaculous.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// For details, see the script.aculo.us web site: http://script.aculo.us/
var Scriptaculous = {
Version: '1.8.1',
require: function(libraryName) {
// inserting via DOM fails in Safari 2.0, so brute force approach
document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
},
REQUIRED_PROTOTYPE: '1.6.0',
load: function() {
function convertVersionString(versionString){
var r = versionString.split('.');
return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
}
if((typeof Prototype=='undefined') || 
(typeof Element == 'undefined') || 
(typeof Element.Methods=='undefined') ||
(convertVersionString(Prototype.Version) < 
convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
throw("script.aculo.us requires the Prototype JavaScript framework >= " +
Scriptaculous.REQUIRED_PROTOTYPE);
$A(document.getElementsByTagName("script")).findAll( function(s) {
return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
}).each( function(s) {
var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
var includes = s.src.match(/\?.*load=([a-z,]*)/);
(includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
function(include) { Scriptaculous.require(path+include+'.js') });
});
}
}
Scriptaculous.load();
// script.aculo.us effects.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
// 
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/ 
// converts rgb() and #xxx to #xxxxxx format,  
// returns self (or first argument) if not convertable  
String.prototype.parseColor = function() {  
var color = '#';
if (this.slice(0,4) == 'rgb(') {  
var cols = this.slice(4,this.length-1).split(',');  
var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
} else {  
if (this.slice(0,1) == '#') {  
if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
if (this.length==7) color = this.toLowerCase();  
}  
}  
return (color.length==7 ? color : (arguments[0] || this));  
};
/*--------------------------------------------------------------------------*/
Element.collectTextNodes = function(element) {  
return $A($(element).childNodes).collect( function(node) {
return (node.nodeType==3 ? node.nodeValue : 
(node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
}).flatten().join('');
};
Element.collectTextNodesIgnoreClass = function(element, className) {  
return $A($(element).childNodes).collect( function(node) {
return (node.nodeType==3 ? node.nodeValue : 
((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
Element.collectTextNodesIgnoreClass(node, className) : ''));
}).flatten().join('');
};
Element.setContentZoom = function(element, percent) {
element = $(element);  
element.setStyle({fontSize: (percent/100) + 'em'});   
if (Prototype.Browser.WebKit) window.scrollBy(0,0);
return element;
};
Element.getInlineOpacity = function(element){
return $(element).style.opacity || '';
};
Element.forceRerendering = function(element) {
try {
element = $(element);
var n = document.createTextNode(' ');
element.appendChild(n);
element.removeChild(n);
} catch(e) { }
};
/*--------------------------------------------------------------------------*/
var Effect = {
_elementDoesNotExistError: {
name: 'ElementDoesNotExistError',
message: 'The specified DOM element does not exist, but is required for this effect to operate'
},
Transitions: {
linear: Prototype.K,
sinoidal: function(pos) {
return (-Math.cos(pos*Math.PI)/2) + 0.5;
},
reverse: function(pos) {
return 1-pos;
},
flicker: function(pos) {
var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
return pos > 1 ? 1 : pos;
},
wobble: function(pos) {
return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
},
pulse: function(pos, pulses) { 
pulses = pulses || 5; 
return (
((pos % (1/pulses)) * pulses).round() == 0 ? 
((pos * pulses * 2) - (pos * pulses * 2).floor()) : 
1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
);
},
spring: function(pos) { 
return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 
},
none: function(pos) {
return 0;
},
full: function(pos) {
return 1;
}
},
DefaultOptions: {
duration:   1.0,   // seconds
fps:        100,   // 100= assume 66fps max.
sync:       false, // true for combining
from:       0.0,
to:         1.0,
delay:      0.0,
queue:      'parallel'
},
tagifyText: function(element) {
var tagifyStyle = 'position:relative';
if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
element = $(element);
$A(element.childNodes).each( function(child) {
if (child.nodeType==3) {
child.nodeValue.toArray().each( function(character) {
element.insertBefore(
new Element('span', {style: tagifyStyle}).update(
character == ' ' ? String.fromCharCode(160) : character), 
child);
});
Element.remove(child);
}
});
},
multiple: function(element, effect) {
var elements;
if (((typeof element == 'object') || 
Object.isFunction(element)) && 
(element.length))
elements = element;
else
elements = $(element).childNodes;
var options = Object.extend({
speed: 0.1,
delay: 0.0
}, arguments[2] || { });
var masterDelay = options.delay;
$A(elements).each( function(element, index) {
new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
});
},
PAIRS: {
'slide':  ['SlideDown','SlideUp'],
'blind':  ['BlindDown','BlindUp'],
'appear': ['Appear','Fade']
},
toggle: function(element, effect) {
element = $(element);
effect = (effect || 'appear').toLowerCase();
var options = Object.extend({
queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
}, arguments[2] || { });
Effect[element.visible() ? 
Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
}
};
Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
/* ------------- core effects ------------- */
Effect.ScopedQueue = Class.create(Enumerable, {
initialize: function() {
this.effects  = [];
this.interval = null;    
},
_each: function(iterator) {
this.effects._each(iterator);
},
add: function(effect) {
var timestamp = new Date().getTime();
var position = Object.isString(effect.options.queue) ? 
effect.options.queue : effect.options.queue.position;
switch(position) {
case 'front':
// move unstarted effects after this effect  
this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
e.startOn  += effect.finishOn;
e.finishOn += effect.finishOn;
});
break;
case 'with-last':
timestamp = this.effects.pluck('startOn').max() || timestamp;
break;
case 'end':
// start effect after last queued effect has finished
timestamp = this.effects.pluck('finishOn').max() || timestamp;
break;
}
effect.startOn  += timestamp;
effect.finishOn += timestamp;
if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
this.effects.push(effect);
if (!this.interval)
this.interval = setInterval(this.loop.bind(this), 15);
},
remove: function(effect) {
this.effects = this.effects.reject(function(e) { return e==effect });
if (this.effects.length == 0) {
clearInterval(this.interval);
this.interval = null;
}
},
loop: function() {
var timePos = new Date().getTime();
for(var i=0, len=this.effects.length;i<len;i++) 
this.effects[i] && this.effects[i].loop(timePos);
}
});
Effect.Queues = {
instances: $H(),
get: function(queueName) {
if (!Object.isString(queueName)) return queueName;
return this.instances.get(queueName) ||
this.instances.set(queueName, new Effect.ScopedQueue());
}
};
Effect.Queue = Effect.Queues.get('global');
Effect.Base = Class.create({
position: null,
start: function(options) {
function codeForEvent(options,eventName){
return (
(options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
(options[eventName] ? 'this.options.'+eventName+'(this);' : '')
);
}
if (options && options.transition === false) options.transition = Effect.Transitions.linear;
this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
this.currentFrame = 0;
this.state        = 'idle';
this.startOn      = this.options.delay*1000;
this.finishOn     = this.startOn+(this.options.duration*1000);
this.fromToDelta  = this.options.to-this.options.from;
this.totalTime    = this.finishOn-this.startOn;
this.totalFrames  = this.options.fps*this.options.duration;
eval('this.render = function(pos){ '+
'if (this.state=="idle"){this.state="running";'+
codeForEvent(this.options,'beforeSetup')+
(this.setup ? 'this.setup();':'')+ 
codeForEvent(this.options,'afterSetup')+
'};if (this.state=="running"){'+
'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
'this.position=pos;'+
codeForEvent(this.options,'beforeUpdate')+
(this.update ? 'this.update(pos);':'')+
codeForEvent(this.options,'afterUpdate')+
'}}');
this.event('beforeStart');
if (!this.options.sync)
Effect.Queues.get(Object.isString(this.options.queue) ? 
'global' : this.options.queue.scope).add(this);
},
loop: function(timePos) {
if (timePos >= this.startOn) {
if (timePos >= this.finishOn) {
this.render(1.0);
this.cancel();
this.event('beforeFinish');
if (this.finish) this.finish(); 
this.event('afterFinish');
return;  
}
var pos   = (timePos - this.startOn) / this.totalTime,
frame = (pos * this.totalFrames).round();
if (frame > this.currentFrame) {
this.render(pos);
this.currentFrame = frame;
}
}
},
cancel: function() {
if (!this.options.sync)
Effect.Queues.get(Object.isString(this.options.queue) ? 
'global' : this.options.queue.scope).remove(this);
this.state = 'finished';
},
event: function(eventName) {
if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
if (this.options[eventName]) this.options[eventName](this);
},
inspect: function() {
var data = $H();
for(property in this)
if (!Object.isFunction(this[property])) data.set(property, this[property]);
return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
}
});
Effect.Parallel = Class.create(Effect.Base, {
initialize: function(effects) {
this.effects = effects || [];
this.start(arguments[1]);
},
update: function(position) {
this.effects.invoke('render', position);
},
finish: function(position) {
this.effects.each( function(effect) {
effect.render(1.0);
effect.cancel();
effect.event('beforeFinish');
if (effect.finish) effect.finish(position);
effect.event('afterFinish');
});
}
});
Effect.Tween = Class.create(Effect.Base, {
initialize: function(object, from, to) {
object = Object.isString(object) ? $(object) : object;
var args = $A(arguments), method = args.last(), 
options = args.length == 5 ? args[3] : null;
this.method = Object.isFunction(method) ? method.bind(object) :
Object.isFunction(object[method]) ? object[method].bind(object) : 
function(value) { object[method] = value };
this.start(Object.extend({ from: from, to: to }, options || { }));
},
update: function(position) {
this.method(position);
}
});
Effect.Event = Class.create(Effect.Base, {
initialize: function() {
this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
},
update: Prototype.emptyFunction
});
Effect.Opacity = Class.create(Effect.Base, {
initialize: function(element) {
this.element = $(element);
if (!this.element) throw(Effect._elementDoesNotExistError);
// make this work on IE on elements without 'layout'
if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
this.element.setStyle({zoom: 1});
var options = Object.extend({
from: this.element.getOpacity() || 0.0,
to:   1.0
}, arguments[1] || { });
this.start(options);
},
update: function(position) {
this.element.setOpacity(position);
}
});
Effect.Move = Class.create(Effect.Base, {
initialize: function(element) {
this.element = $(element);
if (!this.element) throw(Effect._elementDoesNotExistError);
var options = Object.extend({
x:    0,
y:    0,
mode: 'relative'
}, arguments[1] || { });
this.start(options);
},
setup: function() {
this.element.makePositioned();
this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
if (this.options.mode == 'absolute') {
this.options.x = this.options.x - this.originalLeft;
this.options.y = this.options.y - this.originalTop;
}
},
update: function(position) {
this.element.setStyle({
left: (this.options.x  * position + this.originalLeft).round() + 'px',
top:  (this.options.y  * position + this.originalTop).round()  + 'px'
});
}
});
// for backwards compatibility
Effect.MoveBy = function(element, toTop, toLeft) {
return new Effect.Move(element, 
Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
};
Effect.Scale = Class.create(Effect.Base, {
initialize: function(element, percent) {
this.element = $(element);
if (!this.element) throw(Effect._elementDoesNotExistError);
var options = Object.extend({
scaleX: true,
scaleY: true,
scaleContent: true,
scaleFromCenter: false,
scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
scaleFrom: 100.0,
scaleTo:   percent
}, arguments[2] || { });
this.start(options);
},
setup: function() {
this.restoreAfterFinish = this.options.restoreAfterFinish || false;
this.elementPositioning = this.element.getStyle('position');
this.originalStyle = { };
['top','left','width','height','fontSize'].each( function(k) {
this.originalStyle[k] = this.element.style[k];
}.bind(this));
this.originalTop  = this.element.offsetTop;
this.originalLeft = this.element.offsetLeft;
var fontSize = this.element.getStyle('font-size') || '100%';
['em','px','%','pt'].each( function(fontSizeType) {
if (fontSize.indexOf(fontSizeType)>0) {
this.fontSize     = parseFloat(fontSize);
this.fontSizeType = fontSizeType;
}
}.bind(this));
this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
this.dims = null;
if (this.options.scaleMode=='box')
this.dims = [this.element.offsetHeight, this.element.offsetWidth];
if (/^content/.test(this.options.scaleMode))
this.dims = [this.element.scrollHeight, this.element.scrollWidth];
if (!this.dims)
this.dims = [this.options.scaleMode.originalHeight,
this.options.scaleMode.originalWidth];
},
update: function(position) {
var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
if (this.options.scaleContent && this.fontSize)
this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
},
finish: function(position) {
if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
},
setDimensions: function(height, width) {
var d = { };
if (this.options.scaleX) d.width = width.round() + 'px';
if (this.options.scaleY) d.height = height.round() + 'px';
if (this.options.scaleFromCenter) {
var topd  = (height - this.dims[0])/2;
var leftd = (width  - this.dims[1])/2;
if (this.elementPositioning == 'absolute') {
if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
} else {
if (this.options.scaleY) d.top = -topd + 'px';
if (this.options.scaleX) d.left = -leftd + 'px';
}
}
this.element.setStyle(d);
}
});
Effect.Highlight = Class.create(Effect.Base, {
initialize: function(element) {
this.element = $(element);
if (!this.element) throw(Effect._elementDoesNotExistError);
var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
this.start(options);
},
setup: function() {
// Prevent executing on elements not in the layout flow
if (this.element.getStyle('display')=='none') { this.cancel(); return; }
// Disable background image during the effect
this.oldStyle = { };
if (!this.options.keepBackgroundImage) {
this.oldStyle.backgroundImage = this.element.getStyle('background-image');
this.element.setStyle({backgroundImage: 'none'});
}
if (!this.options.endcolor)
this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
if (!this.options.restorecolor)
this.options.restorecolor = this.element.getStyle('background-color');
// init color calculations
this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
},
update: function(position) {
this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
},
finish: function() {
this.element.setStyle(Object.extend(this.oldStyle, {
backgroundColor: this.options.restorecolor
}));
}
});
Effect.ScrollTo = function(element) {
var options = arguments[1] || { },
scrollOffsets = document.viewport.getScrollOffsets(),
elementOffsets = $(element).cumulativeOffset(),
max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();  
if (options.offset) elementOffsets[1] += options.offset;
return new Effect.Tween(null,
scrollOffsets.top,
elementOffsets[1] > max ? max : elementOffsets[1],
options,
function(p){ scrollTo(scrollOffsets.left, p.round()) }
);
};
/* ------------- combination effects ------------- */
Effect.Fade = function(element) {
element = $(element);
var oldOpacity = element.getInlineOpacity();
var options = Object.extend({
from: element.getOpacity() || 1.0,
to:   0.0,
afterFinishInternal: function(effect) { 
if (effect.options.to!=0) return;
effect.element.hide().setStyle({opacity: oldOpacity}); 
}
}, arguments[1] || { });
return new Effect.Opacity(element,options);
};
Effect.Appear = function(element) {
element = $(element);
var options = Object.extend({
from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
to:   1.0,
// force Safari to render floated elements properly
afterFinishInternal: function(effect) {
effect.element.forceRerendering();
},
beforeSetup: function(effect) {
effect.element.setOpacity(effect.options.from).show(); 
}}, arguments[1] || { });
return new Effect.Opacity(element,options);
};
Effect.Puff = function(element) {
element = $(element);
var oldStyle = { 
opacity: element.getInlineOpacity(), 
position: element.getStyle('position'),
top:  element.style.top,
left: element.style.left,
width: element.style.width,
height: element.style.height
};
return new Effect.Parallel(
[ new Effect.Scale(element, 200, 
{ sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
Object.extend({ duration: 1.0, 
beforeSetupInternal: function(effect) {
Position.absolutize(effect.effects[0].element)
},
afterFinishInternal: function(effect) {
effect.effects[0].element.hide().setStyle(oldStyle); }
}, arguments[1] || { })
);
};
Effect.BlindUp = function(element) {
element = $(element);
element.makeClipping();
return new Effect.Scale(element, 0,
Object.extend({ scaleContent: false, 
scaleX: false, 
restoreAfterFinish: true,
afterFinishInternal: function(effect) {
effect.element.hide().undoClipping();
} 
}, arguments[1] || { })
);
};
Effect.BlindDown = function(element) {
element = $(element);
var elementDimensions = element.getDimensions();
return new Effect.Scale(element, 100, Object.extend({ 
scaleContent: false, 
scaleX: false,
scaleFrom: 0,
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
restoreAfterFinish: true,
afterSetup: function(effect) {
effect.element.makeClipping().setStyle({height: '0px'}).show(); 
},  
afterFinishInternal: function(effect) {
effect.element.undoClipping();
}
}, arguments[1] || { }));
};
Effect.SwitchOff = function(element) {
element = $(element);
var oldOpacity = element.getInlineOpacity();
return new Effect.Appear(element, Object.extend({
duration: 0.4,
from: 0,
transition: Effect.Transitions.flicker,
afterFinishInternal: function(effect) {
new Effect.Scale(effect.element, 1, { 
duration: 0.3, scaleFromCenter: true,
scaleX: false, scaleContent: false, restoreAfterFinish: true,
beforeSetup: function(effect) { 
effect.element.makePositioned().makeClipping();
},
afterFinishInternal: function(effect) {
effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
}
})
}
}, arguments[1] || { }));
};
Effect.DropOut = function(element) {
element = $(element);
var oldStyle = {
top: element.getStyle('top'),
left: element.getStyle('left'),
opacity: element.getInlineOpacity() };
return new Effect.Parallel(
[ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
Object.extend(
{ duration: 0.5,
beforeSetup: function(effect) {
effect.effects[0].element.makePositioned(); 
},
afterFinishInternal: function(effect) {
effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
} 
}, arguments[1] || { }));
};
Effect.Shake = function(element) {
element = $(element);
var options = Object.extend({
distance: 20,
duration: 0.5
}, arguments[1] || {});
var distance = parseFloat(options.distance);
var split = parseFloat(options.duration) / 10.0;
var oldStyle = {
top: element.getStyle('top'),
left: element.getStyle('left') };
return new Effect.Move(element,
{ x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
new Effect.Move(effect.element,
{ x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
new Effect.Move(effect.element,
{ x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
new Effect.Move(effect.element,
{ x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
new Effect.Move(effect.element,
{ x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
new Effect.Move(effect.element,
{ x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
effect.element.undoPositioned().setStyle(oldStyle);
}}) }}) }}) }}) }}) }});
};
Effect.SlideDown = function(element) {
element = $(element).cleanWhitespace();
// SlideDown need to have the content of the element wrapped in a container element with fixed height!
var oldInnerBottom = element.down().getStyle('bottom');
var elementDimensions = element.getDimensions();
return new Effect.Scale(element, 100, Object.extend({ 
scaleContent: false, 
scaleX: false, 
scaleFrom: window.opera ? 0 : 1,
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
restoreAfterFinish: true,
afterSetup: function(effect) {
effect.element.makePositioned();
effect.element.down().makePositioned();
if (window.opera) effect.element.setStyle({top: ''});
effect.element.makeClipping().setStyle({height: '0px'}).show(); 
},
afterUpdateInternal: function(effect) {
effect.element.down().setStyle({bottom:
(effect.dims[0] - effect.element.clientHeight) + 'px' }); 
},
afterFinishInternal: function(effect) {
effect.element.undoClipping().undoPositioned();
effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
}, arguments[1] || { })
);
};
Effect.SlideUp = function(element) {
element = $(element).cleanWhitespace();
var oldInnerBottom = element.down().getStyle('bottom');
var elementDimensions = element.getDimensions();
return new Effect.Scale(element, window.opera ? 0 : 1,
Object.extend({ scaleContent: false, 
scaleX: false, 
scaleMode: 'box',
scaleFrom: 100,
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
restoreAfterFinish: true,
afterSetup: function(effect) {
effect.element.makePositioned();
effect.element.down().makePositioned();
if (window.opera) effect.element.setStyle({top: ''});
effect.element.makeClipping().show();
},  
afterUpdateInternal: function(effect) {
effect.element.down().setStyle({bottom:
(effect.dims[0] - effect.element.clientHeight) + 'px' });
},
afterFinishInternal: function(effect) {
effect.element.hide().undoClipping().undoPositioned();
effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
}
}, arguments[1] || { })
);
};
// Bug in opera makes the TD containing this element expand for a instance after finish 
Effect.Squish = function(element) {
return new Effect.Scale(element, window.opera ? 1 : 0, { 
restoreAfterFinish: true,
beforeSetup: function(effect) {
effect.element.makeClipping(); 
},  
afterFinishInternal: function(effect) {
effect.element.hide().undoClipping(); 
}
});
};
Effect.Grow = function(element) {
element = $(element);
var options = Object.extend({
direction: 'center',
moveTransition: Effect.Transitions.sinoidal,
scaleTransition: Effect.Transitions.sinoidal,
opacityTransition: Effect.Transitions.full
}, arguments[1] || { });
var oldStyle = {
top: element.style.top,
left: element.style.left,
height: element.style.height,
width: element.style.width,
opacity: element.getInlineOpacity() };
var dims = element.getDimensions();    
var initialMoveX, initialMoveY;
var moveX, moveY;
switch (options.direction) {
case 'top-left':
initialMoveX = initialMoveY = moveX = moveY = 0; 
break;
case 'top-right':
initialMoveX = dims.width;
initialMoveY = moveY = 0;
moveX = -dims.width;
break;
case 'bottom-left':
initialMoveX = moveX = 0;
initialMoveY = dims.height;
moveY = -dims.height;
break;
case 'bottom-right':
initialMoveX = dims.width;
initialMoveY = dims.height;
moveX = -dims.width;
moveY = -dims.height;
break;
case 'center':
initialMoveX = dims.width / 2;
initialMoveY = dims.height / 2;
moveX = -dims.width / 2;
moveY = -dims.height / 2;
break;
}
return new Effect.Move(element, {
x: initialMoveX,
y: initialMoveY,
duration: 0.01, 
beforeSetup: function(effect) {
effect.element.hide().makeClipping().makePositioned();
},
afterFinishInternal: function(effect) {
new Effect.Parallel(
[ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
new Effect.Scale(effect.element, 100, {
scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
], Object.extend({
beforeSetup: function(effect) {
effect.effects[0].element.setStyle({height: '0px'}).show(); 
},
afterFinishInternal: function(effect) {
effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 
}
}, options)
)
}
});
};
Effect.Shrink = function(element) {
element = $(element);
var options = Object.extend({
direction: 'center',
moveTransition: Effect.Transitions.sinoidal,
scaleTransition: Effect.Transitions.sinoidal,
opacityTransition: Effect.Transitions.none
}, arguments[1] || { });
var oldStyle = {
top: element.style.top,
left: element.style.left,
height: element.style.height,
width: element.style.width,
opacity: element.getInlineOpacity() };
var dims = element.getDimensions();
var moveX, moveY;
switch (options.direction) {
case 'top-left':
moveX = moveY = 0;
break;
case 'top-right':
moveX = dims.width;
moveY = 0;
break;
case 'bottom-left':
moveX = 0;
moveY = dims.height;
break;
case 'bottom-right':
moveX = dims.width;
moveY = dims.height;
break;
case 'center':  
moveX = dims.width / 2;
moveY = dims.height / 2;
break;
}
return new Effect.Parallel(
[ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
], Object.extend({            
beforeStartInternal: function(effect) {
effect.effects[0].element.makePositioned().makeClipping(); 
},
afterFinishInternal: function(effect) {
effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
}, options)
);
};
Effect.Pulsate = function(element) {
element = $(element);
var options    = arguments[1] || { };
var oldOpacity = element.getInlineOpacity();
var transition = options.transition || Effect.Transitions.sinoidal;
var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
reverser.bind(transition);
return new Effect.Opacity(element, 
Object.extend(Object.extend({  duration: 2.0, from: 0,
afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
}, options), {transition: reverser}));
};
Effect.Fold = function(element) {
element = $(element);
var oldStyle = {
top: element.style.top,
left: element.style.left,
width: element.style.width,
height: element.style.height };
element.makeClipping();
return new Effect.Scale(element, 5, Object.extend({   
scaleContent: false,
scaleX: false,
afterFinishInternal: function(effect) {
new Effect.Scale(element, 1, { 
scaleContent: false, 
scaleY: false,
afterFinishInternal: function(effect) {
effect.element.hide().undoClipping().setStyle(oldStyle);
} });
}}, arguments[1] || { }));
};
Effect.Morph = Class.create(Effect.Base, {
initialize: function(element) {
this.element = $(element);
if (!this.element) throw(Effect._elementDoesNotExistError);
var options = Object.extend({
style: { }
}, arguments[1] || { });
if (!Object.isString(options.style)) this.style = $H(options.style);
else {
if (options.style.include(':'))
this.style = options.style.parseStyle();
else {
this.element.addClassName(options.style);
this.style = $H(this.element.getStyles());
this.element.removeClassName(options.style);
var css = this.element.getStyles();
this.style = this.style.reject(function(style) {
return style.value == css[style.key];
});
options.afterFinishInternal = function(effect) {
effect.element.addClassName(effect.options.style);
effect.transforms.each(function(transform) {
effect.element.style[transform.style] = '';
});
}
}
}
this.start(options);
},
setup: function(){
function parseColor(color){
if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
color = color.parseColor();
return $R(0,2).map(function(i){
return parseInt( color.slice(i*2+1,i*2+3), 16 ) 
});
}
this.transforms = this.style.map(function(pair){
var property = pair[0], value = pair[1], unit = null;
if (value.parseColor('#zzzzzz') != '#zzzzzz') {
value = value.parseColor();
unit  = 'color';
} else if (property == 'opacity') {
value = parseFloat(value);
if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
this.element.setStyle({zoom: 1});
} else if (Element.CSS_LENGTH.test(value)) {
var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
value = parseFloat(components[1]);
unit = (components.length == 3) ? components[2] : null;
}
var originalValue = this.element.getStyle(property);
return { 
style: property.camelize(), 
originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 
targetValue: unit=='color' ? parseColor(value) : value,
unit: unit
};
}.bind(this)).reject(function(transform){
return (
(transform.originalValue == transform.targetValue) ||
(
transform.unit != 'color' &&
(isNaN(transform.originalValue) || isNaN(transform.targetValue))
)
)
});
},
update: function(position) {
var style = { }, transform, i = this.transforms.length;
while(i--)
style[(transform = this.transforms[i]).style] = 
transform.unit=='color' ? '#'+
(Math.round(transform.originalValue[0]+
(transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
(Math.round(transform.originalValue[1]+
(transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
(Math.round(transform.originalValue[2]+
(transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
(transform.originalValue +
(transform.targetValue - transform.originalValue) * position).toFixed(3) + 
(transform.unit === null ? '' : transform.unit);
this.element.setStyle(style, true);
}
});
Effect.Transform = Class.create({
initialize: function(tracks){
this.tracks  = [];
this.options = arguments[1] || { };
this.addTracks(tracks);
},
addTracks: function(tracks){
tracks.each(function(track){
track = $H(track);
var data = track.values().first();
this.tracks.push($H({
ids:     track.keys().first(),
effect:  Effect.Morph,
options: { style: data }
}));
}.bind(this));
return this;
},
play: function(){
return new Effect.Parallel(
this.tracks.map(function(track){
var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
var elements = [$(ids) || $$(ids)].flatten();
return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
}).flatten(),
this.options
);
}
});
Element.CSS_PROPERTIES = $w(
'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 
'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
'fontSize fontWeight height left letterSpacing lineHeight ' +
'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
'right textIndent top width wordSpacing zIndex');
Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
String.__parseStyleElement = document.createElement('div');
String.prototype.parseStyle = function(){
var style, styleRules = $H();
if (Prototype.Browser.WebKit)
style = new Element('div',{style:this}).style;
else {
String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
style = String.__parseStyleElement.childNodes[0].style;
}
Element.CSS_PROPERTIES.each(function(property){
if (style[property]) styleRules.set(property, style[property]); 
});
if (Prototype.Browser.IE && this.include('opacity'))
styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
return styleRules;
};
if (document.defaultView && document.defaultView.getComputedStyle) {
Element.getStyles = function(element) {
var css = document.defaultView.getComputedStyle($(element), null);
return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
styles[property] = css[property];
return styles;
});
};
} else {
Element.getStyles = function(element) {
element = $(element);
var css = element.currentStyle, styles;
styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
results[property] = css[property];
return results;
});
if (!styles.opacity) styles.opacity = element.getOpacity();
return styles;
};
};
Effect.Methods = {
morph: function(element, style) {
element = $(element);
new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
return element;
},
visualEffect: function(element, effect, options) {
element = $(element)
var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
new Effect[klass](element, options);
return element;
},
highlight: function(element, options) {
element = $(element);
new Effect.Highlight(element, options);
return element;
}
};
$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
'pulsate shake puff squish switchOff dropOut').each(
function(effect) { 
Effect.Methods[effect] = function(element, options){
element = $(element);
Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
return element;
}
}
);
$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( 
function(f) { Effect.Methods[f] = Element[f]; }
);
Element.addMethods(Effect.Methods);
// script.aculo.us builder.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
var Builder = {
NODEMAP: {
AREA: 'map',
CAPTION: 'table',
COL: 'table',
COLGROUP: 'table',
LEGEND: 'fieldset',
OPTGROUP: 'select',
OPTION: 'select',
PARAM: 'object',
TBODY: 'table',
TD: 'table',
TFOOT: 'table',
TH: 'table',
THEAD: 'table',
TR: 'table'
},
// note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
//       due to a Firefox bug
node: function(elementName) {
elementName = elementName.toUpperCase();
// try innerHTML approach
var parentTag = this.NODEMAP[elementName] || 'div';
var parentElement = document.createElement(parentTag);
try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
} catch(e) {}
var element = parentElement.firstChild || null;
// see if browser added wrapping tags
if(element && (element.tagName.toUpperCase() != elementName))
element = element.getElementsByTagName(elementName)[0];
// fallback to createElement approach
if(!element) element = document.createElement(elementName);
// abort if nothing could be created
if(!element) return;
// attributes (or text)
if(arguments[1])
if(this._isStringOrNumber(arguments[1]) ||
(arguments[1] instanceof Array) ||
arguments[1].tagName) {
this._children(element, arguments[1]);
} else {
var attrs = this._attributes(arguments[1]);
if(attrs.length) {
try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
parentElement.innerHTML = "<" +elementName + " " +
attrs + "></" + elementName + ">";
} catch(e) {}
element = parentElement.firstChild || null;
// workaround firefox 1.0.X bug
if(!element) {
element = document.createElement(elementName);
for(attr in arguments[1]) 
element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
}
if(element.tagName.toUpperCase() != elementName)
element = parentElement.getElementsByTagName(elementName)[0];
}
} 
// text, or array of children
if(arguments[2])
this._children(element, arguments[2]);
return element;
},
_text: function(text) {
return document.createTextNode(text);
},
ATTR_MAP: {
'className': 'class',
'htmlFor': 'for'
},
_attributes: function(attributes) {
var attrs = [];
for(attribute in attributes)
attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
'="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
return attrs.join(" ");
},
_children: function(element, children) {
if(children.tagName) {
element.appendChild(children);
return;
}
if(typeof children=='object') { // array can hold nodes and text
children.flatten().each( function(e) {
if(typeof e=='object')
element.appendChild(e)
else
if(Builder._isStringOrNumber(e))
element.appendChild(Builder._text(e));
});
} else
if(Builder._isStringOrNumber(children))
element.appendChild(Builder._text(children));
},
_isStringOrNumber: function(param) {
return(typeof param=='string' || typeof param=='number');
},
build: function(html) {
var element = this.node('div');
$(element).update(html.strip());
return element.down();
},
dump: function(scope) { 
if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope 
var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
"BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
"FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
"KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
"PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
"TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
tags.each( function(tag){ 
scope[tag] = function() { 
return Builder.node.apply(Builder, [tag].concat($A(arguments)));  
} 
});
}
}
/*  Prototype-UI, version trunk
*
*  Prototype-UI is freely distributable under the terms of an MIT-style license.
*  For details, see the PrototypeUI web site: http://www.prototype-ui.com/
*
*--------------------------------------------------------------------------*/
if(typeof Prototype == 'undefined' || !Prototype.Version.match("1.6"))
throw("Prototype-UI library require Prototype library >= 1.6.0");
if (Prototype.Browser.WebKit) {
Prototype.Browser.WebKitVersion = parseFloat(navigator.userAgent.match(/AppleWebKit\/([\d\.\+]*)/)[1]);
Prototype.Browser.Safari2 = (Prototype.Browser.WebKitVersion < 420);
}
if (Prototype.Browser.IE) {
Prototype.Browser.IEVersion = parseFloat(navigator.appVersion.split(';')[1].strip().split(' ')[1]);
Prototype.Browser.IE6 =  Prototype.Browser.IEVersion == 6;
Prototype.Browser.IE7 =  Prototype.Browser.IEVersion == 7;
}
Prototype.falseFunction = function() { return false };
Prototype.trueFunction  = function() { return true  };
/*
Namespace: UI
Introduction:
Prototype-UI is a library of user interface components based on the Prototype framework.
Its aim is to easilly improve user experience in web applications.
It also provides utilities to help developers.
Guideline:
- Prototype conventions are followed
- Everything should be unobstrusive
- All components are themable with CSS stylesheets, various themes are provided
Warning:
Prototype-UI is still under deep development, this release is targeted to developers only.
All interfaces are subjects to changes, suggestions are welcome.
DO NOT use it in production for now.
Authors:
- Sébastien Gruhier, <http://www.xilinus.com>
- Samuel Lebeau, <http://gotfresh.info>
*/
var UI = {
Abstract: { },
Ajax: { }
};
Object.extend(Class.Methods, {
extend: Object.extend.methodize(),
addMethods: Class.Methods.addMethods.wrap(function(proceed, source) {
// ensure we are not trying to add null or undefined
if (!source) return this;
// no callback, vanilla way
if (!source.hasOwnProperty('methodsAdded'))
return proceed(source);
var callback = source.methodsAdded;
delete source.methodsAdded;
proceed(source);
callback.call(source, this);
source.methodsAdded = callback;
return this;
}),
addMethod: function(name, lambda) {
var methods = {};
methods[name] = lambda;
return this.addMethods(methods);
},
method: function(name) {
return this.prototype[name].valueOf();
},
classMethod: function() {
$A(arguments).flatten().each(function(method) {
this[method] = (function() {
return this[method].apply(this, arguments);
}).bind(this.prototype);
}, this);
return this;
},
// prevent any call to this method
undefMethod: function(name) {
this.prototype[name] = undefined;
return this;
},
// remove the class' own implementation of this method
removeMethod: function(name) {
delete this.prototype[name];
return this;
},
aliasMethod: function(newName, name) {
this.prototype[newName] = this.prototype[name];
return this;
},
aliasMethodChain: function(target, feature) {
feature = feature.camelcase();
this.aliasMethod(target+"Without"+feature, target);
this.aliasMethod(target, target+"With"+feature);
return this;
}
});
Object.extend(Number.prototype, {
// Snap a number to a grid
snap: function(round) {
return parseInt(round == 1 ? this : (this / round).floor() * round);
}
});
/*
Interface: String
*/
Object.extend(String.prototype, {
camelcase: function() {
var string = this.dasherize().camelize();
return string.charAt(0).toUpperCase() + string.slice(1);
},
/*
Method: makeElement
toElement is unfortunately already taken :/
Transforms html string into an extended element or null (when failed)
> '<li><a href="#">some text</a></li>'.makeElement(); // => LI href#
> '<img src="foo" id="bar" /><img src="bar" id="bar" />'.makeElement(); // => IMG#foo (first one)
Returns:
Extended element
*/
makeElement: function() {
var wrapper = new Element('div'); wrapper.innerHTML = this;
return wrapper.down();
}
});
Object.extend(Array.prototype, {
empty: function() {
return !this.length;
},
extractOptions: function() {
return this.last().constructor === Object ? this.pop() : { };
},
removeAt: function(index) {
var object = this[index];
this.splice(index, 1);
return object;
},
remove: function(object) {
var index;
while ((index = this.indexOf(object)) != -1)
this.removeAt(index);
return object;
},
insert: function(index) {
var args = $A(arguments);
args.shift();
this.splice.apply(this, [ index, 0 ].concat(args));
return this;
}
});
Element.addMethods({
getScrollDimensions: function(element) {
return {
width:  element.scrollWidth,
height: element.scrollHeight
}
},
getScrollOffset: function(element) {
return Element._returnOffset(element.scrollLeft, element.scrollTop);
},
setScrollOffset: function(element, offset) {
element = $(element);
if (arguments.length == 3)
offset = { left: offset, top: arguments[2] };
element.scrollLeft = offset.left;
element.scrollTop  = offset.top;
return element;
},
// returns "clean" numerical style (without "px") or null if style can not be resolved
// or is not numeric
getNumStyle: function(element, style) {
var value = parseFloat($(element).getStyle(style));
return isNaN(value) ? null : value;
},
// by Tobie Langel (http://tobielangel.com/2007/5/22/prototype-quick-tip)
appendText: function(element, text) {
element = $(element);
text = String.interpret(text);
element.appendChild(document.createTextNode(text));
return element;
}
});
document.whenReady = function(callback) {
if (document.loaded)
callback.call(document);
else
document.observe('dom:loaded', callback);
};
Object.extend(document.viewport, {
// Alias this method for consistency
getScrollOffset: document.viewport.getScrollOffsets,
setScrollOffset: function(offset) {
Element.setScrollOffset(Prototype.Browser.WebKit ? document.body : document.documentElement, offset);
},
getScrollDimensions: function() {
return Element.getScrollDimensions(Prototype.Browser.WebKit ? document.body : document.documentElement);
}
});
/*
Interface: UI.Options
Mixin to handle *options* argument in initializer pattern.
TODO: find a better example than Circle that use an imaginary Point function,
this example should be used in tests too.
It assumes class defines a property called *options*, containing
default options values.
Instances hold their own *options* property after a first call to <setOptions>.
Example:
> var Circle = Class.create(UI.Options, {
>
>   // default options
>   options: {
>     radius: 1,
>     origin: Point(0, 0)
>   },
>
>   // common usage is to call setOptions in initializer
>   initialize: function(options) {
>     this.setOptions(options);
>   }
> });
>
> var circle = new Circle({ origin: Point(1, 4) });
>
> circle.options
> // => { radius: 1, origin: Point(1,4) }
Accessors:
There are builtin methods to automatically write options accessors. All those
methods can take either an array of option names nor option names as arguments.
Notice that those methods won't override an accessor method if already present.
* <optionsGetter> creates getters
* <optionsSetter> creates setters
* <optionsAccessor> creates both getters and setters
Common usage is to invoke them on a class to create accessors for all instances
of this class.
Invoking those methods on a class has the same effect as invoking them on the class prototype.
See <classMethod> for more details.
Example:
> // Creates getter and setter for the "radius" options of circles
> Circle.optionsAccessor('radius');
>
> circle.setRadius(4);
> // 4
>
> circle.getRadius();
> // => 4 (circle.options.radius)
Inheritance support:
Subclasses can refine default *options* values, after a first instance call on setOptions,
*options* attribute will hold all default options values coming from the inheritance hierarchy.
*/
(function() {
UI.Options = {
methodsAdded: function(klass) {
klass.classMethod($w(' setOptions allOptions optionsGetter optionsSetter optionsAccessor '));
},
// Group: Methods
/*
Method: setOptions
Extends object's *options* property with the given object
*/
setOptions: function(options) {
if (!this.hasOwnProperty('options'))
this.options = this.allOptions();
this.options = Object.extend(this.options, options || {});
},
/*
Method: allOptions
Computes the complete default options hash made by reverse extending all superclasses
default options.
> Widget.prototype.allOptions();
*/
allOptions: function() {
var superclass = this.constructor.superclass, ancestor = superclass && superclass.prototype;
return (ancestor && ancestor.allOptions) ?
Object.extend(ancestor.allOptions(), this.options) :
Object.clone(this.options);
},
/*
Method: optionsGetter
Creates default getters for option names given as arguments.
With no argument, creates getters for all option names.
*/
optionsGetter: function() {
addOptionsAccessors(this, arguments, false);
},
/*
Method: optionsSetter
Creates default setters for option names given as arguments.
With no argument, creates setters for all option names.
*/
optionsSetter: function() {
addOptionsAccessors(this, arguments, true);
},
/*
Method: optionsAccessor
Creates default getters/setters for option names given as arguments.
With no argument, creates accessors for all option names.
*/
optionsAccessor: function() {
this.optionsGetter.apply(this, arguments);
this.optionsSetter.apply(this, arguments);
}
};
// Internal
function addOptionsAccessors(receiver, names, areSetters) {
names = $A(names).flatten();
if (names.empty())
names = Object.keys(receiver.allOptions());
names.each(function(name) {
var accessorName = (areSetters ? 'set' : 'get') + name.camelcase();
receiver[accessorName] = receiver[accessorName] || (areSetters ?
// Setter
function(value) { return this.options[name] = value } :
// Getter
function()      { return this.options[name]         });
});
}
})();
/*
Namespace: CSS
Utility functions for CSS/StyleSheet files access
Authors:
- Sébastien Gruhier, <http://www.xilinus.com>
- Samuel Lebeau, <http://gotfresh.info>
*/
var CSS = (function() {
// Code based on:
//   - IE5.5+ PNG Alpha Fix v1.0RC4 (c) 2004-2005 Angus Turnbull http://www.twinhelix.com
//   - Whatever:hover - V2.02.060206 - hover, active & focus (c) 2005 - Peter Nederlof * Peterned - http://www.xs4all.nl/~peterned/
function fixPNG() {
parseStylesheet.apply(this, $A(arguments).concat(fixRule));
};
function parseStylesheet() {
var patterns = $A(arguments);
var method = patterns.pop();
// To avoid flicking background
//document.execCommand("BackgroundImageCache", false, true);
// Parse all document stylesheets
var styleSheets = $A(document.styleSheets);
if (patterns.length > 1) {
styleSheets = styleSheets.select(function(css) {
return patterns.any(function(pattern) {
return css.href && css.href.match(pattern)
});
});
}
styleSheets.each(function(styleSheet) {fixStylesheet.call(this, styleSheet, method)});
};
// Fixes a stylesheet
function fixStylesheet(stylesheet, method) {
// Parse import files
if (stylesheet.imports)
$A(stylesheet.imports).each(fixStylesheet);
var href = stylesheet.href || document.location.href;
var docPath = href.substr(0, href.lastIndexOf('/'));
// Parse all CSS Rules
$A(stylesheet.rules || stylesheet.cssRules).each(function(rule) { method.call(this, rule, docPath) });
};
var filterPattern = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="#{src}",sizingMethod="#{method}")';
// Fixes a rule if it has a PNG background
function fixRule(rule, docPath) {
var bgImg = rule.style.backgroundImage;
// Rule with PNG background image
if (bgImg && bgImg != 'none' && bgImg.match(/^url[("']+(.*\.png)[)"']+$/i)) {
var src = RegExp.$1;
var bgRepeat = rule.style.backgroundRepeat;
// Relative path
if (src[0] != '/')
src = docPath + "/" + src;
// Apply filter
rule.style.filter = filterPattern.interpolate({
src:    src,
method: bgRepeat == "no-repeat" ? "crop" : "scale" });
rule.style.backgroundImage = "none";
}
};
var preloadedImages = new Hash();
function preloadRule(rule, docPath) {
var bgImg = rule.style.backgroundImage;
if (bgImg && bgImg != 'none'  && bgImg != 'initial' ) {
if (!preloadedImages.get(bgImg)) {
bgImg.match(/^url[("']+(.*)[)"']+$/i);
var src = RegExp.$1;
// Relative path
if (!(src[0] == '/' || src.match(/^file:/) || src.match(/^https?:/)))
src = docPath + "/" + src;
preloadedImages.set(bgImg, true);
var image = new Image();
image.src = src;
}
}
}
return {
/*
Method: fixPNG
Fix transparency of PNG background of document stylesheets.
(only on IE version<7, otherwise does nothing)
Warning: All png background will not work as IE filter use for handling transparency in PNG
is not compatible with all background. It does not support top/left position (so no CSS sprite)
I recommend to create a special CSS file with png that needs to be fixed and call CSS.fixPNG on this CSS
Examples:
> CSS.fixPNG() // To fix all css
>
> CSS.fixPNG("mac_shadow.css") // to fix all css files with mac_shadow.css so mainly only on file
>
> CSS.fixPNG("shadow", "vista"); // To fix all css files with shadow or vista in their names
Parameters
patterns: (optional) list of pattern to filter css files
*/
fixPNG: (Prototype.Browser.IE && Prototype.Browser.IEVersion < 7) ? fixPNG : Prototype.emptyFunction,
// By Tobie Langel (http://tobielangel.com)
//   inspired by http://yuiblog.com/blog/2007/06/07/style/
addRule: function(css, backwardCompatibility) {
if (backwardCompatibility) css = css + '{' + backwardCompatibility + '}';
var style = new Element('style', { type: 'text/css', media: 'screen' });
$(document.getElementsByTagName('head')[0]).insert(style);
if (style.styleSheet) style.styleSheet.cssText = css;
else style.appendText(css);
return style;
},
preloadImages: function() {
parseStylesheet.apply(this, $A(arguments).concat(preloadRule));
}
};
})();
UI.Benchmark = {
benchmark: function(lambda, iterations) {
var date = new Date();
(iterations || 1).times(lambda);
return (new Date() - date) / 1000;
}
};
/*
Group: Drag
UI provides Element#enableDrag method that allow elements to fire drag-related events.
Events fired:
- drag:started : fired when a drag is started (mousedown then mousemove)
- drag:updated : fired when a drag is updated (mousemove)
- drag:ended   : fired when a drag is ended (mouseup)
Notice it doesn't actually move anything, drag behavior has to be implemented
by attaching handlers to drag events.
Drag-related informations:
event.memo contains useful information about the drag occuring:
- dx         : difference between pointer x position when drag started
and actual x position
- dy         : difference between pointer y position when drag started
and actual y position
- mouseEvent : the original mouse event, useful to know pointer absolute position,
or if key were pressed.
Example, with event handling for a specific element:
> // Now "resizable" will fire drag-related events
> $('resizable').enableDrag();
>
> // Let's observe them
> $('resizable').observe('drag:started', function(event) {
>   this._dimensions = this.getDimensions();
> }).observe('drag:updated', function(event) {
>   var drag = event.memo;
>
>   this.setStyle({
>     width:  this._dimensions.width  + drag.dx + 'px',
>     height: this._dimensions.height + drag.dy + 'px'
>   });
> });
Example, with event delegating on the whole document:
> // All elements in the having the "draggable" class name will fire drag events.
> $$('.draggable').invoke('enableDrag');
>
> document.observe('drag:started', function(event) {
>   UI.logger.info('trying to drag ' + event.element().id);
> }):
*/
(function() {
var initPointer, currentDraggable, dragging;
document.observe('mousedown', onMousedown);
function onMousedown(event) {
var draggable = event.findElement('[ui:draggable="true"]');
if (draggable) {
// prevent default browser action
event.stop();
currentDraggable = draggable;
initPointer = event.pointer();
document.observe("mousemove", onMousemove)
.observe("mouseup",   onMouseup);
}
};
function onMousemove(event) {
event.stop();
if (dragging)
fire('drag:updated', event);
else {
dragging = true;
fire('drag:started', event);
}
};
function onMouseup(event) {
document.stopObserving('mousemove', onMousemove)
.stopObserving('mouseup',   onMouseup);
if (dragging) {
dragging = false;
fire('drag:ended', event);
}
};
function fire(eventName, mouseEvent) {
var pointer = mouseEvent.pointer();
currentDraggable.fire(eventName, {
dx: pointer.x - initPointer.x,
dy: pointer.y - initPointer.y,
mouseEvent: mouseEvent
})
};
Element.addMethods({
enableDrag: function(element) {
element = $(element);
element.writeAttribute('ui:draggable', 'true');
return element;
},
disableDrag: function(element){
element = $(element);
element.writeAttribute('ui:draggable', null);
return element;
},
isDraggable: function(element) {
return $(element).readAttribute('ui:draggable') == 'true';
}
});
})();
/*
Class: UI.IframeShim
Handles IE6 bug when <select> elements overlap other elements with higher z-index
Example:
> // creates iframe and positions it under "contextMenu" element
> this.iefix = new UI.IframeShim().positionUnder('contextMenu');
> ...
> document.observe('click', function(e) {
>   if (e.isLeftClick()) {
>     this.contextMenu.hide();
>
>     // hides iframe when left click is fired on a document
>     this.iefix.hide();
>   }
> }.bind(this))
> ...
*/
// TODO:
//
// Maybe it makes sense to bind iframe to an element
// so that it automatically calls positionUnder method
// when the element it's binded to is moved or resized
// Not sure how this might affect overall perfomance...
UI.IframeShim = Class.create(UI.Options, {
/*
Method: initialize
Constructor
Creates iframe shim and appends it to the body.
Note that this method does not perform proper positioning and resizing of an iframe.
To do that use positionUnder method
Returns:
this
*/
initialize: function() {
this.element = new Element('iframe', {
style: 'position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);display:none',
src: 'javascript:false;',
frameborder: 0
});
$(document.body).insert(this.element);
},
/*
Method: hide
Hides iframe shim leaving its position and dimensions intact
Returns:
this
*/
hide: function() {
this.element.hide();
return this;
},
/*
Method: show
Show iframe shim leaving its position and dimensions intact
Returns:
this
*/
show: function() {
this.element.show();
return this;
},
/*
Method: positionUnder
Positions iframe shim under the specified element
Sets proper dimensions, offset, zIndex and shows it
Note that the element should have explicitly specified zIndex
Returns:
this
*/
positionUnder: function(element) {
var element = $(element),
offset = element.cumulativeOffset(),
dimensions = element.getDimensions(),
style = {
left: offset[0] + 'px',
top: offset[1] + 'px',
width: dimensions.width + 'px',
height: dimensions.height + 'px',
zIndex: element.getStyle('zIndex') - 1
};
this.element.setStyle(style).show();
return this;
},
/*
Method: setBounds
Sets element's width, height, top and left css properties using 'px' as units
Returns:
this
*/
setBounds: function(bounds) {
for (prop in bounds) {
bounds[prop] += 'px';
}
this.element.setStyle(bounds);
return this;
},
/*
Method: destroy
Completely removes the iframe shim from the document
Returns:
this
*/
destroy: function() {
if (this.element)
this.element.remove();
return this;
}
});
/*
Class: UI.Logger
*/
/*
Group: Logging Facilities
Prototype UI provides a facility to log message with levels.
Levels are in order "debug", "info", "warn" and "error".
As soon as the DOM is loaded, a default logger is present in UI.logger.
This logger is :
* an <ElementLogger> if $('log') is present
* a <ConsoleLogger> if window.console is defined
* a <MemLogger> otherwise
See <AbstractLogger> to learn how to use it.
Example:
> UI.logger.warn('something bad happenned !');
*/
// Class: AbstractLogger
UI.Abstract.Logger = Class.create({
/*
Property: level
The log level, default value is debug  <br/>
*/
level: 'debug'
});
(function() {
/*
Method: debug
Logs with "debug" level
Method: info
Logs with "info" level
Method: warn
Logs with "warn" level
Method: error
Logs with "error" level
*/
var levels = $w(" debug info warn error ");
levels.each(function(level, index) {
UI.Abstract.Logger.addMethod(level, function(message) {
// filter lower level messages
if (index >= levels.indexOf(this.level))
this._log({ level: level, message: message, date: new Date() });
});
});
})();
/*
Class: NullLogger
Does nothing
*/
UI.NullLogger = Class.create(UI.Abstract.Logger, {
_log: Prototype.emptyFunction
});
/*
Class: MemLogger
Logs in memory
Property: logs
An array of logs, objects with "date", "level", and "message" properties
*/
UI.MemLogger = Class.create(UI.Abstract.Logger, {
initialize: function() {
this.logs = [ ];
},
_log: function(log) {
this.logs.push(log);
}
});
/*
Class: ConsoleLogger
Logs using window.console
*/
UI.ConsoleLogger = Class.create(UI.Abstract.Logger, {
_log: function(log) {
console[log.level || 'log'](log.message);
}
});
/*
Class: ElementLogger
Logs in a DOM element
*/
UI.ElementLogger = Class.create(UI.Abstract.Logger, {
/*
Method: initialize
Constructor, takes a DOM element to log into as argument
*/
initialize: function(element) {
this.element = $(element);
},
/*
Property: format
A format string, will be interpolated with "date", "level" and "message"
Example:
> "<p>(#{date}) #{level}: #{message}</p>"
*/
format: '<p>(<span class="date">#{date}</span>) ' +
'<span class="level">#{level}</span> : ' +
'<span class="message">#{message}</span></p>',
_log: function(log) {
var entry = this.format.interpolate({
level:   log.level.toUpperCase(),
message: log.message.escapeHTML(),
date:    log.date.toLocaleTimeString()
});
this.element.insert({ top: entry });
}
});
document.observe('dom:loaded', function() {
if ($('log'))             UI.logger = new UI.ElementLogger('log');
else if (window.console)  UI.logger = new UI.ConsoleLogger();
else                      UI.logger = new UI.MemLogger();
});
/*
Class: UI.Shadow
Add shadow around a DOM element. The element MUST BE in ABSOLUTE position.
Shadow can be skinned by CSS (see mac_shadow.css or drop_shadow.css).
CSS must be included to see shadow.
A shadow can have two states: focused and blur.
Shadow shifts are set in CSS file as margin and padding of shadow_container to add visual information.
Example:
> new UI.Shadow("element_id");
*/
UI.Shadow = Class.create(UI.Options, {
options: {
theme: "mac_shadow",
focus: false,
zIndex: 100
},
/*
Method: initialize
Constructor, adds shadow elements to the DOM if element is in the DOM.
Element MUST BE in ABSOLUTE position.
Parameters:
element - DOM element
options - Hashmap of options
- theme (default: mac_shadow)
- focus (default: true)
- zIndex (default: 100)
Returns:
this
*/
initialize: function(element, options) {
this.setOptions(options);
this.element = $(element);
this.create();
if (Object.isElement(this.element.parentNode))
this.render();
},
/*
Method: destroy
Destructor, removes elements from the DOM
*/
destroy: function() {
if (this.shadow.parentNode)
this.remove();
},
// Group: Size and Position
/*
Method: setPosition
Sets top/left shadow position in pixels
Parameters:
top -  top position in pixel
left - left position in pixel
Returns:
this
*/
setPosition: function(top, left) {
if (this.shadowSize) {
var shadowStyle = this.shadow.style;
shadowStyle.top  = parseInt(top)  - this.shadowSize.top  + this.shadowShift.top + 'px';
shadowStyle.left = parseInt(left) - this.shadowSize.left + this.shadowShift.left+ 'px';
}
return this;
},
/*
Method: setSize
Sets width/height shadow in pixels
Parameters:
width  - width in pixel
height - height in pixel
Returns:
this
*/
setSize: function(width, height) {
if (this.shadowSize) {
var w = parseInt(width) + this.shadowSize.width - this.shadowShift.width + "px";
this.shadow.style.width = w;
var h =  parseInt(height) - this.shadowShift.height + "px";
// this.shadowContents[1].style.height = h;
this.shadowContents[1].childElements().each(function(e) {e.style.height = h});
this.shadowContents.each(function(item){ item.style.width = w});
}
return this;
},
/*
Method: setBounds
Sets shadow bounds in pixels
Parameters:
bounds - an Hash {top:, left:, width:, height:}
Returns:
this
*/
setBounds: function(bounds) {
return this.setPosition(bounds.top, bounds.left).setSize(bounds.width, bounds.height);
},
/*
Method: setZIndex
Sets shadow z-index
Parameters:
zIndex - zIndex value
Returns:
this
*/
setZIndex: function(zIndex) {
this.shadow.style.zIndex = zIndex;
return this;
},
// Group: Render
/*
Method: show
Displays shadow
Returns:
this
*/
show: function() {
this.shadow.show();
return this;
},
/*
Method: hide
Hides shadow
Returns:
this
*/
hide: function() {
this.shadow.hide();
return this;
},
/*
Method: remove
Removes shadow from the DOM
Returns:
this
*/
remove: function() {
this.shadow.remove();
return this;
},
// Group: Status
/*
Method: focus
Focus shadow.
Change shadow shift. Shift values are set in CSS file as margin and padding of shadow_container
to add visual information of shadow status.
Returns:
this
*/
focus: function() {
this.options.focus = true;
this.updateShadow();
return this;
},
/*
Method: blur
Blurs shadow.
Change shadow shift. Shift values are set in CSS file as margin and padding of shadow_container
to add visual information of shadow status.
Returns:
this
*/
blur: function() {
this.options.focus = false;
this.updateShadow();
return this;
},
// Private Functions
// Adds shadow elements to DOM, computes shadow size and displays it
render: function() {
if (this.element.parentNode && !Object.isElement(this.shadow.parentNode)) {
this.element.parentNode.appendChild(this.shadow);
this.computeSize();
this.setBounds(Object.extend(this.element.getDimensions(), this.getElementPosition()));
this.shadow.show();
}
return this;
},
// Creates HTML elements without inserting them into the DOM
create: function() {
var zIndex = this.element.getStyle('zIndex');
if (!zIndex)
this.element.setStyle({zIndex: this.options.zIndex});
zIndex = (zIndex || this.options.zIndex) - 1;
this.shadowContents = new Array(3);
this.shadowContents[0] = new Element("div")
.insert(new Element("div", {className: "shadow_center_wrapper"}).insert(new Element("div", {className: "n_shadow"})))
.insert(new Element("div", {className: "shadow_right ne_shadow"}))
.insert(new Element("div", {className: "shadow_left nw_shadow"}));
this.shadowContents[1] = new Element("div")
.insert(new Element("div", {className: "shadow_center_wrapper c_shadow"}))
.insert(new Element("div", {className: "shadow_right e_shadow"}))
.insert(new Element("div", {className: "shadow_left w_shadow"}));
this.centerElements = this.shadowContents[1].childElements();
this.shadowContents[2] = new Element("div")
.insert(new Element("div", {className: "shadow_center_wrapper"}).insert(new Element("div", {className: "s_shadow"})))
.insert(new Element("div", {className: "shadow_right se_shadow"}))
.insert(new Element("div", {className: "shadow_left sw_shadow"}));
this.shadow = new Element("div", {className: "shadow_container " + this.options.theme,
style: "position:absolute; top:-10000px; left:-10000px; display:none; z-index:" + zIndex })
.insert(this.shadowContents[0])
.insert(this.shadowContents[1])
.insert(this.shadowContents[2]);
},
// Compute shadow size
computeSize: function() {
if (this.focusedShadowShift)
return;
this.shadow.show();
// Trick to get shadow shift designed in CSS as padding
var content = this.shadowContents[1].select("div.c_shadow").first();
this.unfocusedShadowShift = {};
this.focusedShadowShift = {};
$w("top left bottom right").each(function(pos) {this.unfocusedShadowShift[pos] = content.getNumStyle("padding-" + pos) || 0}.bind(this));
this.unfocusedShadowShift.width  = this.unfocusedShadowShift.left + this.unfocusedShadowShift.right;
this.unfocusedShadowShift.height = this.unfocusedShadowShift.top + this.unfocusedShadowShift.bottom;
$w("top left bottom right").each(function(pos) {this.focusedShadowShift[pos] = content.getNumStyle("margin-" + pos) || 0}.bind(this));
this.focusedShadowShift.width  = this.focusedShadowShift.left + this.focusedShadowShift.right;
this.focusedShadowShift.height = this.focusedShadowShift.top + this.focusedShadowShift.bottom;
this.shadowShift = this.options.focus ? this.focusedShadowShift : this.unfocusedShadowShift;
// Get shadow size
this.shadowSize  = {top:    this.shadowContents[0].childElements()[1].getNumStyle("height"),
left:   this.shadowContents[0].childElements()[1].getNumStyle("width"),
bottom: this.shadowContents[2].childElements()[1].getNumStyle("height"),
right:  this.shadowContents[0].childElements()[2].getNumStyle("width")};
this.shadowSize.width  = this.shadowSize.left + this.shadowSize.right;
this.shadowSize.height = this.shadowSize.top + this.shadowSize.bottom;
// Remove padding
content.setStyle("padding:0; margin:0");
this.shadow.hide();
},
// Update shadow size (called when it changes from focused to blur and vice-versa)
updateShadow: function() {
this.shadowShift = this.options.focus ? this.focusedShadowShift : this.unfocusedShadowShift;
var shadowStyle = this.shadow.style, pos  = this.getElementPosition(), size = this.element.getDimensions();
shadowStyle.top  =  pos.top    - this.shadowSize.top   + this.shadowShift.top   + 'px';
shadowStyle.left  = pos.left   - this.shadowSize.left  + this.shadowShift.left  + 'px';
shadowStyle.width = size.width + this.shadowSize.width - this.shadowShift.width + "px";
var h = size.height - this.shadowShift.height + "px";
this.centerElements.each(function(e) {e.style.height = h});
var w = size.width + this.shadowSize.width - this.shadowShift.width+ "px";
this.shadowContents.each(function(item) { item.style.width = w });
},
// Get element position in integer values
getElementPosition: function() {
return {top: this.element.getNumStyle("top"), left: this.element.getNumStyle("left")}
}
});
// Set theme and focus as read/write accessor
document.whenReady(function() { CSS.fixPNG("shadow") });
/*
Class: UI.Window
Main class to handle windows inside a web page.
Example:
> new UI.Window({ theme: 'bluglighting' }).show()
*/
/*
<div class="STitle">Options</div>
*/
UI.Window = Class.create(UI.Options, {
// Group: Options
options: {
// Property: theme
//   window theme, uses the window manager theme as default
theme:         null,
// Property: shadowTheme
//   window shadow theme, uses the window manager one as default
//   Only useful if <shadow> options is true, see <UI.Shadow> for details
shadowTheme:   null,
// Property: id
//   id ot the window, generated by default
id:            null,
// Property: windowManager
//   window manager that manages this window,
//   uses UI.defaultWM as default
windowManager: null,
top:           null,
left:          null,
width:         200,
height:        300,
minHeight:     100,
minWidth:      200,
maxHeight:     null,
maxWidth:      null,
altitude:      "front",
// Property: resizable
//   true by default
resizable:     true,
// Property: draggable
//   true by default
draggable:     true,
// Property: wired
//   draw wires around window when dragged, false by default
wired:         false,
// Property: show
//   Function used to show the window, default is Element.show
show: Element.show,
// Property: hide
//   Function used to hide the window, default is Element.hide.
hide: Element.hide,
// Property: superflousEffects
//   uses superflous effects when resizing or moving window.
//   it's true if Scriptaculous' Effect is defined, false otherwise
superflousEffects: !Object.isUndefined(window.Effect),
// Property: shadow
//   draw shadow around the window, default is false
shadow:            false,
// Property: activeOnClick
//   When set to true, a click on an blurred window content activates it,
//   default is true
activeOnClick:     true,
// Grid
gridX:  1,
gridY:  1,
// Buttons and actions (false to disable)
// Property: close
//   Window method name as string, or false to disable close button
//   Default is 'destroy'
close:    'destroy',
// Property: minimize
//   Window method name as string, or false to disable minimize button
//   Default is 'toggleFold'
minimize: 'toggleFold',
// Property: maximize
//   Window method name as string, or false to disable maximize button
//   Default is 'toggleMaximize'
maximize: 'toggleMaximize'
},
// Group: Attributes
/*
Property: id
DOM id of the window's element
Property: element
DOM element containing the window
Property: windowManager
Window manager that manages the window
Property: content
Window content element
Property: header
Window header element
Property: footer
Window footer element
Property: visible
true if window is visible
Property: focused
true if window is focused
Property: folded
true if window is folded
Property: maximized
true if window is maximized
*/
/*
Group: Events
List of events fired by a window
*/
/*
Property: created
Fired after creating the window
Property: destroyed
Fired after destroying the window
Property: showing
Fired when showing a window
Property: shown
Fired after showing effect
Property: hiding
Fired when hiding a window
Property: hidden
Fired after hiding effect
Property: focused
Fired after focusing the window
Property: blurred
Fired after bluring the window
Property: maximized
Fired after maximizing the window
Property: restored
Fired after restoring the window from its maximized state
Property: fold
Fired after unfolding the window
Property: unfold
Fired after folding the window
Property: altitude:changed
Fired when window altitude has changed (z-index)
Property: size:changed
Fired when window size has changed
Property: position:changed
Fired when window position has changed
Property: move:started
Fired when user has started a moving a window, position:changed are then fired continously
Property: move:ended
Fired when user has finished moving a window
Property: resize:started
Fired when user has started resizing window, size:changed are then fired continuously
Property: resize:ended
Fired when user has finished resizing window
*/
// Group: Contructor
/*
Method: initialize
Constructor, should not be called directly, it's called by new operator (new Window())
The window is not open and nothing has been added to the DOM yet
Parameters:
options - (Hash) list of optional parameters
Returns:
this
*/
initialize: function(options) {
this.setOptions(options);
this.windowManager = this.options.windowManager || UI.defaultWM;
this.create();
this.id = this.element.id;
this.windowManager.register(this);
this.render();
if (this.options.activeOnClick)
this.overlay.setStyle({ zIndex: this.lastZIndex + 1 }).show();
},
/*
Method: destroy
Destructor, closes window, cleans up DOM and memory
*/
destroy: function($super) {
this.hide();
if (this.centerOptions)
Event.stopObserving(this.windowManager.scrollContainer, "scroll", this.centerOptions.handler);
this.windowManager.unregister(this);
this.fire('destroyed');
},
// Group: Event handling
/*
Method: fire
Fires a window custom event automatically namespaced in "window:" (see Prototype custom events).
The memo object contains a "window" property referring to the window.
Example:
> UI.Window.addMethods({
>   iconify: function() {
>     // ... your iconifying code here ...
>     this.fire('iconified');
>     // chain friendly
>     return this;
>   }
> });
>
> document.observe('window:iconified', function(event) {
>   alert("Window with id " + event.memo.window.id + " has just been iconified");
> });
Parameters:
eventName - an event name
memo - a memo object
Returns:
fired event
*/
fire: function(eventName, memo) {
memo = memo || { };
memo.window = this;
return this.element.fire('window:' + eventName, memo);
},
/*
Method: observe
Observe a window event with a handler function automatically bound to the window
Parameters:
eventName - an event name
handler - a handler function
Returns:
this
*/
observe: function(eventName, handler) {
this.element.observe('window:' + eventName, handler.bind(this));
return this;
},
// Group: Actions
/*
Method: show
Opens the window (appends it to the DOM)
Parameters:
modal - open the window in a modal mode (default false)
Returns:
this
*/
show: function(modal) {
if (this.visible) return this;
this.fire('showing');
this.effect('show');
if (modal) {
this.windowManager.startModalSession(this);
this.modalSession = true;
}
this.addElements();
this.visible = true;
new PeriodicalExecuter(function(executer) {
if (!this.element.visible()) return;
this.fire('shown');
executer.stop();
}.bind(this), 0.1);
return this;
},
/*
Method: hide
Hides the window, (removes it from the DOM)
Returns:
this
*/
hide: function() {
if (!this.visible) return this;
this.fire('hiding');
this.effect('hide');
if (this.modalSession) {
this.windowManager.endModalSession(this);
this.modalSession = false;
}
this.windowManager.hide(this);
new PeriodicalExecuter(function(executer) {
if (this.element.visible()) return;
this.visible = false;
this.element.remove();
this.fire('hidden');
executer.stop();
}.bind(this), 0.1);
return this;
},
close: function() {
return this.action('close');
},
/*
Method: activate
Brings window to the front and sets focus on it
Returns:
this
*/
activate: function() {
return this.bringToFront().focus();
},
/*
Method: bringToFront
Brings window to the front (but does not set focus on it)
Returns:
this
*/
bringToFront: function() {
return this.setAltitude('front');
},
/*
Method: sendToBack
Sends window to the back (without changing its focus)
Returns:
this
*/
sendToBack: function() {
return this.setAltitude('back');
},
/*
Method: focus
Focuses the window (without bringing window to the front)
Returns:
this
*/
focus: function() {
if (this.focused) return this;
this.windowManager.focus(this);
// Hide the overlay that catch events
this.overlay.hide();
// Add focused class name
this.element.addClassName(this.options.theme + '_focused');
this.focused = true;
this.fire('focused');
return this;
},
/*
Method: blur
Blurs the window (without changing windows order)
Returns:
this
*/
blur: function() {
if (!this.focused) return this;
this.windowManager.blur(this);
this.element.removeClassName(this.options.theme + '_focused');
// Show the overlay to catch events
if (this.options.activeOnClick)
this.overlay.setStyle({ zIndex: this.lastZIndex + 1 }).show();
this.focused = false;
this.fire('blurred');
return this;
},
/*
Method: maximize
Maximizes window inside its viewport (managed by WindowManager)
Makes window take full size of its viewport
Returns:
this
*/
maximize: function() {
if (this.maximized) return this;
// Get bounds has to be before  this.windowManager.maximize for IE!! this.windowManager.maximize remove overflow
// and it breaks this.getBounds()
var bounds = this.getBounds();
if (this.windowManager.maximize(this)) {
this.disableButton('minimize').setResizable(false).setDraggable(false);
this.activate();
this.maximized = true;
this.savedArea = bounds;
var newBounds = Object.extend(this.windowManager.viewport.getDimensions(), { top: 0, left: 0 });
this[this.options.superflousEffects && !Prototype.Browser.IE ? "morph" : "setBounds"](newBounds);
this.fire('maximized');
return this;
}
},
/*
Function: restore
Restores a maximized window to its initial size
Returns:
this
*/
restore: function() {
if (!this.maximized) return this;
if (this.windowManager.restore(this)) {
this[this.options.superflousEffects  && !Prototype.Browser.IE ? "morph" : "setBounds"](this.savedArea);
this.enableButton("minimize").setResizable(true).setDraggable(true);
this.maximized = false;
this.fire('restored');
return this;
}
},
/*
Function: toggleMaximize
Maximizes/Restores window inside it's viewport (managed by WindowManager)
Returns:
this
*/
toggleMaximize: function() {
return this.maximized ? this.restore() : this.maximize();
},
/*
Function: adapt
Adapts window size to fit its content
Returns:
this
*/
adapt: function() {
var dimensions = this.content.getScrollDimensions();
if (this.options.superflousEffects)
this.morph(dimensions, true);
else
this.setSize(dimensions.width, dimensions.height, true);
return this;
},
/*
Method: fold
Folds window content
Returns:
this
*/
fold: function() {
if (!this.folded) {
var size = this.getSize(true);
this.folded = true;
this.savedInnerHeight = size.height;
if (this.options.superflousEffects)
this.morph({ width: size.width, height: 0 }, true);
else
this.setSize(size.width, 0, true);
this.setResizable(false);
this.fire("fold");
}
return this;
},
/*
Method: unfold
Unfolds window content
Returns:
this
*/
unfold: function() {
if (this.folded) {
var size = this.getSize(true);
this.folded = false;
if (this.options.superflousEffects)
this.morph({ width: size.width, height: this.savedInnerHeight }, true);
else
this.setSize(size.width, this.savedInnerHeight, true);
this.setResizable(true);
this.fire("unfold");
}
return this;
},
/*
Method: toggleFold
Folds/Unfolds window content
Returns:
this
*/
toggleFold: function() {
return this.folded ? this.unfold() : this.fold();
},
/*
Method: setHeader
Sets window header, equivalent to this.header.update(...) but allows chaining
Returns:
this
*/
setHeader: function(header) {
this.header.update(header);
return this;
},
/*
Method: setContent
Sets window content, equivalent to this.content.update(...) but allows chaining
Returns:
this
*/
setContent: function(content) {
this.content.update(content);
return this;
},
/*
Method: setFooter
Sets window footer, equivalent to this.footer.update(...) but allows chaining
Returns:
this
*/
setFooter: function(footer) {
this.footer.update(footer);
return this;
},
/*
Method: setAjaxContent
Sets window content using Ajax request
Parameters:
url - Ajax URL
options - Ajax Updater options (see http://prototypejs.org/api/ajax/options and
http://prototypejs.org/api/ajax/updater)
Returns:
this
*/
setAjaxContent: function(url, options) {
// bind all callbacks to the window
Object.keys(options || { }).each(function(name) {
if (Object.isFunction(options[name]))
options[name] = options[name].bind(this);
}, this);
new Ajax.Updater(this.content, url, options);
return this;
},
// Group: Size and Position
/*
Method: getPosition
Returns top/left position of a window (in pixels)
Returns:
an Hash {top:, left:}
*/
getPosition: function() {
return { left: this.options.left, top: this.options.top };
},
/*
Method: setPosition
Sets top/left position of a window (in pixels)
Parameters
top:  top position in pixel
left: left position in pixel
Returns:
this
*/
setPosition: function(top, left) {
var pos = this.computePosition(top, left);
this.options.top  = pos.top;
this.options.left = pos.left;
var elementStyle  = this.element.style;
elementStyle.top  = pos.top + 'px';
elementStyle.left = pos.left + 'px';
this.fire('position:changed');
return this;
},
/*
Method: center
Centers the window within its viewport
Returns:
this
*/
center: function(options) {
var size          = this.getSize(),
windowManager = this.windowManager,
viewport      = windowManager.viewport;
viewportArea  = viewport.getDimensions(),
offset        = viewport.getScrollOffset();
if (options && options.auto) {
this.centerOptions = Object.extend({ handler: this.recenter.bind(this) }, options);
Event.observe(this.windowManager.scrollContainer,"scroll", this.centerOptions.handler);
Event.observe(window,"resize", this.centerOptions.handler);
}
options = Object.extend({
top:  (viewportArea.height - size.height) / 2,
left: (viewportArea.width  - size.width)  / 2
}, options || {});
return this.setPosition(options.top + offset.top, options.left + offset.left);
},
/*
Method: getSize
Returns window width/height dimensions (in pixels)
Parameters
innerSize: returns content size if true, window size if false (defaults to false)
Returns:
Hash {width:, height:}
*/
getSize: function(innerSize) {
if (innerSize)
return { width:  this.options.width  - this.borderSize.width,
height: this.options.height - this.borderSize.height };
else
return { width: this.options.width, height: this.options.height };
},
/*
Method: setSize
Sets window width/height dimensions (in pixels), fires size:changed
Parameters
width:  width (in pixels)
height: height (in pixels)
innerSize: if true change set content size, else set window size (defaults to false)
Returns:
this
*/
setSize: function(width, height, innerSize) {
var size = this.computeSize(width, height, innerSize);
var elementStyle = this.element.style, contentStyle = this.content.style;
this.options.width  = size.outerWidth;
this.options.height = size.outerHeight;
elementStyle.width = size.outerWidth + "px", elementStyle.height = size.outerHeight + "px";
contentStyle.width = size.innerWidth + "px", contentStyle.height = size.innerHeight + "px";
this.overlay.style.height = size.innerHeight + "px";
this.fire('size:changed');
return this;
},
/*
Method: getBounds
Returns window bounds (in pixels)
Parameters
innerSize: returns content size if true, window size otherwise
Returns:
an Hash {top:, left:, width:, height:}
*/
getBounds: function(innerSize) {
return Object.extend(this.getPosition(), this.getSize(innerSize));
},
/*
Method: setBounds
Sets window bounds (in pixels), fires position:changed and size:changed
Parameters
bounds: Hash {top:, left:, width:, height:} where all values are optional
innerSize: sets content size if true, window size otherwise
Returns:
Hash {top:, left:, width:, height:}
*/
setBounds: function(bounds, innerSize) {
return this.setPosition(bounds.top, bounds.left)
.setSize(bounds.width, bounds.height, innerSize);
},
morph: function(bounds, innerSize) {
bounds = Object.extend(this.getBounds(innerSize), bounds || {});
if (this.centerOptions && this.centerOptions.auto)
bounds = Object.extend(bounds, this.computeRecenter(bounds));
if (innerSize) {
bounds.width  += this.borderSize.width;
bounds.height += this.borderSize.height;
}
this.animating = true;
new UI.Window.Effects.Morph(this, bounds, {
duration: 0.5,
afterFinish: function() { this.animating = false }.bind(this)
});
Object.extend(this.options, bounds);
return this;
},
/*
Method: getAltitude
Returns window altitude, an integer between 0 and the number of windows,
the higher the altitude number - the higher the window position.
*/
getAltitude: function() {
return this.windowManager.getAltitude(this);
},
/*
Method: setAltitude
Sets window altitude, fires 'altitude:changed' if altitude was changed
*/
setAltitude: function(altitude) {
if (this.windowManager.setAltitude(this, altitude))
this.fire('altitude:changed');
return this;
},
/*
Method: setResizable
TODO
*/
setResizable: function(resizable) {
this.options.resizable = resizable;
var toggleClassName = (resizable ? 'add' : 'remove') + 'ClassName';
this.element[toggleClassName]('resizable')
.select('div:[class*=_sizer]').invoke(resizable ? 'show' : 'hide');
if (resizable)
this.createResizeHandles();
this.element.select('div.se').first()[toggleClassName]('se_resize_handle');
return this;
},
/*
Method: setDraggable
TODO
*/
setDraggable: function(draggable) {
this.options.draggable = draggable;
this.element[(draggable ? 'add' : 'remove') + 'ClassName']('draggable');
return this;
},
// Group: Theme
/*
Method: getTheme
Returns window theme name
*/
getTheme: function() {
return this.options.theme || this.windowManager.getTheme();
},
/*
Method: setTheme
Sets window theme
*/
setTheme: function(theme, windowManagerTheme) {
this.element.removeClassName(this.getTheme()).addClassName(theme);
// window has it's own theme
if (!windowManagerTheme)
this.options.theme = theme;
return this;
},
/*
Method: getShadowTheme
Returns shadow theme name
*/
getShadowTheme: function() {
return this.options.shadowTheme || this.windowManager.getShadowTheme();
}
});
UI.Window.addMethods(UI.Window.Buttons);
UI.Window.addMethods(UI.Window.Shadow);
UI.Window.optionsAccessor($w(" minWidth minHeight maxWidth maxHeight gridX gridY altitude "));
// Private functions for window.js
UI.Window.addMethods({
style: "position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;",
action: function(name) {
var action = this.options[name];
if (action)
Object.isString(action) ? this[action]() : action.call(this, this);
},
create: function() {
function createDiv(className, options) {
return new Element('div', Object.extend({ className: className }, options));
};
// Main div
this.element = createDiv("ui-window " + this.getTheme(), {
id: this.options.id,
style: "top:-10000px; left:-10000px"
});
// Create HTML window code
this.header  = createDiv('n move_handle').enableDrag();
this.content = createDiv('content').appendText(' ');
this.footer  = createDiv('s move_handle').enableDrag();
var header   = createDiv('nw').insert(createDiv('ne').insert(this.header));
var content  = createDiv('w').insert(createDiv('e', {style: "position:relative"}).insert(this.content));
var footer   = createDiv('sw').insert(createDiv('se' + (this.options.resizable ?  " se_resize_handle" : "")).insert(this.footer));
this.element.insert(header).insert(content).insert(footer).identify('ui-window');
this.header.observe('mousedown', this.activate.bind(this));
this.setDraggable(this.options.draggable);
this.setResizable(this.options.resizable);
this.overlay = new Element('div', { style: this.style + "display: none" })
.observe('mousedown', this.activate.bind(this));
if (this.options.activeOnClick)
this.content.insert({ before: this.overlay });
},
createWiredElement: function() {
this.wiredElement = this.wiredElement || new Element("div", {
className: this.getTheme() + "_wired",
style:    "display: none; position: absolute; top: 0; left: 0"
});
},
createResizeHandles: function() {
$w(" n  w  e  s  nw  ne  sw  se ").each(function(id) {
this.insert(new Element("div", {
className:   id + "_sizer resize_handle",
drag_prefix: id }).enableDrag());
}, this.element);
this.createResizeHandles = Prototype.emptyFunction;
},
// First rendering, pre-compute window border size
render: function() {
this.addElements();
this.computeBorderSize();
this.updateButtonsOrder();
this.element.hide().remove();
// this.options contains top, left, width and height keys
return this.setBounds(this.options);
},
// Adds window elements to the DOM
addElements: function() {
this.windowManager.container.appendChild(this.element);
},
// Set z-index to all window elements
setZIndex: function(zIndex) {
if (this.zIndex != zIndex) {
this.zIndex = zIndex;
[ this.element ].concat(this.element.childElements()).each(function(element) {
element.style.zIndex = zIndex++;
});
this.lastZIndex = zIndex;
}
return this;
},
effect: function(name, element, options) {
var effect = this.options[name] || Prototype.emptyFunction;
effect(element || this.element, options || {});
},
// re-compute window border size
computeBorderSize: function() {
if (this.element) {
if (Prototype.Browser.IEVersion >= 7)
this.content.style.width = "100%";
var dim = this.element.getDimensions(), pos = this.content.positionedOffset();
this.borderSize = {  top:    pos[1],
bottom: dim.height - pos[1] - this.content.getHeight(),
left:   pos[0],
right:  dim.width - pos[0] - this.content.getWidth() };
this.borderSize.width  = this.borderSize.left + this.borderSize.right;
this.borderSize.height = this.borderSize.top  + this.borderSize.bottom;
if (Prototype.Browser.IEVersion >= 7)
this.content.style.width = "auto";
}
},
computeSize: function(width, height, innerSize) {
var innerWidth, innerHeight, outerWidth, outerHeight;
if (innerSize) {
outerWidth  =  width  + this.borderSize.width;
outerHeight =  height + this.borderSize.height;
} else {
outerWidth  =  width;
outerHeight =  height;
}
// Check grid value
if (!this.animating) {
outerWidth = outerWidth.snap(this.options.gridX);
outerHeight = outerHeight.snap(this.options.gridY);
// Check min size
if (!this.folded) {
if (outerWidth < this.options.minWidth)
outerWidth = this.options.minWidth;
if (outerHeight < this.options.minHeight)
outerHeight = this.options.minHeight;
}
// Check max size
if (this.options.maxWidth && outerWidth > this.options.maxWidth)
outerWidth = this.options.maxWidth;
if (this.options.maxHeight && outerHeight > this.options.maxHeight)
outerHeight = this.options.maxHeight;
}
if (this.centerOptions && this.centerOptions.auto)
this.recenter();
innerWidth  = outerWidth - this.borderSize.width;
innerHeight = outerHeight - this.borderSize.height;
return {
innerWidth: innerWidth, innerHeight: innerHeight,
outerWidth: outerWidth, outerHeight: outerHeight
};
},
computePosition: function(top, left) {
if (this.centerOptions && this.centerOptions.auto)
return this.computeRecenter(this.getSize());                                                                                                            ;
return {
top:  this.animating ? top  : top.snap(this.options.gridY),
left: this.animating ? left : left.snap(this.options.gridX)
};
},
computeRecenter: function(size) {
var viewport   = this.windowManager.viewport,
area       = viewport.getDimensions(),
offset     = viewport.getScrollOffset(),
center     = {
top:  Object.isUndefined(this.centerOptions.top)  ? (area.height - size.height) / 2 : this.centerOptions.top,
left: Object.isUndefined(this.centerOptions.left) ? (area.width  - size.width)  / 2 : this.centerOptions.left
};
return {
top:  parseInt(center.top + offset.top),
left: parseInt(center.left + offset.left)
};
},
recenter: function(event) {
var pos = this.computeRecenter(this.getSize());
this.setPosition(pos.top, pos.left);
}
});
UI.URLWindow = Class.create(UI.Window, {
options: {
url: 'about:blank'
},
afterClassCreate: function() {
this.undefMethod('setAjaxContent');
},
initialize: function($super, options) {
$super(options);
this.setUrl(this.options.url);
},
destroy: function($super){
this.iframe.src = null;
$super();
},
getUrl: function() {
return this.iframe.src;
},
setUrl: function(url, options) {
this.iframe.src = url;
return this;
},
create: function($super) {
$super();
this.iframe = new Element('iframe', {
style: this.style,
frameborder: 0,
src: this.options.url,
name: this.element.id + "_frame",
id:  this.element.id + "_frame"
});
this.content.insert(this.iframe);
}
});
if (!Object.isUndefined(window.Effect)) {
UI.Window.Effects = UI.Window.Effects || {};
UI.Window.Effects.Morph = Class.create(Effect.Base, {
initialize: function(window, bounds) {
this.window = window;
var options = Object.extend({
fromBounds: this.window.getBounds(),
toBounds:   bounds,
from:       0,
to:         1
}, arguments[2] || { });
this.start(options);
},
update: function(position) {
var t = this.options.fromBounds.top + (this.options.toBounds.top   - this.options.fromBounds.top) * position;
var l = this.options.fromBounds.left + (this.options.toBounds.left - this.options.fromBounds.left) * position;
var ow = this.options.fromBounds.width + (this.options.toBounds.width - this.options.fromBounds.width) * position;
var oh = this.options.fromBounds.height + (this.options.toBounds.height - this.options.fromBounds.height) * position;
this.window.setBounds({top: t,  left: l, width: ow, height: oh})
}
});
}
UI.Window.addMethods({
startDrag: function(handle) {
this.initBounds = this.getBounds();
this.activate();
if (this.options.wired) {
this.createWiredElement();
this.wiredElement.style.cssText = this.element.style.cssText;
this.element.hide();
this.saveElement = this.element;
this.windowManager.container.appendChild(this.wiredElement);
this.element = this.wiredElement;
}
handle.hasClassName('resize_handle') ? this.startResize(handle) : this.startMove();
},
endDrag: function() {
this.element.hasClassName('resized') ? this.endResize() : this.endMove();
if (this.options.wired) {
this.saveElement.style.cssText = this.wiredElement.style.cssText;
this.wiredElement.remove();
this.element = this.saveElement;
}
},
startMove: function() {
// method used to drag
this.drag = this.moveDrag;
this.element.addClassName('moved');
this.fire('move:started');
},
endMove: function() {
this.element.removeClassName('moved');
this.fire('move:ended');
},
startResize: function(handle) {
this.drag = this[handle.readAttribute('drag_prefix')+'Drag'];
this.element.addClassName('resized');
this.fire('resize:started');
},
endResize: function() {
this.element.removeClassName('resized');
this.fire('resize:ended');
},
moveDrag: function(dx, dy) {
this.setPosition(this.initBounds.top + dy, this.initBounds.left + dx);
},
swDrag: function(dx, dy) {
var initBounds = this.initBounds;
this.setSize(initBounds.width - dx, initBounds.height + dy)
.setPosition(initBounds.top,
initBounds.left + (initBounds.width - this.getSize().width));
},
seDrag: function(dx, dy) {
this.setSize(this.initBounds.width + dx, this.initBounds.height + dy);
},
nwDrag: function(dx, dy) {
var initBounds = this.initBounds;
this.setSize(initBounds.width - dx, initBounds.height - dy)
.setPosition(initBounds.top + (initBounds.height - this.getSize().height),
initBounds.left + (initBounds.width - this.getSize().width));
},
neDrag: function(dx, dy) {
var initBounds = this.initBounds;
this.setSize(initBounds.width + dx, initBounds.height - dy)
.setPosition(initBounds.top + (initBounds.height - this.getSize().height),
initBounds.left);
},
wDrag: function(dx, dy) {
var initBounds = this.initBounds;
this.setSize(initBounds.width - dx, initBounds.height)
.setPosition(initBounds.top,
initBounds.left + (initBounds.width - this.getSize().width));
},
eDrag: function(dx, dy) {
this.setSize(this.initBounds.width + dx, this.initBounds.height);
},
nDrag: function(dx, dy) {
var initBounds = this.initBounds;
this.setSize(initBounds.width, initBounds.height - dy)
.setPosition(initBounds.top + (initBounds.height - this.getSize().height),
initBounds.left);
},
sDrag: function(dx, dy) {
this.setSize(this.initBounds.width, this.initBounds.height + dy);
}
});
UI.Window.addMethods({
methodsAdded: function(base) {
base.aliasMethodChain('create',  'buttons');
base.aliasMethodChain('destroy', 'buttons');
},
createWithButtons: function() {
this.createWithoutButtons();
if (!this.options.resizable) {
this.options.minimize = false;
this.options.maximize = false;
}
this.buttons = new Element("div", { className: "buttons" })
.observe('click',     this.onButtonsClick.bind(this))
.observe('mouseover', this.onButtonsHover.bind(this))
.observe('mouseout',  this.onButtonsOut.bind(this));
this.element.insert(this.buttons);
this.defaultButtons.each(function(button) {
if (this.options[button] !== false)
this.addButton(button);
}, this);
},
destroyWithButtons: function() {
this.buttons.stopObserving();
this.destroyWithoutButtons();
},
defaultButtons: $w(' minimize maximize close '),
getButtonElement: function(buttonName) {
return this.buttons.down("." + buttonName);
},
// Controls close, minimize, maximize, etc.
// action can be either a string or a function
// if action is a string, it is the method name that will be called
// else the function will take the window as first parameter.
// if not given action will be taken in window's options
addButton: function(buttonName, action) {
this.buttons.insert(new Element("a", { className: buttonName, href: "#"}));
if (action)
this.options[buttonName] = action;
return this;
},
removeButton: function(buttonName) {
this.getButtonElement(buttonName).remove();
return this;
},
disableButton: function(buttonName) {
this.getButtonElement(buttonName).addClassName("disabled");
return this;
},
enableButton: function(buttonName) {
this.getButtonElement(buttonName).removeClassName("disabled");
return this;
},
onButtonsClick: function(event) {
var element = event.findElement('a:not(.disabled)');
if (element) this.action(element.className);
event.stop();
},
onButtonsHover: function(event) {
this.buttons.addClassName("over");
},
onButtonsOut: function(event) {
this.buttons.removeClassName("over");
},
updateButtonsOrder: function() {
var buttons = this.buttons.childElements();
buttons.inject(new Array(buttons.length), function(array, button) {
array[parseInt(button.getStyle("padding-top"))] = button.setStyle("padding: 0");
return array;
}).each(function(button) { this.buttons.appendChild(button) }, this);
}
});
UI.Window.addMethods({
methodsAdded: function(base) {
(function(methods) {
$w(methods).each(function(m) { base.aliasMethodChain(m, 'shadow') });
})(' create addElements setZIndex setPosition setSize setBounds ');
},
showShadow: function() {
if (this.shadow) {
this.shadow.hide();
this.effect('show', this.shadow.shadow);
}
},
hideShadow: function() {
if (this.shadow)
this.effect('hide', this.shadow.shadow);
},
removeShadow: function() {
if (this.shadow)
this.shadow.remove();
},
focusShadow: function() {
if (this.shadow)
this.shadow.focus();
},
blurShadow: function() {
if (this.shadow)
this.shadow.blur();
},
// Private Functions
createWithShadow: function() {
this.createWithoutShadow();
this.observe('showing', this.showShadow)
.observe('hiding',  this.hideShadow)
.observe('hidden',  this.removeShadow)
.observe('focused', this.focusShadow)
.observe('blurred', this.blurShadow);
if (this.options.shadow)
this.shadow = new UI.Shadow(this.element, {theme: this.getShadowTheme()});
},
addElementsWithShadow: function() {
this.addElementsWithoutShadow();
if (this.shadow) {
this.shadow.setBounds(this.options).render();
}
},
setZIndexWithShadow: function(zIndex) {
if (this.zIndex != zIndex) {
if (this.shadow)
this.shadow.setZIndex(zIndex - 1);
this.setZIndexWithoutShadow(zIndex);
this.zIndex = zIndex;
}
return this;
},
setPositionWithShadow: function(top, left) {
this.setPositionWithoutShadow(top, left);
if (this.shadow) {
var pos = this.getPosition();
this.shadow.setPosition(pos.top, pos.left);
}
return this;
},
setSizeWithShadow: function(width, height, innerSize) {
this.setSizeWithoutShadow(width, height, innerSize);
if (this.shadow) {
var size = this.getSize();
this.shadow.setSize(size.width, size.height);
}
return this;
},
setBoundsWithShadow: function(bounds, innerSize) {
this.setBoundsWithoutShadow(bounds, innerSize);
if (this.shadow)
this.shadow.setBounds(this.getBounds());
}
});
/*
Class: UI.WindowManager
Window Manager.
A default instance of this class is created in UI.defaultWM.
Example:
> new UI.WindowManger({
>   container: 'desktop',
>   theme: 'mac_os_x'
> });
*/
UI.WindowManager = Class.create(UI.Options, {
options: {
container:   null, // will default to document.body
zIndex:      0,
theme:       "alphacube",
shadowTheme: "mac_shadow",
showOverlay: Element.show,
hideOverlay: Element.hide,
positionningStrategy: function(win, area) {
UI.WindowManager.DumbPositionningStrategy(win, area);
}
},
initialize: function(options) {
this.setOptions(options);
this.container = $(this.options.container || document.body);
if (this.container === $(document.body)) {
this.viewport = document.viewport;
this.scrollContainer = window;
} else {
this.viewport = this.scrollContainer = this.container;
}
this.container.observe('drag:started', this.onStartDrag.bind(this))
.observe('drag:updated', this.onDrag.bind(this))
.observe('drag:ended',   this.onEndDrag.bind(this));
this.stack = new UI.WindowManager.Stack();
this.modalSessions = 0;
this.createOverlays();
this.resizeEvent = this.resize.bind(this);
Event.observe(window, "resize", this.resizeEvent);
},
destroy: function() {
this.windows().invoke('destroy');
this.stack.destroy();
Event.stopObserving(window, "resize", this.resizeEvent);
},
/*
Method: setTheme
Changes window manager's theme, all windows that don't have a own theme
will have this new theme.
Parameters:
theme - theme name
Example:
> UI.defaultWM.setTheme('bluelighting');
*/
setTheme: function(theme) {
this.stack.windows.select(function(w) {
return !w.options.theme;
}).invoke('setTheme', theme, true);
this.options.theme = theme;
return this;
},
register: function(win) {
if (this.getWindow(win.id)) return;
this.handlePosition(win);
this.stack.add(win);
this.restartZIndexes();
},
unregister: function(win) {
this.stack.remove(win);
if (win == this.focusedWindow)
this.focusedWindow = null;
},
/*
Method: getWindow
Find the window containing a given element.
Example:
> $$('.ui-window a.close').invoke('observe', 'click', function() {
>   UI.defaultWM.getWindow(this).close();
> });
Parameters:
element - element or element identifier
Returns:
containing window or null
*/
getWindow: function(element) {
element = $(element);
if (!element) return;
if (!element.hasClassName('ui-window'))
element = element.up('.ui-window');
var id = element.id;
return this.stack.windows.find(function(win) { return win.id == id });
},
/*
Method: windows
Returns an array of all windows handled by this window manager.
First one is the back window, last one is the front window.
Example:
> UI.defaultWM.windows().invoke('destroy');
*/
windows: function() {
return this.stack.windows.clone();
},
/*
Method: getFocusedWindow
Returns the focused window
*/
getFocusedWindow: function() {
return this.focusedWindow;
},
// INTERNAL
// Modal mode
startModalSession: function(win) {
if (!this.modalSessions) {
this.removeOverflow();
this.modalOverlay.className = win.getTheme() + "_overlay";
this.container.appendChild(this.modalOverlay);
if (!this.modalOverlay.opacity)
this.modalOverlay.opacity = this.modalOverlay.getOpacity();
this.modalOverlay.setStyle("height: " + this.viewport.getHeight() + "px");
this.options.showOverlay(this.modalOverlay, {from: 0, to: this.modalOverlay.opacity});
}
this.modalOverlay.setStyle({ zIndex: win.zIndex - 1 });
this.modalSessions++;
},
endModalSession: function(win) {
this.modalSessions--;
if (this.modalSessions) {
this.modalOverlay.setStyle({ zIndex: this.stack.getPreviousWindow(win).zIndex - 1 });
} else {
this.resetOverflow();
this.options.hideOverlay(this.modalOverlay, { from: this.modalOverlay.opacity, to: 0 });
}
},
moveHandleSelector:   '.ui-window.draggable .move_handle',
resizeHandleSelector: '.ui-window.resizable .resize_handle',
onStartDrag: function(event) {
var handle = event.element(),
isMoveHandle   = handle.match(this.moveHandleSelector),
isResizeHandle = handle.match(this.resizeHandleSelector);
// ensure dragged element is a window handle !
if (isResizeHandle || isMoveHandle) {
event.stop();
// find the corresponding window
var win = this.getWindow(event.findElement('.ui-window'));
// render drag overlay
this.container.insert(this.dragOverlay.setStyle({ zIndex: this.getLastZIndex() }));
win.startDrag(handle);
this.draggedWindow = win;
}
},
onDrag: function(event) {
if (this.draggedWindow) {
event.stop();
this.draggedWindow.drag(event.memo.dx, event.memo.dy);
}
},
onEndDrag: function(event) {
if (this.draggedWindow) {
event.stop();
this.dragOverlay.remove();
this.draggedWindow.endDrag();
this.draggedWindow = null;
}
},
maximize: function(win) {
this.removeOverflow();
this.maximizedWindow = win;
return true;
},
restore: function(win) {
if (this.maximizedWindow) {
this.resetOverflow();
this.maximizedWindow = false;
}
return true;
},
removeOverflow: function() {
var container = this.container;
// Remove overflow, save overflow and scrolloffset values to restore them when restore window
container.savedOverflow = container.style.overflow || "auto";
container.savedOffset = this.viewport.getScrollOffset();
container.style.overflow = "hidden";
this.viewport.setScrollOffset({ top:0, left:0 });
if (this.container == document.body && Prototype.Browser.IE)
this.cssRule = CSS.addRule("html { overflow: hidden }");
},
resetOverflow: function() {
var container = this.container;
// Restore overflow ans scrolloffset
if (container.savedOverflow) {
if (this.container == document.body && Prototype.Browser.IE)
this.cssRule.remove();
container.style.overflow = container.savedOverflow;
this.viewport.setScrollOffset(container.savedOffset);
container.savedOffset = container.savedOverflow = null;
}
},
hide: function(win) {
var previous = this.stack.getPreviousWindow(win);
if (previous) previous.focus();
},
restartZIndexes: function(){
// Reset zIndex
var zIndex = this.getZIndex() + 1; // keep a zIndex free for overlay divs
this.stack.windows.each(function(w) {
w.setZIndex(zIndex);
zIndex = w.lastZIndex + 1;
});
},
getLastZIndex: function() {
return this.stack.getFrontWindow().lastZIndex + 1;
},
overlayStyle: "position: absolute; top: 0; left: 0; display: none; width: 100%;",
createOverlays: function() {
this.modalOverlay = new Element("div", { style: this.overlayStyle });
this.dragOverlay  = new Element("div", { style: this.overlayStyle+"height: 100%" });
},
focus: function(win) {
// Blur the previous focused window
if (this.focusedWindow)
this.focusedWindow.blur();
this.focusedWindow = win;
},
blur: function(win) {
if (win == this.focusedWindow)
this.focusedWindow = null;
},
setAltitude: function(win, altitude) {
var stack = this.stack;
if (altitude === "front") {
if (stack.getFrontWindow() === win) return;
stack.bringToFront(win);
} else if (altitude === "back") {
if (stack.getBackWindow() === win) return;
stack.sendToBack(win);
} else {
if (stack.getPosition(win) == altitude) return;
stack.setPosition(win, altitude);
}
this.restartZIndexes();
return true;
},
getAltitude: function(win) {
return this.stack.getPosition(win);
},
resize: function(event) {
var area = this.viewport.getDimensions();
if (this.maximizedWindow)
this.maximizedWindow.setSize(area.width, area.height);
if (this.modalOverlay.visible())
this.modalOverlay.setStyle("height:" + area.height + "px");
},
handlePosition: function(win) {
// window has its own position, nothing needs to be done
if (Object.isNumber(win.options.top) && Object.isNumber(win.options.left))
return;
var strategy = this.options.positionningStrategy,
area     = this.viewport.getDimensions();
Object.isFunction(strategy) ? strategy(win, area) : strategy.position(win, area);
}
});
UI.WindowManager.DumbPositionningStrategy = function(win, area) {
size = win.getSize();
var top  = area.height - size.height,
left = area.width  - size.width;
top  = top  < 0 ? 0 : Math.random() * top;
left = left < 0 ? 0 : Math.random() * left;
win.setPosition(top, left);
};
UI.WindowManager.optionsAccessor('zIndex', 'theme', 'shadowTheme');
UI.WindowManager.Stack = Class.create(Enumerable, {
initialize: function() {
this.windows = [ ];
},
each: function(iterator) {
this.windows.each(iterator);
},
add: function(win, position) {
this.windows.splice(position || this.windows.length, 0, win);
},
remove: function(win) {
this.windows = this.windows.without(win);
},
sendToBack: function(win) {
this.remove(win);
this.windows.unshift(win);
},
bringToFront: function(win) {
this.remove(win);
this.windows.push(win);
},
getPosition: function(win) {
return this.windows.indexOf(win);
},
setPosition: function(win, position) {
this.remove(win);
this.windows.splice(position, 0, win);
},
getFrontWindow: function() {
return this.windows.last();
},
getBackWindow: function() {
return this.windows.first();
},
getPreviousWindow: function(win) {
return (win == this.windows.first()) ? null : this.windows[this.windows.indexOf(win) - 1];
}
});
document.whenReady(function() {
UI.defaultWM = new UI.WindowManager();
});
//  Prototip 1.2.1 - 08-03-2008
//  Copyright (c) 2008 Nick Stakenburg (http://www.nickstakenburg.com)
//
//  Permission is hereby granted, free of charge, to any person obtaining
//  a copy of this software and associated documentation files (the
//  "Software"), to deal in the Software without restriction, including
//  without limitation the rights to use, copy, modify, merge, publish,
//  distribute, sublicense, and/or sell copies of the Software, and to
//  permit persons to whom the Software is furnished to do so, subject to
//  the following conditions:
//
//  The above copyright notice and this permission notice shall be
//  included in all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
//  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
//  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
//  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
//  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
//  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//  More information on this project:
//  http://www.nickstakenburg.com/projects/prototip/
var Prototip = {
Version: '1.2.1',
REQUIRED_Prototype: '1.6.0.2',
REQUIRED_Scriptaculous: '1.8.1',
start: function() {
this.require('Prototype');
Tips.initialize();
Element.observe(window, 'unload', this.unload);
},
// Version check
require: function(library) {
if ((typeof window[library] == 'undefined') ||
(this.convertVersionString(window[library].Version) <
this.convertVersionString(this['REQUIRED_' + library])))
throw('Lightview requires ' + library + ' >= ' + this['REQUIRED_' + library]);
},
convertVersionString: function(versionString) {
var v = versionString.replace(/_.*|\./g, '');
v = parseInt(v + '0'.times(4-v.length));
return versionString.indexOf('_') > -1 ? v-1 : v;
},
capture: function(func) {
if (!Prototype.Browser.IE) {
func = func.wrap(function(proceed, event) {
var rel = event.relatedTarget, cur = event.currentTarget;
if (rel && rel.nodeType == Node.TEXT_NODE) rel = rel.parentNode;
if (rel && rel != cur && rel.descendantOf && !(rel.descendantOf(cur)))
proceed(event);
});
}
return func;
},
unload: function() { Tips.removeAll(); }
};
var Tips = {
// Configuration
closeButtons: false,
zIndex: 1200,
tips : [],
visible : [],
initialize: function() {
this.zIndexTop = this.zIndex;
},
useEvent : (function(IE) { return {
'mouseover': (IE ? 'mouseenter' : 'mouseover'),
'mouseout': (IE ? 'mouseleave' : 'mouseout'),
'mouseenter': (IE ? 'mouseenter' : 'mouseover'),
'mouseleave': (IE ? 'mouseleave' : 'mouseout')
};})(Prototype.Browser.IE),
fixIE: (function(agent) {
var version = new RegExp('MSIE ([\\d.]+)').exec(agent);
return version ? (parseFloat(version[1]) < 7) : false;
})(navigator.userAgent),
add: function(tip) {
this.tips.push(tip);
},
remove: function(element) {
var tip = this.tips.find(function(t){ return t.element == $(element); });
if (tip) {
tip.deactivate();
if (tip.tooltip) {
tip.wrapper.remove();
if (Tips.fixIE) tip.iframeShim.remove();
}
this.tips = this.tips.without(tip);
}
},
removeAll: function() {
this.tips.each(function(tip) { this.remove(tip.element); }.bind(this));
},
raise: function(tip) {
if (tip.highest) return;
if (this.visible.length == 0) {
this.zIndexTop = this.zIndex;
for (var i=0;i<this.tips.length;i++) {
this.tips[i].wrapper.style.zIndex = this.zIndex;
}
}
tip.style.zIndex = this.zIndexTop++;
for (var i=0;i<this.tips.length;i++) { this.tips[i].wrapper.highest = false; };
tip.highest = true;
},
addVisibile: function(tip) {
this.removeVisible(tip);
this.visible.push(tip);
},
removeVisible: function(tip) {
this.visible = this.visible.without(tip);
}
};
Tips.initialize();
var Tip = Class.create({
initialize: function(element, content) {
this.element = $(element);
Tips.remove(this.element);
this.content = content;
var isHooking = (arguments[2] && arguments[2].hook);
var isShowOnClick = (arguments[2] && arguments[2].showOn == 'click');
this.options = Object.extend({
className: 'default',                 // see css, this will lead to .prototip .default
closeButton: Tips.closeButtons,       // true, false
delay: !isShowOnClick ? 0.2 : false,  // seconds before tooltip appears
duration: 0.3,                        // duration of the effect
effect: false,                        // false, 'appear' or 'blind'
hideAfter: false,                     // second before hide after no hover/activity
hideOn: 'mouseleave',                 // or any other event, false
hook: false,                          // { element: topLeft|topRight|bottomLeft|bottomRight, tip: see element }
offset: isHooking ? {x:0, y:0} : {x:16, y:16},
fixed: isHooking ? true : false,      // follow the mouse if false
showOn: 'mousemove',
target: this.element,                 // or another element
title: false,
viewport: isHooking ? false : true    // keep within viewport if mouse is followed
}, arguments[2] || {});
this.target = $(this.options.target);
this.setup();
if (this.options.effect) {
Prototip.require('Scriptaculous');
this.queue = { position: 'end', limit: 1, scope: this.wrapper.identify() }
}
Tips.add(this);
this.activate();
},
setup: function() {
this.wrapper = new Element('div', { className: 'prototip' }).setStyle({
display: 'none', zIndex: Tips.zIndex });
this.wrapper.identify();
if (Tips.fixIE) {
this.iframeShim = new Element('iframe', {
className : 'iframeShim',
src: 'javascript:false;',
frameBorder: 0
}).setStyle({
display: 'none',
zIndex: Tips.zIndex - 1
});
}
this.tip = new Element('div', { className : 'content' }).insert(this.content);
this.tip.insert(new Element('div').setStyle({ clear: 'both' }));
if (this.options.closeButton || (this.options.hideOn.element && this.options.hideOn.element == 'closeButton'))
this.closeButton = new Element('a', { href: '#', className: 'close' });
},
build: function() {
if (Tips.fixIE) document.body.appendChild(this.iframeShim).setOpacity(0);
// effects go smooth with extra wrapper
var wrapper = 'wrapper';
if (this.options.effect) {
this.effectWrapper = this.wrapper.appendChild(new Element('div', { className: 'effectWrapper' }));
wrapper = 'effectWrapper';
}
this.tooltip = this[wrapper].appendChild(new Element('div', { className: 'tooltip ' + this.options.className }));
if (this.options.title || this.options.closeButton) {
this.toolbar = this.tooltip.appendChild(new Element('div', { className: 'toolbar' }));
this.title = this.toolbar.appendChild(new Element('div', { className: 'title' }).update(this.options.title || ' '));
}
this.tooltip.insert(this.tip);
document.body.appendChild(this.wrapper);
// fixate elements for better positioning and effects
var fixate = (this.options.effect) ? [this.wrapper, this.effectWrapper]: [this.wrapper];
if (Tips.fixIE) fixate.push(this.iframeShim);
// fix width
var fixedWidth = this.wrapper.getWidth();
fixate.invoke('setStyle', { width: fixedWidth + 'px' });
// make toolbar width fixed
if(this.toolbar) {
this.wrapper.setStyle({ visibility : 'hidden' }).show();
this.toolbar.setStyle({ width: this.toolbar.getWidth() + 'px'});
this.wrapper.hide().setStyle({ visibility : 'visible' });
}
// add close button
if (this.closeButton)
this.title.insert({ top: this.closeButton }).insert(new Element('div').setStyle({ clear: 'both' }));
var fixedHeight = this.wrapper.getHeight();
fixate.invoke('setStyle', { width: fixedWidth + 'px', height: fixedHeight + 'px' });
this[this.options.effect ? wrapper : 'tooltip'].hide();
},
activate: function() {
this.eventShow = this.showDelayed.bindAsEventListener(this);
this.eventHide = this.hide.bindAsEventListener(this);
// if fixed use mouseover instead of mousemove for less event calls
if (this.options.fixed && this.options.showOn == 'mousemove') this.options.showOn = 'mouseover';
if(this.options.showOn == this.options.hideOn) {
this.eventToggle = this.toggle.bindAsEventListener(this);
this.element.observe(this.options.showOn, this.eventToggle);
}
var hideOptions = {
'element': this.eventToggle ? [] : [this.element],
'target': this.eventToggle ? [] : [this.target],
'tip': this.eventToggle ? [] : [this.wrapper],
'closeButton': [],
'none': []
};
var el = this.options.hideOn.element;
this.hideElement = el || (!this.options.hideOn ? 'none' : 'element');
this.hideTargets = hideOptions[this.hideElement];
if (!this.hideTargets && el && Object.isString(el)) this.hideTargets = this.tip.select(el);
var realEvent = {'mouseenter': 'mouseover', 'mouseleave': 'mouseout'};
$w('show hide').each(function(e) {
var E = e.capitalize();
var event = (this.options[e + 'On'].event || this.options[e + 'On']);
this[e + 'Action'] = event;
if (['mouseenter', 'mouseleave', 'mouseover', 'mouseout'].include(event)) {
this[e + 'Action'] = (Tips.useEvent[event] || event);
this['event' + E] = Prototip.capture(this['event' + E]);
}
}.bind(this));
if (!this.eventToggle) this.element.observe(this.options.showOn, this.eventShow);
if (this.hideTargets) this.hideTargets.invoke('observe', this.hideAction, this.eventHide);
// add postion observer to moving showOn click tips
if (!this.options.fixed && this.options.showOn == 'click') {
this.eventPosition = this.position.bindAsEventListener(this);
this.element.observe('mousemove', this.eventPosition);
}
// close button
this.buttonEvent = this.hide.wrap(function(proceed, event) {
event.stop();
proceed(event);
}).bindAsEventListener(this);
if (this.closeButton) this.closeButton.observe('click', this.buttonEvent);
// delay timeout
if (this.options.showOn != 'click' && (this.hideElement != 'element')) {
this.eventCheckDelay = Prototip.capture(function() {
this.clearTimer('show');
}).bindAsEventListener(this);
this.element.observe(Tips.useEvent['mouseout'], this.eventCheckDelay);
}
// activity (hideAfter, raise)
var elements = [this.element, this.wrapper];
this.activityEnter = Prototip.capture(function() {
Tips.raise(this.wrapper);
this.cancelHideAfter();
}).bindAsEventListener(this);
this.activityLeave = Prototip.capture(this.hideAfter).bindAsEventListener(this);
elements.invoke('observe', Tips.useEvent['mouseover'], this.activityEnter);
elements.invoke('observe', Tips.useEvent['mouseout'], this.activityLeave);
},
deactivate: function() {
if(this.options.showOn == this.options.hideOn)
this.element.stopObserving(this.options.showOn, this.eventToggle);
else {
this.element.stopObserving(this.options.showOn, this.eventShow);
if (this.hideTargets) this.hideTargets.invoke('stopObserving');
}
if (this.eventPosition) this.element.stopObserving('mousemove', this.eventPosition);
if (this.closeButton) this.closeButton.stopObserving();
if (this.eventCheckDelay) this.element.stopObserving('mouseout', this.eventCheckDelay);
this.wrapper.stopObserving();
this.element.stopObserving(Tips.useEvent['mouseover'], this.activityEnter);
this.element.stopObserving(Tips.useEvent['mouseout'], this.activityLeave);
},
showDelayed: function(event) {
if (!this.tooltip) this.build();
this.position(event); // follow mouse
if (this.wrapper.visible()) return;
this.clearTimer('show');
this.showTimer = this.show.bind(this).delay(this.options.delay);
},
clearTimer: function(timer) {
if (this[timer + 'Timer']) clearTimeout(this[timer + 'Timer']);
},
show: function() {
if (this.wrapper.visible() && this.options.effect != 'appear') return;
if (Tips.fixIE) this.iframeShim.show();
Tips.addVisibile(this.wrapper);
this.wrapper.show();
if (!this.options.effect) this.tooltip.show();
else {
if (this.activeEffect) Effect.Queues.get(this.queue.scope).remove(this.activeEffect);
this.activeEffect = Effect[Effect.PAIRS[this.options.effect][0]](this.effectWrapper,
{ duration: this.options.duration, queue: this.queue});
}
},
hideAfter: function(event) {
if (!this.options.hideAfter) return;
this.cancelHideAfter();
this.hideAfterTimer = this.hide.bind(this).delay(this.options.hideAfter);
},
cancelHideAfter: function() {
if (this.options.hideAfter) this.clearTimer('hideAfter');
},
hide: function() {
this.clearTimer('show');
if(!this.wrapper.visible()) return;
if (!this.options.effect) {
if (Tips.fixIE) this.iframeShim.hide();
this.tooltip.hide();
this.wrapper.hide();
Tips.removeVisible(this.wrapper);
}
else {
if (this.activeEffect) Effect.Queues.get(this.queue.scope).remove(this.activeEffect);
this.activeEffect = Effect[Effect.PAIRS[this.options.effect][1]](this.effectWrapper,
{ duration: this.options.duration, queue: this.queue, afterFinish: function() {
if (Tips.fixIE) this.iframeShim.hide();
this.wrapper.hide();
Tips.removeVisible(this.wrapper);
}.bind(this)});
}
},
toggle: function(event) {
if (this.wrapper && this.wrapper.visible()) this.hide(event);
else this.showDelayed(event);
},
position: function(event) {
Tips.raise(this.wrapper);
var offset = {left: this.options.offset.x, top: this.options.offset.y};
var targetPosition = Position.cumulativeOffset(this.target);
var tipd = this.wrapper.getDimensions();
var pos = { left: (this.options.fixed) ? targetPosition[0] : Event.pointerX(event),
top: (this.options.fixed) ? targetPosition[1] : Event.pointerY(event) };
// add offsets
pos.left += offset.left;
pos.top += offset.top;
if (this.options.hook) {
var dims = {target: this.target.getDimensions(), tip: tipd}
var hooks = {target: Position.cumulativeOffset(this.target), tip: Position.cumulativeOffset(this.target)}
for (var z in hooks) {
switch (this.options.hook[z]) {
case 'topRight':
hooks[z][0] += dims[z].width;
break;
case 'topMiddle':
hooks[z][0] += (dims[z].width / 2);
break;
case 'rightMiddle':
hooks[z][0] += dims[z].width;
hooks[z][1] += (dims[z].height / 2);
break;
case 'bottomLeft':
hooks[z][1] += dims[z].height;
break;
case 'bottomRight':
hooks[z][0] += dims[z].width;
hooks[z][1] += dims[z].height;
break;
case 'bottomMiddle':
hooks[z][0] += (dims[z].width / 2);
hooks[z][1] += dims[z].height;
break;
case 'leftMiddle':
hooks[z][1] += (dims[z].height / 2);
break;
}
}
// move based on hooks
pos.left += -1*(hooks.tip[0] - hooks.target[0]);
pos.top += -1*(hooks.tip[1] - hooks.target[1]);
}
// move tooltip when there is a different target
if (!this.options.fixed && this.element !== this.target) {
var elementPosition = Position.cumulativeOffset(this.element);
pos.left += -1*(elementPosition[0] - targetPosition[0]);
pos.top += -1*(elementPosition[1] - targetPosition[1]);
}
if (!this.options.fixed && this.options.viewport) {
var scroll = document.viewport.getScrollOffsets();
var viewport = document.viewport.getDimensions();
var pair = {left: 'width', top: 'height'};
for(var z in pair) {
if ((pos[z] + tipd[pair[z]] - scroll[z]) > viewport[pair[z]])
pos[z] = pos[z] - tipd[pair[z]] - 2*offset[z];
}
}
var setPos = { left: pos.left + 'px', top: pos.top + 'px' };
this.wrapper.setStyle(setPos);
if (Tips.fixIE) this.iframeShim.setStyle(setPos);
}
});
Prototip.start();
/**
* Main Sypad class. Handles basic functionality
* such as plugin registration and event handling.
* Also provides handly functions such as how to
* get a full URL from a Cake URL, and opening
* popup windows.
*
* @author Mariano Iglesias
*/
var Sypad = Class.create({
/**
* Base URL to CakePHP application.
*
* @var string
* @access private
*/
baseUrl: '',
/**
* List of registered plugins.
*
* @var array
* @access private
*/
plugins: [],
/**
* AJAX responders.
*
* @var object
* @access private
*/
__responders: false,
/**
* Initializes this JS component
*
* @access public
*/
initialize: function()
{
var self = this;
this.__setBaseUrl();
this.registerAjaxResponders();
// Register the DOM update event triggerer
document.observe('dom:loaded', function(event) {
self.domUpdated();
});
},
/**
* Called when the DOM object has updated (after window
* load or after an AJAX complete)
*
* @access public
*/
domUpdated: function()
{
// Dispatch DOM update event on plugins
this.plugins.each(function(plugin)
{
if (typeof plugin == 'object' && typeof plugin.domUpdated == 'function')
{
plugin.domUpdated();
}
});
},
/**
* Open a new embedded window.
*
* @param string id	ID for the window
* @param object properties	Set of properties to override
* @return object	Window object
* @access public
*/
openWindow: function(id, properties)
{
var settings = {
id: id,
draggable: true,
resizable: false,
minimize: false,
maximize: false,
close: 'destroy',
theme: 'leopard',
shadowTheme: 'drop_shadow',
shadow: true,
full_url: false
};
// Set settings
settings = Object.extend(settings, properties);
if (settings.url != null && settings.full_url != true)
{
settings.url += (settings.url.indexOf('?') != -1 ? '&' : '?') + 'popup';
settings.url = this.url(settings.url);
}
// Create window
var win = new UI.URLWindow(settings).center().show(true);
return win;
},
/**
* Closes an embedded window.
*
* @param string id	ID for the window
* @access public
*/
closeWindow: function(id)
{
UI.defaultWM.getWindow(id).close();
},
/**
* Registers a plugin to the list of plugins.
*
* @param object plugin Plugin to register
* @access public
*/
registerPlugin: function(plugin)
{
this.plugins.push(plugin);
},
/**
* Unregisters a plugin from the list of plugins.
*
* @param object plugin Plugin to unregister
* @access public
*/
unregisterPlugin: function(plugin)
{
this.plugins = this.plugins.without(plugin);
},
/**
* Get the full URL to a CakePHP action
*
* @param string url CakePHP URL
* @return string Full URL
* @access public
*/
url: function(url)
{
if (url.match(/^\//))
{
url = url.substr(1);
}
return this.baseUrl + url;
},
/**
* Show loading message, used when by AJAX responder callback.
*
* @access public
*/
startLoading: function()
{
if ($('divLoadingSpinner'))
{
var settings = {
id: "_loading_window",
draggable: false,
resizable: false,
minimize: false,
maximize: false,
width: 300,
height: 50,
close: 'destroy',
theme: 'leopard',
shadowTheme: 'drop_shadow',
shadow: true
};
// Create window
this.__loadingWindow = new UI.Window(settings).center().setContent('<div style="padding: 7px;">' + $('divLoadingSpinner').innerHTML + '</div>').show(true);
}
},
/**
* Hide loading message, used when by AJAX responder callback.
*
* @access public
*/
endLoading: function()
{
if (this.__loadingWindow != null)
{
this.__loadingWindow.close();
this.__loadingWindow = null;
Element.hide('divLoadingSpinner');
}
},
/**
* Register AJAX responders for showing loading messages.
*
* @access public
*/
registerAjaxResponders: function()
{
if (!this.__responders)
{
var self = this;
this.__responders = {
onCreate: function() {
if (Ajax.activeRequestCount > 0)
{
self.startLoading();
}
},
onComplete: function() {
if (Ajax.activeRequestCount == 0)
{
self.endLoading();
self.domUpdated();
}
}
};
}
Ajax.Responders.register(this.__responders);
},
/**
* Unregister AJAX responders for showing loading messages.
*
* @access public
*/
unregisterAjaxResponders: function()
{
if (this.__responders)
{
Ajax.Responders.unregister(this.__responders);
}
},
/**
* Set current CakePHP base URL to this application.
*
* @access private
*/
__setBaseUrl: function()
{
var self = this;
var javascriptFile = 'prototype';
// In case we are being packaged, use the package file as file locator
if (typeof(__PackagedJavaScriptFile) != 'undefined')
{
javascriptFile = __PackagedJavaScriptFile;
}
if (!javascriptFile.match(/.*\.js$/))
{
javascriptFile += '.js';
}
if (javascriptFile.charAt(0) == '/')
{
javascriptFile = javascriptFile.substr(1);
}
var regex = new RegExp('\\/([^\\/]*)\\/' + javascriptFile.replace(/\./g, '\\.') + '(\\?.*)?$');
$A(document.getElementsByTagName("script")).findAll( function(s) {
return (s.src && s.src.match(regex))
}).each( function(s) {
var path = s.src.replace(regex,'');
self.baseUrl = path + '/';
});
}
});
// Instantiate main sypad class
Sypad = new Sypad();
/**
* Sypad base plugin class. Provides a base for
* all Sypad plugins. It must be extended.
*
* @author Mariano Iglesias
*/
var SypadPlugin = Class.create({
/**
* Initializes this plugin. If you override this method,
* make sure to call _register() to register your plugin.
* If you are looking for an initialization method, better
* extend startup instead.
*
* @access public
*/
initialize: function()
{
// Startup plugin
this.startup();
// Register this plugin
this._register();
},
/**
* Starts the plugin.
*
* @access public
*/
startup: function()
{
},
/**
* Called when the DOM object has updated (after window
* load or after an AJAX complete)
*
* @access public
*/
domUpdated: function()
{
},
/**
* Get the full URL to a CakePHP action
*
* @param string url CakePHP URL
* @return string Full URL
* @access public
*/
url: function(url)
{
return Sypad.url(url);
},
/**
* Registers this plugin.
*
* @access protected
*/
_register: function()
{
Sypad.registerPlugin(this);
}
});
/**
* Clock Plugin to insert clock on current DOM model.
*
* @author Mariano Iglesias
*/
var ClockPlugin = Class.create(SypadPlugin, {
/**
* Holds all instances
*
* @var array
*/
instances: [],
/**
* Indicates if the clocks have started
*
* @var boolean
*/
started: false,
/**
* Timeout handle
*
* @var object
*/
handle: null,
/**
* Called when the DOM object has updated (after window
* load or after an AJAX complete)
*
* @access public
*/
domUpdated: function()
{
var self = this;
$$('span.clock[title]').each(function (span) {
// Make sure we have not visited this element before
if (!span.__ClockPluginVisited)
{
// Mark as visited
span.__ClockPluginVisited = true;
// Add to set of instances
instance = {
element: span,
offset: span.getAttribute('title')
};
// Empty attribute title
span.title = '';
// Calculate offset in regards to client timezone
instance.offset = parseInt(instance.offset);
clientOffset = -(new Date().getTimezoneOffset());
if (isNaN(instance.offset))
{
instance.offset = clientOffset;
}
if (instance.offset != clientOffset)
{
instance.offset = instance.offset - clientOffset;
}
else
{
instance.offset = 0;
}
self.instances.push(instance);
// Start instances
self.start();
}
});
},
/**
* Start all clocks.
*
* @access public
*/
start: function()
{
this.stop();
this.started = true;
this.process();
},
/**
* Stop all clocks.
*
* @access public
*/
stop: function()
{
this.started = false;
if (this.handle != null)
{
clearTimeout(this.handle);
this.handle = null;
}
},
/**
* Goes through all instances updating the clocks.
*
* @access public
*/
process: function()
{
if (!this.started)
{
return false;
}
this.instances.each(function(instance) {
var date = new Date();
if (instance.offset != 0)
{
date.setHours(date.getHours() + (instance.offset / 60));
}
var time = '';
time += (date.getHours() < 10 ? '0' : '') + date.getHours();
time += ':';
time += (date.getMinutes() < 10 ? '0' : '') + date.getMinutes();
time += ':';
time += (date.getSeconds() < 10 ? '0' : '') + date.getSeconds();
instance.element.innerHTML = time;
});
// Set timeout
this.handle = setTimeout(function() {
clockPlugin.process();
}, 1000);
}
});
// Instantiate plugin
clockPlugin = new ClockPlugin();
var Attachment = Class.create({
initialize: function()
{
this.id = null;
this.file = null;
this.path = null;
this.size = null;
this.include = false;
},
setId: function(id)
{
this.id = id;
},
getId: function()
{
return this.id;
},
setFile: function(file)
{
this.file = file;
},
getFile: function()
{
return this.file;
},
setPath: function(path)
{
this.path = path;
},
getPath: function()
{
return this.path;
},
setSize: function(size)
{
this.size = size;
},
getSize: function()
{
return this.size;
},
setInclude: function(include)
{
this.include = include;
},
isInclude: function()
{
return this.include;
}
});
var Attachments = Class.create({
initialize: function(properties)
{
this.attachments = [];
var settings = {
id: 'wAttach',
div: 'attachments',
form: 'formAttachFile',
field: 'data[Model][field]',
url: Sypad.url( '/uploads/upload' ),
texts: {
title: 'Add Files',
file: 'File',
size: 'Size',
options: 'Options',
remove: 'Remove',
restore: 'Restore'
}
};
if (properties && properties.texts)
{
properties.texts = Object.extend(settings.texts, properties.texts);
}
// Set settings
this.settings = Object.extend(settings, properties);
},
cancel: function()
{
Sypad.closeWindow(this.settings.id);
},
remove: function(file)
{
var self = this;
this.attachments.each(function (attachment, index) {
if (attachment.getFile() == file && attachment.isInclude())
{
self.attachments[index].setInclude(false);
}
});
this.show();
},
restore: function(file)
{
var self = this;
this.attachments.each(function (attachment, index) {
if (attachment.getFile() == file && !attachment.isInclude())
{
self.attachments[index].setInclude(true);
}
});
this.show();
},
push: function(id, file, path, size, include)
{
var oAttachment = new Attachment();
if (id != null)
{
oAttachment.setId(id);
}
oAttachment.setFile(file);
oAttachment.setPath(path);
oAttachment.setSize(size);
oAttachment.setInclude(include);
this.attachments.push(oAttachment);
},
add: function(file, path, size, submit)
{
this.cancel();
this.push(null, file, path, size, true);
this.show();
if (submit == true && $(this.settings.form))
{
$(this.settings.form).submit();
}
},
attach: function()
{
Sypad.openWindow(this.settings.id, {
full_url: true,
url: this.settings.url,
title: this.settings.texts.title,
width: 550,
height: 450
});
},
show: function()
{
var self = this;
var html = "";
if (this.attachments.length > 0)
{
html += "<br class=\"break\">\n";
html += "<table class=\"listing\">\n";
html += "<thead>\n";
html += "<tr>\n";
html += "<th>" + this.settings.texts.file + "</th>\n";
html += "<th>" + this.settings.texts.size + "</th>\n";
html += "<th>" + this.settings.texts.options + "</th>\n";
html += "</tr>\n";
html += "</thead>\n";
html += "<tbody>\n";
totalIncludedAttachments = 0;
this.attachments.each(function (attachment, index) {
html += "<tr class=\"" + (totalIncludedAttachments % 2 == 0 ? "even" : "odd") + "\">\n";
html += "<td" + (!attachment.isInclude() ? " class=\"deleted\"" : "") + ">" + attachment.getFile() + "</td>\n";
html += "<td" + (!attachment.isInclude() ? " class=\"deleted\"" : "") + ">" + attachment.getSize() + "</td>\n";
if (attachment.isInclude())
{
html += "<td><a href=\"#\" onClick=\"attachments.remove(\'" + attachment.getFile() + "\'); return false;\">" + self.settings.texts.remove + "</a></td>\n";
}
else
{
html += "<td><a href=\"#\" onClick=\"attachments.restore(\'" + attachment.getFile() + "\'); return false;\">" + self.settings.texts.restore + "</a></td>\n";
}
html += "</tr>\n";
});
html += "</tbody>\n";
html += "</table>";
}
this.attachments.each(function (attachment, index) {
var value = "";
value += "[";
if (attachment.getId() == null || attachment.getId() <= 0)
{
value += "0";
}
else
{
value += attachment.getId();
}
value += "|";
value += (attachment.isInclude() ? "Y" : "N");
value += "]";
value += attachment.getFile();
html += "<input type=\"hidden\" name=\"" + self.settings.field + "[]\" value=\"" + value + "\" />\n";
});
if ($(this.settings.div))
{
$(this.settings.div).innerHTML = html;
}
}
});
/**
* Tooltip Plugin to load tooltips on current DOM model.
*
* @author Mariano Iglesias
*/
var TooltipPlugin = Class.create(SypadPlugin, {
/**
* Called when the DOM object has updated (after window
* load or after an AJAX complete)
*
* @access public
*/
domUpdated: function()
{
$$('a.tooltip').each(function(link) {
// Make sure we have not visited this element before
if (!link.__TooltipPluginVisited)
{
// Mark as visited
link.__TooltipPluginVisited = true;
// Get the contents for the tooltip and remove it from element attributes
var content = Element.readAttribute(link, 'title');
// Removing title from DOM element to avoid showing it
link.title = "";
// If descendant elements has 'alt' attribute defined, clear it
link.descendants().each(function(el) {
if (Element.readAttribute(el, 'alt'))
{
el.alt = '';
}
});
if (content != '')
{
// Instantiate the plugin handle
link.tooltip = new Tip(link, content, {
className: 'sypadtip',
fixed: false,
hideOn: 'mouseout',
showOn: 'mousemove'
});
}
}
});
}
});
// Instantiate plugin
tooltipPlugin = new TooltipPlugin();
/**
* Popup Plugin to load popups on current DOM model.
*
* @author Mariano Iglesias
*/
var PopupPlugin = Class.create(SypadPlugin, {
/**
* Called when the DOM object has updated (after window
* load or after an AJAX complete)
*
* @access public
*/
domUpdated: function()
{
Popup.zIndex = 1000;  // z-index of first popup.
$$('a.submenu').each(function (link) {
// Make sure we have not visited this element before
if (!link.__PopupPluginVisited)
{
// Mark as visited
link.__PopupPluginVisited = true;
// Instantiate the plugin handle
new Popup(link.id + '_div', link, {
position: 'below',
trigger: 'mouseover',
effect: 'fade',
duration: 0.1
} );
}
});
}
});
// Instantiate plugin
popupPlugin = new PopupPlugin();
/**
* Popup class to handle a popup instance.
*
* @author Mariano Iglesias
*/
var Popup = Class.create({
initialize: function(popup, link)
{
var options = Object.extend({
modal: false,
effect: 'fade',
hidden: true,
closebox: 'popup_closebox',
draghandle: 'popup_draghandle'
}, arguments[2] || {});
options.position = options.position || (options.modal ? 'center' : 'auto');
options.trigger = options.trigger || (options.modal ? 'click' : 'mouseover');
options.duration = this.first_value(options.duration, Popup.duration, 0.5);
options.show_duration = this.first_value(options.show_duration, options.duration);
options.hide_duration = this.first_value(options.hide_duration, options.duration);
options.opacity = this.first_value(options.opacity, Popup.opacity, 0.5);
options.show_delay = this.first_value(options.show_delay, Popup.show_delay, 500);
options.hide_delay = this.first_value(options.hide_delay, Popup.hide_delay, 200);
options.cursor_margin = this.first_value(options.cursor_margin, Popup.cursor_margin, 5);
this.options = options;
if (link)
{
this.link = $(link);
}
this.popup = $(popup);
this.popup.popup = this;  // Make the popup object a property of the DOM popup element.
if (options.hidden)
{
this.popup.hide();
}
if (options.closebox)
{
this.closeboxes = document.getElementsByClassName(options.closebox, this.popup);
if (this.popup.hasClassName(options.closebox))
{
this.closeboxes[this.closeboxes.length] = this.popup;
}
}
else
{
this.closeboxes = [];
}
if (options.draghandle)
{
var draghandles = document.getElementsByClassName(options.draghandle, this.popup);
for (i = 0; i < draghandles.length; i++)
{
new Draggable(this.popup, { handle: draghandles[i] });
}
if (this.popup.hasClassName(options.draghandle))
{
new Draggable(this.popup, { handle: this.popup });
}
}
this.register_events();
},
register_events: function()
{
var trigger_function;
if (this.is_auto_open())
{
trigger_function = this.start_show_timer;
if (this.link)
{
Event.observe(this.link, 'mouseout', this.stop_show_timer.bindAsEventListener(this));
}
}
else
{
trigger_function = this.show;
}
if (this.link)
{
Event.observe(this.link, this.options.trigger, trigger_function.bindAsEventListener(this));
}
if (!this.options.modal)
{
Event.observe(this.popup, 'click', this.bring_to_front.bindAsEventListener(this));
}
if (this.closeboxes.length > 0)
{
for (var i = 0; i < this.closeboxes.length; i++)
{
Event.observe(this.closeboxes[i], 'click', this.hide.bindAsEventListener(this));
}
}
else
{
if (this.link)
{
Event.observe(this.link, 'mouseout', this.start_hide_timer.bindAsEventListener(this));
}
Event.observe(this.popup, 'mouseover', this.stop_hide_timer.bindAsEventListener(this));
Event.observe(this.popup, 'mouseout', this.start_hide_timer.bindAsEventListener(this));
}
},
bring_to_front: function(event)
{
if (Number(this.popup.style.zIndex) < Popup.zIndex - 1)
{
this.popup.style.zIndex = Popup.zIndex++;
}
},
start_show_timer: function(event)
{
this.stop_show_timer(event);
this.mouse_x = Event.pointerX(event);
this.mouse_y = Event.pointerY(event);
this.show_timer = setTimeout(this.show.bind(this, event), this.options.show_delay);
},
stop_show_timer: function(event)
{
if (this.show_timer)
{
clearTimeout(this.show_timer);
this.show_timer = null;
}
},
start_hide_timer: function(event)
{
this.stop_hide_timer(event);
this.hide_timer = setTimeout(this.hide.bind(this, event), this.options.hide_delay);
},
stop_hide_timer: function(event)
{
if (this.hide_timer)
{
clearTimeout(this.hide_timer);
this.hide_timer = null;
}
},
show: function(event)
{
this.stop_show_timer(event);
this.stop_hide_timer(event);
if (this.is_open)
{
return;
}
if (this.options.modal)
{
this.show_overlay();
}
var pos;
if (!event)
{
pos = this.get_popup_position();
}
else if (this.is_auto_open())
{
pos = this.get_popup_position(this.mouse_x, this.mouse_y);
}
else
{
pos = this.get_popup_position(Event.pointerX(event), Event.pointerY(event));
}
Element.setStyle(this.popup, { top: pos.y, left: pos.x, zIndex: Popup.zIndex++ });
this.is_open = true;
switch (this.options.effect)
{
case 'slide':
Effect.SlideDown(this.popup, {duration: this.options.show_duration});
break;
case 'grow':
Effect.Grow(this.popup, {duration: this.options.show_duration});
break;
case 'blind':
Effect.BlindDown(this.popup, {duration: this.options.show_duration});
break;
case 'fade':
default:
Effect.Appear(this.popup, {duration: this.options.show_duration});
break;
}
},
hide: function(event)
{
this.is_open = false;
switch (this.options.effect)
{
case 'slide':
Effect.SlideUp(this.popup, {duration: this.options.hide_duration});
break;
case 'grow':
Effect.Shrink(this.popup, {duration: this.options.hide_duration});
break;
case 'blind':
Effect.BlindUp(this.popup, {duration: this.options.hide_duration});
break;
case 'fade':
default:
Effect.Fade(this.popup, {duration: this.options.hide_duration});
break;
}
if (this.options.modal)
{
this.hide_overlay();
}
},
first_value: function()
{
for (var i = 0; i < arguments.length; i++)
{
if (arguments[i] !== undefined)
{
return arguments[i];
}
}
return undefined;
},
is_auto_open: function()
{
return this.options.trigger == 'mouseover';
},
show_overlay: function()
{
if (!Popup.overlay)
{
var overlay = document.createElement('div');
overlay.setAttribute('id','submenu_overlay');
overlay.style.display = 'none';
document.body.appendChild(overlay);
Popup.overlay = overlay;
Popup.overlay_levels = [];
}
Popup.overlay.style.height = this.get_page_dimensions().height + 'px';
var z = Popup.zIndex++;
Popup.overlay.style.zIndex = z;
Popup.overlay_levels.push(z);
if ( Popup.overlay_levels.length == 1)
{
new Effect.Appear(Popup.overlay, {
duration: this.options.show_duration,
to: this.options.opacity,
queue: {position: 'end', scope: 'submenu_overlay'}
});
}
else
{
Popup.overlay.style.zIndex = z;
}
},
hide_overlay: function()
{
Popup.overlay_levels.pop();
var z = Popup.overlay_levels.pop();
if (z)
{
Popup.overlay_levels.push(z);
Popup.overlay.style.zIndex = z;
}
else
{
new Effect.Fade(Popup.overlay, {
duration: this.options.hide_duration,
queue: {position: 'end', scope: 'submenu_overlay'}
});
}
},
get_popup_position: function(mouse_x, mouse_y)
{
var pos;
switch (this.options.position)
{
case 'auto':
pos = this.get_auto_position(mouse_x, mouse_y);
break;
case 'center':
pos = this.get_center_position();
break;
case 'below':
pos = this.get_below_position();
break;
default:
if (mo = this.options.position.match(/^\s*([^\s,]+)\s*,\s*([^\s,]+)\s*$/))
{
pos = {x: mo[1], y: mo[2]};
pos.x = Number(pos.x) || pos.x;
pos.y = Number(pos.y) || pos.y;
}
else
{
pos = {x: 0, y: 0};
}
break;
}
if (typeof pos.x == 'number')
{
pos.x += 'px';
}
if (typeof pos.y == 'number')
{
pos.y += 'px';
}
return pos;
},
get_below_position: function()
{
var pos = Position.cumulativeOffset(this.link);
return {x: pos[0], y: pos[1] + Element.getHeight(this.link)};
},
get_center_position: function()
{
dim = Element.getDimensions(this.popup);
var popup_width = dim.width;
var popup_height = dim.height;
dim = this.get_viewport_dimensions();
var viewport_width = dim.width;
var viewport_height = dim.height;
var x;
if (popup_width >= viewport_width)
{
x = 0;
}
else
{
x = (viewport_width - popup_width)/2;
}
var y;
if (popup_height >= viewport_height)
{
y = 0;
}
else
{
y = (viewport_height - popup_height)/2;
}
return {x: x, y: y};
},
get_auto_position: function(mouse_x, mouse_y)
{
dim = Element.getDimensions(this.popup);
var popup_width = dim.width;
var popup_height = dim.height;
dim = this.get_viewport_dimensions();
var viewport_width = dim.width;
var viewport_height = dim.height;
var available_right = viewport_width - (mouse_x + this.options.cursor_margin);
var available_left = mouse_x - this.options.cursor_margin;
var available_top = mouse_y - this.options.cursor_margin;
var available_bottom = viewport_height - (mouse_x + this.options.cursor_margin);
var offset = this.options.cursor_margin;
var x = mouse_x;
var y = mouse_y;
if (popup_width >= viewport_width)
{
x = 0;
}
else if (popup_width <= available_right)
{
x += offset;
}
else if (popup_width <= available_left)
{
x -= popup_width + offset;
}
else if (available_right >= available_left)
{
x = viewport_width - popup_width;
}
else
{
x = 0;
}
if (popup_height >= viewport_height)
{
y = 0;
}
else if (popup_height <= available_bottom)
{
y += offset;
}
else if (popup_height <= available_top)
{
y -= popup_height + offset;
}
else if (available_bottom >= available_top)
{
y = viewport_height - popup_height;
}
else
{
y = 0;
}
return {x: x, y: y};
},
get_viewport_dimensions: function()
{
var dim = this.getPageSize();
return {width: dim[2], height: dim[3]};
},
get_page_dimensions: function()
{
var dim = this.getPageSize();
return {width: dim[0], height: dim[1]};
},
getPageSize: function()
{
var xScroll, yScroll;
if (window.innerHeight && window.scrollMaxY)
{
xScroll = document.body.scrollWidth;
yScroll = window.innerHeight + window.scrollMaxY;
}
else if (document.body.scrollHeight > document.body.offsetHeight)
{
xScroll = document.body.scrollWidth;
yScroll = document.body.scrollHeight;
}
else
{
xScroll = document.body.offsetWidth;
yScroll = document.body.offsetHeight;
}
var windowWidth, windowHeight;
if (self.innerHeight)
{
windowWidth = self.innerWidth;
windowHeight = self.innerHeight;
}
else if (document.documentElement && document.documentElement.clientHeight)
{
windowWidth = document.documentElement.clientWidth;
windowHeight = document.documentElement.clientHeight;
}
else if (document.body)
{
windowWidth = document.body.clientWidth;
windowHeight = document.body.clientHeight;
}
if(yScroll < windowHeight)
{
pageHeight = windowHeight;
}
else
{
pageHeight = yScroll;
}
if(xScroll < windowWidth)
{
pageWidth = windowWidth;
}
else
{
pageWidth = xScroll;
}
arrayPageSize = new Array(pageWidth,pageHeight,windowWidth,windowHeight);
return arrayPageSize;
}
});
/**
* Accordion Plugin to load accordions on current DOM model.
*
* @author Mariano Iglesias
*/
var AccordionPlugin = Class.create(SypadPlugin, {
/**
* Called when the DOM object has updated (after window
* load or after an AJAX complete)
*
* @access public
*/
domUpdated: function()
{
$$('a.accordion').each(function(link) {
// Make sure we have not visited this element before
if (!link.__AccordionPluginVisited)
{
// Mark as visited
link.__AccordionPluginVisited = true;
// Get the DIV id which will be hidden / shown when clicking link
var divId = Element.readAttribute(link, 'rel');
if (divId != '' && $(divId))
{
// Observe clicks for this link
link.observe('click', function(event) {
var div = $(divId);
if (!div.visible())
{
Effect.SlideDown(div, { duration: 0.4 });
}
else
{
Effect.SlideUp(div, { duration: 0.4 });
}
});
// Abort normal clicking
link.onclick = function() {
return false;
}
}
}
});
}
});
// Instantiate plugin
accordionPlugin = new AccordionPlugin();