/* Prototype JavaScript framework, version 1.5.1_rc2 * (c) 2005-2007 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.5.1_rc2', 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 }, BrowserFeatures: { XPath: !!document.evaluate, ElementExtensions: !!window.HTMLElement, SpecificElementExtensions: (document.createElement('div').__proto__ !== document.createElement('form').__proto__) }, ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', emptyFunction: function() {}, K: function(x) { return x } } var Class = { create: function() { return function() { this.initialize.apply(this, arguments); } } } var Abstract = new Object(); 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 === undefined) return 'undefined'; if (object === null) return 'null'; return object.inspect ? object.inspect() : object.toString(); } 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.ownerDocument === document) return; var results = []; for (var property in object) { var value = Object.toJSON(object[property]); if (value !== undefined) results.push(property.toJSON() + ':' + value); } return '{' + results.join(',') + '}'; }, 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); } }); Function.prototype.bind = function() { var __method = this, args = $A(arguments), object = args.shift(); return function() { return __method.apply(object, args.concat($A(arguments))); } } Function.prototype.bindAsEventListener = function(object) { var __method = this, args = $A(arguments), object = args.shift(); return function(event) { return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments))); } } 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'; } }); Date.prototype.toJSON = function() { return '"' + this.getFullYear() + '-' + (this.getMonth() + 1).toPaddedString(2) + '-' + this.getDate().toPaddedString(2) + 'T' + this.getHours().toPaddedString(2) + ':' + this.getMinutes().toPaddedString(2) + ':' + this.getSeconds().toPaddedString(2) + '"'; }; 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; } } /*--------------------------------------------------------------------------*/ var PeriodicalExecuter = Class.create(); PeriodicalExecuter.prototype = { 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); }, stop: function() { if (!this.timer) return; clearInterval(this.timer); this.timer = null; }, onTimerEvent: function() { if (!this.currentlyExecuting) { try { this.currentlyExecuting = true; this.callback(this); } 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 = count === undefined ? 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 this; }, truncate: function(length, truncation) { length = length || 30; truncation = truncation === undefined ? '...' : truncation; return this.length > length ? this.slice(0, length - truncation.length) + truncation : 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 = document.createElement('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 name = decodeURIComponent(pair[0]); var value = pair[1] ? decodeURIComponent(pair[1]) : undefined; if (hash[name] !== undefined) { if (hash[name].constructor != Array) hash[name] = [hash[name]]; if (value) hash[name].push(value); } else hash[name] = 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) { var result = ''; for (var i = 0; i < count; i++) result += this; return result; }, 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); }, evalJSON: function(sanitize) { try { if (!sanitize || (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(this))) return eval('(' + this + ')'); } catch (e) {} throw new SyntaxError('Badly formated JSON string: ' + this.inspect()); }, include: function(pattern) { return this.indexOf(pattern) > -1; }, startsWith: function(pattern) { return this.indexOf(pattern) == 0; }, endsWith: function(pattern) { return this.lastIndexOf(pattern) == (this.length - pattern.length); }, empty: function() { return this == ''; }, blank: function() { return /^\s*$/.test(this); } }); String.prototype.gsub.prepareReplacement = function(replacement) { if (typeof replacement == 'function') 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(); Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; Template.prototype = { initialize: function(template, pattern) { this.template = template.toString(); this.pattern = pattern || Template.Pattern; }, evaluate: function(object) { return this.template.gsub(this.pattern, function(match) { var before = match[1]; if (before == '\\') return match[2]; return before + String.interpret(object[match[3]]); }); } } var $break = new Object(); var $continue = new Object(); var Enumerable = { each: function(iterator) { var index = 0; try { this._each(function(value) { iterator(value, index++); }); } catch (e) { if (e != $break) throw e; } return this; }, eachSlice: function(number, iterator) { var index = -number, slices = [], array = this.toArray(); while ((index += number) < array.length) slices.push(array.slice(index, index+number)); return slices.map(iterator); }, all: function(iterator) { var result = true; this.each(function(value, index) { result = result && !!(iterator || Prototype.K)(value, index); if (!result) throw $break; }); return result; }, any: function(iterator) { var result = false; this.each(function(value, index) { if (result = !!(iterator || Prototype.K)(value, index)) throw $break; }); return result; }, collect: function(iterator) { var results = []; this.each(function(value, index) { results.push((iterator || Prototype.K)(value, index)); }); return results; }, detect: function(iterator) { var result; this.each(function(value, index) { if (iterator(value, index)) { result = value; throw $break; } }); return result; }, findAll: function(iterator) { var results = []; this.each(function(value, index) { if (iterator(value, index)) results.push(value); }); return results; }, grep: function(pattern, iterator) { var results = []; this.each(function(value, index) { var stringValue = value.toString(); if (stringValue.match(pattern)) results.push((iterator || Prototype.K)(value, index)); }) return results; }, include: function(object) { var found = false; this.each(function(value) { if (value == object) { found = true; throw $break; } }); return found; }, inGroupsOf: function(number, fillWith) { fillWith = fillWith === undefined ? null : fillWith; return this.eachSlice(number, function(slice) { while(slice.length < number) slice.push(fillWith); return slice; }); }, inject: function(memo, iterator) { 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) { var result; this.each(function(value, index) { value = (iterator || Prototype.K)(value, index); if (result == undefined || value >= result) result = value; }); return result; }, min: function(iterator) { var result; this.each(function(value, index) { value = (iterator || Prototype.K)(value, index); if (result == undefined || value < result) result = value; }); return result; }, partition: function(iterator) { var trues = [], falses = []; this.each(function(value, index) { ((iterator || Prototype.K)(value, index) ? trues : falses).push(value); }); return [trues, falses]; }, pluck: function(property) { var results = []; this.each(function(value, index) { results.push(value[property]); }); return results; }, reject: function(iterator) { var results = []; this.each(function(value, index) { if (!iterator(value, index)) results.push(value); }); return results; }, sortBy: function(iterator) { 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 (typeof args.last() == 'function') 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 '#'; } } Object.extend(Enumerable, { map: Enumerable.collect, find: Enumerable.detect, select: Enumerable.findAll, member: Enumerable.include, entries: Enumerable.toArray }); var $A = Array.from = function(iterable) { if (!iterable) return []; if (iterable.toArray) { return iterable.toArray(); } else { var results = []; for (var i = 0, length = iterable.length; i < length; i++) results.push(iterable[i]); return results; } } if (Prototype.Browser.WebKit) { $A = Array.from = function(iterable) { if (!iterable) return []; if (!(typeof iterable == 'function' && iterable == '[object NodeList]') && iterable.toArray) { return iterable.toArray(); } else { var results = []; for (var i = 0, length = iterable.length; i < length; i++) results.push(iterable[i]); return results; } } } 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(value && value.constructor == Array ? value.flatten() : [value]); }); }, without: function() { var values = $A(arguments); return this.select(function(value) { return !values.include(value); }); }, indexOf: function(object) { for (var i = 0, length = this.length; i < length; i++) if (this[i] == object) return i; return -1; }, 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; }); }, 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 (value !== undefined) results.push(value); }); return '[' + results.join(',') + ']'; } }); Array.prototype.toArray = Array.prototype.clone; function $w(string) { 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 (arguments[i].constructor == Array) { for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) array.push(arguments[i][j]); } else { array.push(arguments[i]); } } return array; } } var Hash = function(object) { if (object instanceof Hash) this.merge(object); else Object.extend(this, object || {}); }; Object.extend(Hash, { toQueryString: function(obj) { var parts = []; parts.add = arguments.callee.addPair; this.prototype._each.call(obj, function(pair) { if (!pair.key) return; var value = pair.value; if (value && typeof value == 'object') { if (value.constructor == Array) value.each(function(value) { parts.add(pair.key, value); }); return; } parts.add(pair.key, value); }); return parts.join('&'); }, toJSON: function(object) { var results = []; this.prototype._each.call(object, function(pair) { var value = Object.toJSON(pair.value); if (value !== undefined) results.push(pair.key.toJSON() + ':' + value); }); return '{' + results.join(',') + '}'; } }); Hash.toQueryString.addPair = function(key, value, prefix) { if (value == null) return; key = encodeURIComponent(key); this.push(key + '=' + (value == null ? '' : encodeURIComponent(value))); } Object.extend(Hash.prototype, Enumerable); Object.extend(Hash.prototype, { _each: function(iterator) { for (var key in this) { var value = this[key]; if (value && value == Hash.prototype[key]) continue; var pair = [key, value]; pair.key = key; pair.value = value; iterator(pair); } }, keys: function() { return this.pluck('key'); }, values: function() { return this.pluck('value'); }, merge: function(hash) { return $H(hash).inject(this, function(mergedHash, pair) { mergedHash[pair.key] = pair.value; return mergedHash; }); }, remove: function() { var result; for(var i = 0, length = arguments.length; i < length; i++) { var value = this[arguments[i]]; if (value !== undefined){ if (result === undefined) result = value; else { if (result.constructor != Array) result = [result]; result.push(value) } } delete this[arguments[i]]; } return result; }, toQueryString: function() { return Hash.toQueryString(this); }, inspect: function() { return '#'; }, toJSON: function() { return Hash.toJSON(this); } }); function $H(object) { if (object instanceof Hash) return object; return new Hash(object); }; // Safari iterates over shadowed properties if (function() { var i = 0, Test = function(value) { this.key = value }; Test.prototype.key = 'foo'; for (var property in new Test('bar')) i++; return i > 1; }()) Hash.prototype._each = function(iterator) { var cache = []; for (var key in this) { var value = this[key]; if ((value && value == Hash.prototype[key]) || cache.include(key)) continue; cache.push(key); var pair = [key, value]; pair.key = key; pair.value = value; iterator(pair); } }; ObjectRange = Class.create(); Object.extend(ObjectRange.prototype, Enumerable); Object.extend(ObjectRange.prototype, { 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 (typeof responder[callback] == 'function') { 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 = function() {}; Ajax.Base.prototype = { setOptions: function(options) { this.options = { method: 'post', asynchronous: true, contentType: 'application/x-www-form-urlencoded', encoding: 'UTF-8', parameters: '' } Object.extend(this.options, options || {}); this.options.method = this.options.method.toLowerCase(); if (typeof this.options.parameters == 'string') this.options.parameters = this.options.parameters.toQueryParams(); } } Ajax.Request = Class.create(); Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; Ajax.Request.prototype = Object.extend(new Ajax.Base(), { _complete: false, initialize: function(url, options) { this.transport = Ajax.getTransport(); this.setOptions(options); 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 = Hash.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 { Ajax.Responders.dispatch('onCreate', this, this.transport); this.transport.open(this.method.toUpperCase(), this.url, this.options.asynchronous); if (this.options.asynchronous) setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); 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 (typeof extras.push == 'function') 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() { return !this.transport.status || (this.transport.status >= 200 && this.transport.status < 300); }, respondToReadyState: function(readyState) { var state = Ajax.Request.Events[readyState]; var transport = this.transport, json = this.evalJSON(); if (state == 'Complete') { try { this._complete = true; (this.options['on' + this.transport.status] || this.options['on' + (this.success() ? 'Success' : 'Failure')] || Prototype.emptyFunction)(transport, json); } catch (e) { this.dispatchException(e); } if ((this.getHeader('Content-type') || 'text/javascript').strip(). match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) this.evalResponse(); } try { (this.options['on' + state] || Prototype.emptyFunction)(transport, json); Ajax.Responders.dispatch('on' + state, this, transport, json); } catch (e) { this.dispatchException(e); } if (state == 'Complete') { // avoid memory leak in MSIE: clean up this.transport.onreadystatechange = Prototype.emptyFunction; } }, getHeader: function(name) { try { return this.transport.getResponseHeader(name); } catch (e) { return null } }, evalJSON: function() { try { var json = this.getHeader('X-JSON'); return json ? eval('(' + json + ')') : null; } catch (e) { return null } }, evalResponse: function() { try { return eval(this.transport.responseText); } catch (e) { this.dispatchException(e); } }, dispatchException: function(exception) { (this.options.onException || Prototype.emptyFunction)(this, exception); Ajax.Responders.dispatch('onException', this, exception); } }); Ajax.Updater = Class.create(); Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { initialize: function(container, url, options) { this.container = { success: (container.success || container), failure: (container.failure || (container.success ? null : container)) } this.transport = Ajax.getTransport(); this.setOptions(options); var onComplete = this.options.onComplete || Prototype.emptyFunction; this.options.onComplete = (function(transport, param) { this.updateContent(); onComplete(transport, param); }).bind(this); this.request(url); }, updateContent: function() { var receiver = this.container[this.success() ? 'success' : 'failure']; var response = this.transport.responseText; if (!this.options.evalScripts) response = response.stripScripts(); if (receiver = $(receiver)) { if (this.options.insertion) new this.options.insertion(receiver, response); else receiver.update(response); } if (this.success()) { if (this.onComplete) setTimeout(this.onComplete.bind(this), 10); } } }); Ajax.PeriodicalUpdater = Class.create(); Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { initialize: function(container, url, options) { this.setOptions(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(request) { if (this.options.decay) { this.decay = (request.responseText == this.lastText ? this.decay * this.options.decay : 1); this.lastText = request.responseText; } this.timer = setTimeout(this.onTimerEvent.bind(this), this.decay * this.frequency * 1000); }, 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 (typeof element == 'string') 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(query.snapshotItem(i)); return results; }; document.getElementsByClassName = function(className, parentElement) { var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; return document._getElementsByXPath(q, parentElement); } } else document.getElementsByClassName = function(className, parentElement) { var children = ($(parentElement) || document.body).getElementsByTagName('*'); var elements = [], child; for (var i = 0, length = children.length; i < length; i++) { child = children[i]; if (Element.hasClassName(child, className)) elements.push(Element.extend(child)); } return elements; }; /*--------------------------------------------------------------------------*/ if (!window.Element) var Element = {}; Element.extend = function(element) { var F = Prototype.BrowserFeatures; if (!element || !element.tagName || element.nodeType == 3 || element._extended || F.SpecificElementExtensions || element == window) return element; var methods = {}, tagName = element.tagName, cache = Element.extend.cache, T = Element.Methods.ByTag; // extend methods for all tags (Safari doesn't need this) if (!F.ElementExtensions) { Object.extend(methods, Element.Methods), Object.extend(methods, Element.Methods.Simulated); } // extend methods for specific tags if (T[tagName]) Object.extend(methods, T[tagName]); for (var property in methods) { var value = methods[property]; if (typeof value == 'function' && !(property in element)) element[property] = cache.findOrStore(value); } element._extended = Prototype.emptyFunction; return element; }; Element.extend.cache = { findOrStore: function(value) { return this[value] = this[value] || function() { return value.apply(null, [this].concat($A(arguments))); } } }; 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, html) { html = typeof html == 'undefined' ? '' : html.toString(); $(element).innerHTML = html.stripScripts(); setTimeout(function() {html.evalScripts()}, 10); return element; }, replace: function(element, html) { element = $(element); html = typeof html == 'undefined' ? '' : html.toString(); if (element.outerHTML) { element.outerHTML = html.stripScripts(); } else { var range = element.ownerDocument.createRange(); range.selectNodeContents(element); element.parentNode.replaceChild( range.createContextualFragment(html.stripScripts()), element); } setTimeout(function() {html.evalScripts()}, 10); return element; }, 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 $A($(element).getElementsByTagName('*')).each(Element.extend); }, 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 (typeof selector == 'string') selector = new Selector(selector); return selector.match($(element)); }, up: function(element, expression, index) { var ancestors = $(element).ancestors(); return expression ? Selector.findElement(ancestors, expression, index) : ancestors[index || 0]; }, down: function(element, expression, index) { var descendants = $(element).descendants(); return expression ? Selector.findElement(descendants, expression, index) : descendants[index || 0]; }, previous: function(element, expression, index) { var previousSiblings = $(element).previousSiblings(); return expression ? Selector.findElement(previousSiblings, expression, index) : previousSiblings[index || 0]; }, next: function(element, expression, index) { var nextSiblings = $(element).nextSiblings(); return expression ? Selector.findElement(nextSiblings, expression, index) : nextSiblings[index || 0]; }, getElementsBySelector: function() { var args = $A(arguments), element = $(args.shift()); return Selector.findChildElements(element, args); }, getElementsByClassName: function(element, className) { return document.getElementsByClassName(className, element); }, readAttribute: function(element, name) { element = $(element); if (Prototype.Browser.IE) { if (!element.attributes) return null; var t = Element._attributeTranslations; if (t.values[name]) return t.values[name](element, name); if (t.names[name]) name = t.names[name]; var attribute = element.attributes[name]; return attribute ? attribute.nodeValue : null; } return element.getAttribute(name); }, 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; if (elementClassName.length == 0) return false; if (elementClassName == className || elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) return true; return false; }, addClassName: function(element, className) { if (!(element = $(element))) return; Element.classNames(element).add(className); return element; }, removeClassName: function(element, className) { if (!(element = $(element))) return; Element.classNames(element).remove(className); return element; }, toggleClassName: function(element, className) { if (!(element = $(element))) return; Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); return element; }, observe: function() { Event.observe.apply(Event, arguments); return $A(arguments).first(); }, stopObserving: function() { Event.stopObserving.apply(Event, arguments); return $A(arguments).first(); }, // 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); while (element = element.parentNode) if (element == ancestor) return true; return false; }, scrollTo: function(element) { element = $(element); var pos = Position.cumulativeOffset(element); 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, camelized) { element = $(element); var elementStyle = element.style; for (var property in styles) if (property == 'opacity') element.setOpacity(styles[property]) else elementStyle[(property == 'float' || property == 'cssFloat') ? (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') : (camelized ? property : property.camelize())] = 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.style.overflow || 'auto'; if ((Element.getStyle(element, 'overflow') || 'visible') != '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; } }; Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf}); if (Prototype.Browser.Opera) { Element.Methods._getStyle = Element.Methods.getStyle; Element.Methods.getStyle = function(element, style) { switch(style) { case 'left': case 'top': case 'right': case 'bottom': if (Element._getStyle(element, 'position') == 'static') return null; default: return Element._getStyle(element, style); } }; } else if (Prototype.Browser.IE) { 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) { element = $(element); var filter = element.getStyle('filter'), style = element.style; if (value == 1 || value === '') { style.filter = filter.replace(/alpha\([^\)]*\)/gi,''); return element; } else if (value < 0.00001) value = 0; style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') + 'alpha(opacity=' + (value * 100) + ')'; return element; }; // IE is missing .innerHTML support for TABLE-related elements Element.Methods.update = function(element, html) { element = $(element); html = typeof html == 'undefined' ? '' : html.toString(); var tagName = element.tagName.toUpperCase(); if (['THEAD','TBODY','TR','TD'].include(tagName)) { var div = document.createElement('div'); switch (tagName) { case 'THEAD': case 'TBODY': div.innerHTML = '' + html.stripScripts() + '
'; depth = 2; break; case 'TR': div.innerHTML = '' + html.stripScripts() + '
'; depth = 3; break; case 'TD': div.innerHTML = '
' + html.stripScripts() + '
'; depth = 4; } $A(element.childNodes).each(function(node) { element.removeChild(node) }); depth.times(function() { div = div.firstChild }); $A(div.childNodes).each(function(node) { element.appendChild(node) }); } else { element.innerHTML = html.stripScripts(); } setTimeout(function() { html.evalScripts() }, 10); return element; } } else if (Prototype.Browser.Gecko) { Element.Methods.setOpacity = function(element, value) { element = $(element); element.style.opacity = (value == 1) ? 0.999999 : (value === '') ? '' : (value < 0.00001) ? 0 : value; return element; }; } Element._attributeTranslations = { names: { colspan: "colSpan", rowspan: "rowSpan", valign: "vAlign", datetime: "dateTime", accesskey: "accessKey", tabindex: "tabIndex", enctype: "encType", maxlength: "maxLength", readonly: "readOnly", longdesc: "longDesc" }, values: { _getAttr: function(element, attribute) { return element.getAttribute(attribute, 2); }, _flag: function(element, attribute) { return $(element).hasAttribute(attribute) ? attribute : null; }, style: function(element) { return element.style.cssText.toLowerCase(); }, title: function(element) { var node = element.getAttributeNode('title'); return node.specified ? node.nodeValue : null; } } }; (function() { Object.extend(this, { href: this._getAttr, src: this._getAttr, disabled: this._flag, checked: this._flag, readonly: this._flag, multiple: this._flag }); }).call(Element._attributeTranslations.values); Element.Methods.Simulated = { hasAttribute: function(element, attribute) { var t = Element._attributeTranslations, node; attribute = t.names[attribute] || attribute; 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.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 (arguments.length == 2) { var tagName = methods; methods = arguments[1]; } if (!tagName) Object.extend(Element.Methods, methods || {}); else { if (tagName.constructor == Array) 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; var cache = Element.extend.cache; for (var property in methods) { var value = methods[property]; if (!onlyIfAbsent || !(property in destination)) destination[property] = cache.findOrStore(value); } } 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 (typeof klass == "undefined") continue; copy(T[tag], klass.prototype); } } }; var Toggle = { display: Element.toggle }; /*--------------------------------------------------------------------------*/ Abstract.Insertion = function(adjacency) { this.adjacency = adjacency; } Abstract.Insertion.prototype = { initialize: function(element, content) { this.element = $(element); this.content = content.stripScripts(); if (this.adjacency && this.element.insertAdjacentHTML) { try { this.element.insertAdjacentHTML(this.adjacency, this.content); } catch (e) { var tagName = this.element.tagName.toUpperCase(); if (['TBODY', 'TR'].include(tagName)) { this.insertContent(this.contentFromAnonymousTable()); } else { throw e; } } } else { this.range = this.element.ownerDocument.createRange(); if (this.initializeRange) this.initializeRange(); this.insertContent([this.range.createContextualFragment(this.content)]); } setTimeout(function() {content.evalScripts()}, 10); }, contentFromAnonymousTable: function() { var div = document.createElement('div'); div.innerHTML = '' + this.content + '
'; return $A(div.childNodes[0].childNodes[0].childNodes); } } var Insertion = new Object(); Insertion.Before = Class.create(); Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { initializeRange: function() { this.range.setStartBefore(this.element); }, insertContent: function(fragments) { fragments.each((function(fragment) { this.element.parentNode.insertBefore(fragment, this.element); }).bind(this)); } }); Insertion.Top = Class.create(); Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { initializeRange: function() { this.range.selectNodeContents(this.element); this.range.collapse(true); }, insertContent: function(fragments) { fragments.reverse(false).each((function(fragment) { this.element.insertBefore(fragment, this.element.firstChild); }).bind(this)); } }); Insertion.Bottom = Class.create(); Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { initializeRange: function() { this.range.selectNodeContents(this.element); this.range.collapse(this.element); }, insertContent: function(fragments) { fragments.each((function(fragment) { this.element.appendChild(fragment); }).bind(this)); } }); Insertion.After = Class.create(); Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { initializeRange: function() { this.range.setStartAfter(this.element); }, insertContent: function(fragments) { fragments.each((function(fragment) { this.element.parentNode.insertBefore(fragment, this.element.nextSibling); }).bind(this)); } }); /*--------------------------------------------------------------------------*/ 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); /* 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(); Selector.prototype = { initialize: function(expression) { this.expression = expression.strip(); this.compileMatcher(); }, compileMatcher: function() { // Selectors with namespaced attributes can't use the XPath version if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression)) 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(typeof c[i] == 'function' ? 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, p, 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(typeof x[i] == 'function' ? 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) { return this.findElements(document).include(element); }, toString: function() { return this.expression; }, inspect: function() { return "#"; } }; 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: "[@#{1}]", attr: function(m) { 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 (typeof h === 'function') 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) { if (!m[6]) return ''; var p = Selector.patterns, x = Selector.xpath; for (var i in p) { if (mm = m[6].match(p[i])) { var ss = typeof x[i] == 'function' ? x[i](mm) : new Template(x[i]).evaluate(mm); m[6] = ss.substring(1, ss.length - 1); break; } } return "[not(" + m[6] + ")]"; }, '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(predicate, m) { var mm, formula = m[6]; if (formula == 'even') formula = '2n+0'; if (formula == 'odd') formula = '2n+1'; if (mm = formula.match(/^(\d+)$/)) // digit only predicate += "= " + mm[1]; if (mm = formula.match(/^(\d+)?n(\+(\d+))?/)) { // an+b var a = mm[1] ? Number(mm[1]) : 1; var b = mm[3] ? Number(mm[3]) : 0; predicate += "mod " + a + " = " + b; } return "[" + predicate + "]"; } } }, 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 = false;', attr: function(m) { m[3] = m[5] || m[6]; return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); }, pseudo: 'n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;', 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|$)/, attrPresence: /^\[([\w]+)\]/, attr: new RegExp(/\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/) }, 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) { for (var i = 0, node; node = nodes[i]; i++) node._counted = true; return nodes; }, unmark: function(nodes) { for (var i = 0, node; node = nodes[i]; i++) node._counted = 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._counted = true; if (reverse) { for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { node = nodes[i]; if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; } } else { for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) if (node.nodeType == 1 && (!ofType || node._counted)) 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])._counted) { n._counted = true; 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, Element.descendants(node)); return results; }, child: function(nodes) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) h.concat(results, Element.immediateDescendants(node)); 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) { tagName = 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() == tagName) results.push(node); return results; } else return root.getElementsByTagName(tagName); }, id: function(nodes, root, id, combinator) { var targetNode = $(id), h = Selector.handlers; if (!nodes && root == document) return targetNode ? [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) { 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) { 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 (combinator) nodes = this[combinator](nodes); 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 nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type nth: function(nodes, formula, root, reverse, ofType) { 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._counted) { 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 var a = m[1] ? Number(m[1]) : 1; var b = m[3] ? Number(m[3]) : 0; for (var i = 0, node; node = nodes[i]; i++) if (node.nodeIndex % a == b) 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, exclusions = $A(nodes), selectorType, m; for (var i in Selector.patterns) { if (m = selector.match(Selector.patterns[i])) { selectorType = i; break; } } switch(selectorType) { case 'className': case 'tagName': case 'id': // fallthroughs case 'attrPresence': exclusions = h[selectorType](exclusions, root, m[1], false); break; case 'attr': m[3] = m[5] || m[6]; exclusions = h.attr(exclusions, root, m[1], m[3], m[2]); break; case 'pseudo': exclusions = h.pseudo(exclusions, m[1], m[6], root, false); break; // only 'simple selectors' (one token) allowed in a :not clause default: throw 'Illegal selector in :not clause.'; } h.mark(exclusions); for (var i = 0, results = [], node; node = nodes[i]; i++) if (!node._counted) 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() + '-'); } }, matchElements: function(elements, expression) { var matches = new Selector(expression).findElements(), h = Selector.handlers; h.mark(matches); for (var i = 0, results = [], element; element = elements[i]; i++) if (element._counted) results.push(element); h.unmark(matches); return results; }, findElement: function(elements, expression, index) { if (typeof expression == 'number') { index = expression; expression = false; } return Selector.matchElements(elements, expression || '*')[index || 0]; }, findChildElements: function(element, expressions) { var exprs = expressions.join(','), expressions = []; exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { expressions.push(m[1].strip()); }); 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; } }); function $$() { return Selector.findChildElements(document, $A(arguments)); } var Form = { reset: function(form) { $(form).reset(); return form; }, serializeElements: function(elements, getHash) { var data = elements.inject({}, function(result, element) { if (!element.disabled && element.name) { var key = element.name, value = $(element).getValue(); if (value != null) { if (key in result) { if (result[key].constructor != Array) result[key] = [result[key]]; result[key].push(value); } else result[key] = value; } } return result; }); return getHash ? data : Hash.toQueryString(data); } }; Form.Methods = { serialize: function(form, getHash) { return Form.serializeElements(Form.getElements(form), getHash); }, 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().each(function(element) { element.blur(); element.disabled = 'true'; }); return form; }, enable: function(form) { form = $(form); form.getElements().each(function(element) { element.disabled = ''; }); return form; }, findFirstElement: function(form) { return $(form).getElements().find(function(element) { return element.type != 'hidden' && !element.disabled && ['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; options.parameters = form.serialize(true); if (params) { if (typeof params == 'string') params = params.toQueryParams(); Object.extend(options.parameters, params); } if (form.hasAttribute('method') && !options.method) options.method = form.method; return new Ajax.Request(form.action, options); } } Object.extend(Form, Form.Methods); /*--------------------------------------------------------------------------*/ 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 Hash.toQueryString(pair); } } return ''; }, getValue: function(element) { element = $(element); var method = element.tagName.toLowerCase(); return Form.Element.Serializers[method](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; } } 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) }); /*--------------------------------------------------------------------------*/ var Field = Form.Element; var $F = Form.Element.getValue; /*--------------------------------------------------------------------------*/ Form.Element.Serializers = { input: function(element) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': return Form.Element.Serializers.inputSelector(element); default: return Form.Element.Serializers.textarea(element); } }, inputSelector: function(element) { return element.checked ? element.value : null; }, textarea: function(element) { return element.value; }, select: function(element) { return this[element.type == 'select-one' ? 'selectOne' : 'selectMany'](element); }, 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 = function() {} Abstract.TimedObserver.prototype = { initialize: function(element, frequency, callback) { this.frequency = frequency; this.element = $(element); this.callback = callback; this.lastValue = this.getValue(); this.registerCallback(); }, registerCallback: function() { setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, onTimerEvent: function() { var value = this.getValue(); var changed = ('string' == typeof this.lastValue && 'string' == typeof value ? this.lastValue != value : String(this.lastValue) != String(value)); if (changed) { this.callback(this.element, value); this.lastValue = value; } } } Form.Element.Observer = Class.create(); Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { getValue: function() { return Form.Element.getValue(this.element); } }); Form.Observer = Class.create(); Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { getValue: function() { return Form.serialize(this.element); } }); /*--------------------------------------------------------------------------*/ Abstract.EventObserver = function() {} Abstract.EventObserver.prototype = { 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.bind(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(); Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { getValue: function() { return Form.Element.getValue(this.element); } }); Form.EventObserver = Class.create(); Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { getValue: function() { return Form.serialize(this.element); } }); if (!window.Event) { var Event = new Object(); } 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, element: function(event) { return event.target || event.srcElement; }, isLeftClick: function(event) { return (((event.which) && (event.which == 1)) || ((event.button) && (event.button == 1))); }, pointerX: function(event) { return event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)); }, pointerY: function(event) { return event.pageY || (event.clientY + (document.documentElement.scrollTop || document.body.scrollTop)); }, stop: function(event) { if (event.preventDefault) { event.preventDefault(); event.stopPropagation(); } else { event.returnValue = false; event.cancelBubble = true; } }, // find the first node with the given tagName, starting from the // node the event was triggered on; traverses the DOM upwards findElement: function(event, tagName) { var element = Event.element(event); while (element.parentNode && (!element.tagName || (element.tagName.toUpperCase() != tagName.toUpperCase()))) element = element.parentNode; return element; }, observers: false, _observeAndCache: function(element, name, observer, useCapture) { if (!this.observers) this.observers = []; if (element.addEventListener) { this.observers.push([element, name, observer, useCapture]); element.addEventListener(name, observer, useCapture); } else if (element.attachEvent) { this.observers.push([element, name, observer, useCapture]); element.attachEvent('on' + name, observer); } }, unloadCache: function() { if (!Event.observers) return; for (var i = 0, length = Event.observers.length; i < length; i++) { Event.stopObserving.apply(this, Event.observers[i]); Event.observers[i][0] = null; } Event.observers = false; }, observe: function(element, name, observer, useCapture) { element = $(element); useCapture = useCapture || false; if (name == 'keypress' && (Prototype.Browser.WebKit || element.attachEvent)) name = 'keydown'; Event._observeAndCache(element, name, observer, useCapture); }, stopObserving: function(element, name, observer, useCapture) { element = $(element); useCapture = useCapture || false; if (name == 'keypress' && (Prototype.Browser.WebKit || element.attachEvent)) name = 'keydown'; if (element.removeEventListener) { element.removeEventListener(name, observer, useCapture); } else if (element.detachEvent) { try { element.detachEvent('on' + name, observer); } catch (e) {} } } }); /* prevent memory leaks in IE */ if (Prototype.Browser.IE) Event.observe(window, 'unload', Event.unloadCache, false); 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; }, realOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.scrollTop || 0; valueL += element.scrollLeft || 0; element = element.parentNode; } while (element); return [valueL, valueT]; }, cumulativeOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; } while (element); return [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 == 'relative' || p == 'absolute') break; } } while (element); return [valueL, valueT]; }, offsetParent: 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; }, // 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 = this.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 = this.realOffset(element); this.xcomp = x + offsetcache[0] - this.deltaX; this.ycomp = y + offsetcache[1] - this.deltaY; this.offset = this.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; }, page: 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) if (Element.getStyle(element,'position')=='absolute') break; } while (element = element.offsetParent); element = forElement; do { if (!window.opera || element.tagName=='BODY') { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } } while (element = element.parentNode); return [valueL, valueT]; }, clone: function(source, target) { 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 = Position.page(source); // find coordinate system to use target = $(target); 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(target,'position') == 'absolute') { parent = Position.offsetParent(target); delta = Position.page(parent); } // 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) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; if(options.setWidth) target.style.width = source.offsetWidth + 'px'; if(options.setHeight) target.style.height = source.offsetHeight + 'px'; }, absolutize: function(element) { element = $(element); if (element.style.position == 'absolute') return; Position.prepare(); var offsets = Position.positionedOffset(element); 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'; }, relativize: function(element) { element = $(element); if (element.style.position == 'relative') return; Position.prepare(); 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; } } // Safari returns margins on body which is incorrect if the child is absolutely // positioned. For performance reasons, redefine Position.cumulativeOffset for // KHTML/WebKit only. if (Prototype.Browser.WebKit) { Position.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 [valueL, valueT]; } } Element.addMethods(); // script.aculo.us builder.js v1.7.1_beta1, Mon Mar 12 14:40:50 +0100 2007 // 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 + ">"; } 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 + ">"; } 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() + '"'); 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))); } }); } } var Prado = { Version: '3.1', /** * Returns browser information. Example * * var browser = Prado.Browser(); * alert(browser.ie); //should ouput true if IE, false otherwise * * @param ${parameter} * @return ${return} */ Browser : function() { var info = { Version : "1.0" }; var is_major = parseInt( navigator.appVersion ); info.nver = is_major; info.ver = navigator.appVersion; info.agent = navigator.userAgent; info.dom = document.getElementById ? 1 : 0; info.opera = window.opera ? 1 : 0; info.ie5 = ( info.ver.indexOf( "MSIE 5" ) > -1 && info.dom && !info.opera ) ? 1 : 0; info.ie6 = ( info.ver.indexOf( "MSIE 6" ) > -1 && info.dom && !info.opera ) ? 1 : 0; info.ie4 = ( document.all && !info.dom && !info.opera ) ? 1 : 0; info.ie = info.ie4 || info.ie5 || info.ie6; info.mac = info.agent.indexOf( "Mac" ) > -1; info.ns6 = ( info.dom && parseInt( info.ver ) >= 5 ) ? 1 : 0; info.ie3 = ( info.ver.indexOf( "MSIE" ) && ( is_major < 4 ) ); info.hotjava = ( info.agent.toLowerCase().indexOf( 'hotjava' ) != -1 ) ? 1 : 0; info.ns4 = ( document.layers && !info.dom && !info.hotjava ) ? 1 : 0; info.bw = ( info.ie6 || info.ie5 || info.ie4 || info.ns4 || info.ns6 || info.opera ); info.ver3 = ( info.hotjava || info.ie3 ); info.opera7 = ( ( info.agent.toLowerCase().indexOf( 'opera 7' ) > -1 ) || ( info.agent.toLowerCase().indexOf( 'opera/7' ) > -1 ) ); info.operaOld = info.opera && !info.opera7; return info; }, ImportCss : function(doc, css_file) { if (Prado.Browser().ie) var styleSheet = doc.createStyleSheet(css_file); else { var elm = doc.createElement("link"); elm.rel = "stylesheet"; elm.href = css_file; if (headArr = doc.getElementsByTagName("head")) headArr[0].appendChild(elm); } } }; /** * Similar to bindAsEventLister, but takes additional arguments. */ Function.prototype.bindEvent = function() { var __method = this, args = $A(arguments), object = args.shift(); return function(event) { return __method.apply(object, [event || window.event].concat(args)); } }; /** * Creates a new function by copying function definition from * the base and optional definition. * @param function a base function to copy from. * @param array additional definition * @param function return a new function with definition from both * base and definition. */ Class.extend = function(base, definition) { var component = Class.create(); Object.extend(component.prototype, base.prototype); if(definition) Object.extend(component.prototype, definition); return component; }; /* Base, version 1.0.2 Copyright 2006, Dean Edwards License: http://creativecommons.org/licenses/LGPL/2.1/ */ var Base = function() { if (arguments.length) { if (this == window) { // cast an object to this class Base.prototype.extend.call(arguments[0], arguments.callee.prototype); } else { this.extend(arguments[0]); } } }; Base.version = "1.0.2"; Base.prototype = { extend: function(source, value) { var extend = Base.prototype.extend; if (arguments.length == 2) { var ancestor = this[source]; // overriding? if ((ancestor instanceof Function) && (value instanceof Function) && ancestor.valueOf() != value.valueOf() && /\bbase\b/.test(value)) { var method = value; // var _prototype = this.constructor.prototype; // var fromPrototype = !Base._prototyping && _prototype[source] == ancestor; value = function() { var previous = this.base; // this.base = fromPrototype ? _prototype[source] : ancestor; this.base = ancestor; var returnValue = method.apply(this, arguments); this.base = previous; return returnValue; }; // point to the underlying method value.valueOf = function() { return method; }; value.toString = function() { return String(method); }; } return this[source] = value; } else if (source) { var _prototype = {toSource: null}; // do the "toString" and other methods manually var _protected = ["toString", "valueOf"]; // if we are prototyping then include the constructor if (Base._prototyping) _protected[2] = "constructor"; for (var i = 0; (name = _protected[i]); i++) { if (source[name] != _prototype[name]) { extend.call(this, name, source[name]); } } // copy each of the source object's properties to this object for (var name in source) { if (!_prototype[name]) { extend.call(this, name, source[name]); } } } return this; }, base: function() { // call this method from any other method to invoke that method's ancestor } }; Base.extend = function(_instance, _static) { var extend = Base.prototype.extend; if (!_instance) _instance = {}; // build the prototype Base._prototyping = true; var _prototype = new this; extend.call(_prototype, _instance); var constructor = _prototype.constructor; _prototype.constructor = this; delete Base._prototyping; // create the wrapper for the constructor function var klass = function() { if (!Base._prototyping) constructor.apply(this, arguments); this.constructor = klass; }; klass.prototype = _prototype; // build the class interface klass.extend = this.extend; klass.implement = this.implement; klass.toString = function() { return String(constructor); }; extend.call(klass, _static); // single instance var object = constructor ? klass : _prototype; // class initialisation if (object.init instanceof Function) object.init(); return object; }; Base.implement = function(_interface) { if (_interface instanceof Function) _interface = _interface.prototype; this.prototype.extend(_interface); }; /** * Performs a post-back using javascript * */ Prado.PostBack = function(event,options) { var form = $(options['FormID']); var canSubmit = true; if(options['CausesValidation'] && typeof(Prado.Validation) != "undefined") { if(!Prado.Validation.validate(options['FormID'], options['ValidationGroup'], $(options['ID']))) return Event.stop(event); } if(options['PostBackUrl'] && options['PostBackUrl'].length > 0) form.action = options['PostBackUrl']; if(options['TrackFocus']) { var lastFocus = $('PRADO_LASTFOCUS'); if(lastFocus) { var active = document.activeElement; //where did this come from if(active) lastFocus.value = active.id; else lastFocus.value = options['EventTarget']; } } $('PRADO_POSTBACK_TARGET').value = options['EventTarget']; $('PRADO_POSTBACK_PARAMETER').value = options['EventParameter']; /** * Since google toolbar prevents browser default action, * we will always disable default client-side browser action */ /*if(options['StopEvent']) */ Event.stop(event); Event.fireEvent(form,"submit"); }; Prado.Element = { /** * Set the value of a particular element. * @param string element id * @param string new element value. */ setValue : function(element, value) { var el = $(element); if(el && typeof(el.value) != "undefined") el.value = value; }, select : function(element, method, value, total) { var el = $(element); if(!el) return; var selection = Prado.Element.Selection; if(typeof(selection[method]) == "function") { control = selection.isSelectable(el) ? [el] : selection.getListElements(element,total); selection[method](control, value); } }, click : function(element) { var el = $(element); if(el) Event.fireEvent(el,'click'); }, isDisabled : function(element) { if(!element.attributes['disabled']) //FF return false; var value = element.attributes['disabled'].nodeValue; if(typeof(value)=="string") return value.toLowerCase() == "disabled"; else return value == true; }, setAttribute : function(element, attribute, value) { var el = $(element); if(!el) return; if((attribute == "disabled" || attribute == "multiple") && value==false) el.removeAttribute(attribute); else if(attribute.match(/^on/i)) //event methods { try { eval("(func = function(event){"+value+"})"); el[attribute] = func; } catch(e) { throw "Error in evaluating '"+value+"' for attribute "+attribute+" for element "+element.id; } } else el.setAttribute(attribute, value); }, setOptions : function(element, options) { var el = $(element); if(!el) return; var previousGroup = null; var optGroup=null; if(el && el.tagName.toLowerCase() == "select") { while(el.childNodes.length > 0) el.removeChild(el.lastChild); var optDom = Prado.Element.createOptions(options); for(var i = 0; i < optDom.length; i++) el.appendChild(optDom[i]); } }, /** * Create opt-group options from an array of options[0]=text, options[1]=value, options[2]=group */ createOptions : function(options) { var previousGroup = null; var optgroup=null; var result = []; for(var i = 0; i 2) { var group = option[2]; if(group!=previousGroup) { if(previousGroup!=null && optgroup!=null) { result.push(optgroup); previousGroup=null; optgroup=null; } optgroup = document.createElement('optgroup'); optgroup.label = group; previousGroup = group; } } var opt = document.createElement('option'); opt.text = option[0]; opt.innerText = option[0]; opt.value = option[1]; if(optgroup!=null) optgroup.appendChild(opt); else result.push(opt); } if(optgroup!=null) result.push(optgroup); return result; }, /** * A delayed focus on a particular element * @param {element} element to apply focus() */ focus : function(element) { var obj = $(element); if(typeof(obj) != "undefined" && typeof(obj.focus) != "undefined") setTimeout(function(){ obj.focus(); }, 100); return false; }, replace : function(element, method, content, boundary) { if(boundary) { result = Prado.Element.extractContent(this.transport.responseText, boundary); if(result != null) content = result; } if(typeof(element) == "string") { if($(element)) method.toFunction().apply(this,[element,""+content]); } else { method.toFunction().apply(this,[""+content]); } }, extractContent : function(text, boundary) { var tagStart = ''; var tagEnd = ''; var start = text.indexOf(tagStart); if(start > -1) { start += tagStart.length; var end = text.indexOf(tagEnd,start); if(end > -1) return text.substring(start,end); } return null; /*var f = RegExp('(?:)((?:.|\n|\r)+?)(?:)',"m"); var result = text.match(f); if(result && result.length >= 2) return result[1]; else return null;*/ }, evaluateScript : function(content) { content.evalScripts(); } }; Prado.Element.Selection = { isSelectable : function(el) { if(el && el.type) { switch(el.type.toLowerCase()) { case 'checkbox': case 'radio': case 'select': case 'select-multiple': case 'select-one': return true; } } return false; }, inputValue : function(el, value) { switch(el.type.toLowerCase()) { case 'checkbox': case 'radio': return el.checked = value; } }, selectValue : function(elements, value) { elements.each(function(el) { $A(el.options).each(function(option) { if(typeof(value) == "boolean") options.selected = value; else if(option.value == value) option.selected = true; }); }) }, selectValues : function(elements, values) { selection = this; values.each(function(value) { selection.selectValue(elements,value); }) }, selectIndex : function(elements, index) { elements.each(function(el) { if(el.type.toLowerCase() == 'select-one') el.selectedIndex = index; else { for(var i = 0; i1) return Builder.node(tag,args.shift(),args); }; }); } }); Builder.exportTags(); /** * @class String extensions */ Object.extend(String.prototype, { /** * @param {String} "left" to pad the string on the left, "right" to pad right. * @param {Number} minimum string length. * @param {String} character(s) to pad * @return {String} padded character(s) on the left or right to satisfy minimum string length */ pad : function(side, len, chr) { if (!chr) chr = ' '; var s = this; var left = side.toLowerCase()=='left'; while (s.lengthInternationalization * is not supported * @param {String} the decimal character * @return {Double} null if string does not represent a float value */ toDouble : function(decimalchar) { if(this.length <= 0) return null; decimalchar = decimalchar || "."; var exp = new RegExp("^\\s*([-\\+])?(\\d+)?(\\" + decimalchar + "(\\d+))?\\s*$"); var m = this.match(exp); if (m == null) return null; m[1] = m[1] || ""; m[2] = m[2] || "0"; m[4] = m[4] || "0"; var cleanInput = m[1] + (m[2].length>0 ? m[2] : "0") + "." + m[4]; var num = parseFloat(cleanInput); return (isNaN(num) ? null : num); }, /** * Convert strings that represent a currency value (e.g. a float with grouping * characters) to float. E.g. "10,000.50" will become "10000.50". The number * of dicimal digits, grouping and decimal characters can be specified. * The currency input format is very strict, null will be returned if * the pattern does not match. * @param {String} the grouping character, default is "," * @param {Number} number of decimal digits * @param {String} the decimal character, default is "." * @type {Double} the currency value as float. */ toCurrency : function(groupchar, digits, decimalchar) { groupchar = groupchar || ","; decimalchar = decimalchar || "."; digits = typeof(digits) == "undefined" ? 2 : digits; var exp = new RegExp("^\\s*([-\\+])?(((\\d+)\\" + groupchar + ")*)(\\d+)" + ((digits > 0) ? "(\\" + decimalchar + "(\\d{1," + digits + "}))?" : "") + "\\s*$"); var m = this.match(exp); if (m == null) return null; var intermed = m[2] + m[5] ; var cleanInput = m[1] + intermed.replace( new RegExp("(\\" + groupchar + ")", "g"), "") + ((digits > 0) ? "." + m[7] : ""); var num = parseFloat(cleanInput); return (isNaN(num) ? null : num); }, /** * Converts the string to a date by finding values that matches the * date format pattern. * @param string date format pattern, e.g. MM-dd-yyyy * @return {Date} the date extracted from the string */ toDate : function(format) { return Date.SimpleParse(this, format); } }); /** * @class Event extensions. */ Object.extend(Event, { /** * Register a function to be executed when the page is loaded. * Note that the page is only loaded if all resources (e.g. images) * are loaded. * * Example: Show an alert box with message "Page Loaded!" when the * page finished loading. * * Event.OnLoad(function(){ alert("Page Loaded!"); }); * * * @param {Function} function to execute when page is loaded. */ OnLoad : function (fn) { // opera onload is in document, not window var w = document.addEventListener && !window.addEventListener ? document : window; Event.observe(w,'load',fn); }, /** * @param {Event} a keyboard event * @return {Number} the Unicode character code generated by the key * that was struck. */ keyCode : function(e) { return e.keyCode != null ? e.keyCode : e.charCode }, /** * @param {String} event type or event name. * @return {Boolean} true if event type is of HTMLEvent, false * otherwise */ isHTMLEvent : function(type) { var events = ['abort', 'blur', 'change', 'error', 'focus', 'load', 'reset', 'resize', 'scroll', 'select', 'submit', 'unload']; return events.include(type); }, /** * @param {String} event type or event name * @return {Boolean} true if event type is of MouseEvent, * false otherwise */ isMouseEvent : function(type) { var events = ['click', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup']; return events.include(type); }, /** * Dispatch the DOM event of a given type on a DOM * element. Only HTMLEvent and MouseEvent can be * dispatched, keyboard events or UIEvent can not be dispatch * via javascript consistently. * For the "submit" event the submit() method is called. * @param {Object} element id string or a DOM element. * @param {String} event type to dispatch. */ fireEvent : function(element,type) { element = $(element); if(type == "submit") return element.submit(); if(document.createEvent) { if(Event.isHTMLEvent(type)) { var event = document.createEvent('HTMLEvents'); event.initEvent(type, true, true); } else if(Event.isMouseEvent(type)) { var event = document.createEvent('MouseEvents'); if (event.initMouseEvent) { event.initMouseEvent(type,true,true, document.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null); } else { // Safari // TODO we should be initialising other mouse-event related attributes here event.initEvent(type, true, true); } } element.dispatchEvent(event); } else if(document.createEventObject) { var evObj = document.createEventObject(); element.fireEvent('on'+type, evObj); } else if(typeof(element['on'+type]) == "function") element['on'+type](); } }); Object.extend(Date.prototype, { SimpleFormat: function(format, data) { data = data || {}; var bits = new Array(); bits['d'] = this.getDate(); bits['dd'] = String(this.getDate()).zerofill(2); bits['M'] = this.getMonth()+1; bits['MM'] = String(this.getMonth()+1).zerofill(2); if(data.AbbreviatedMonthNames) bits['MMM'] = data.AbbreviatedMonthNames[this.getMonth()]; if(data.MonthNames) bits['MMMM'] = data.MonthNames[this.getMonth()]; var yearStr = "" + this.getFullYear(); yearStr = (yearStr.length == 2) ? '19' + yearStr: yearStr; bits['yyyy'] = yearStr; bits['yy'] = bits['yyyy'].toString().substr(2,2); // do some funky regexs to replace the format string // with the real values var frm = new String(format); for (var sect in bits) { var reg = new RegExp("\\b"+sect+"\\b" ,"g"); frm = frm.replace(reg, bits[sect]); } return frm; }, toISODate : function() { var y = this.getFullYear(); var m = String(this.getMonth() + 1).zerofill(2); var d = String(this.getDate()).zerofill(2); return String(y) + String(m) + String(d); } }); Object.extend(Date, { SimpleParse: function(value, format) { val=String(value); format=String(format); if(val.length <= 0) return null; if(format.length <= 0) return new Date(value); var isInteger = function (val) { var digits="1234567890"; for (var i=0; i < val.length; i++) { if (digits.indexOf(val.charAt(i))==-1) { return false; } } return true; }; var getInt = function(str,i,minlength,maxlength) { for (var x=maxlength; x>=minlength; x--) { var token=str.substring(i,i+x); if (token.length < minlength) { return null; } if (isInteger(token)) { return token; } } return null; }; var i_val=0; var i_format=0; var c=""; var token=""; var token2=""; var x,y; var now=new Date(); var year=now.getFullYear(); var month=now.getMonth()+1; var date=1; while (i_format < format.length) { // Get next token from format string c=format.charAt(i_format); token=""; while ((format.charAt(i_format)==c) && (i_format < format.length)) { token += format.charAt(i_format++); } // Extract contents of value based on format token if (token=="yyyy" || token=="yy" || token=="y") { if (token=="yyyy") { x=4;y=4; } if (token=="yy") { x=2;y=2; } if (token=="y") { x=2;y=4; } year=getInt(val,i_val,x,y); if (year==null) { return null; } i_val += year.length; if (year.length==2) { if (year > 70) { year=1900+(year-0); } else { year=2000+(year-0); } } } else if (token=="MM"||token=="M") { month=getInt(val,i_val,token.length,2); if(month==null||(month<1)||(month>12)){return null;} i_val+=month.length; } else if (token=="dd"||token=="d") { date=getInt(val,i_val,token.length,2); if(date==null||(date<1)||(date>31)){return null;} i_val+=date.length; } else { if (val.substring(i_val,i_val+token.length)!=token) {return null;} else {i_val+=token.length;} } } // If there are any trailing characters left in the value, it doesn't match if (i_val != val.length) { return null; } // Is date valid for month? if (month==2) { // Check for leap year if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) { // leap year if (date > 29){ return null; } } else { if (date > 28) { return null; } } } if ((month==4)||(month==6)||(month==9)||(month==11)) { if (date > 30) { return null; } } var newdate=new Date(year,month-1,date, 0, 0, 0); return newdate; } }); Prado.WebUI = Class.create(); Prado.WebUI.PostBackControl = Class.create(); Prado.WebUI.PostBackControl.prototype = { initialize : function(options) { this._elementOnClick = null, //capture the element's onclick function this.element = $(options.ID); if(this.element) { if(this.onInit) this.onInit(options); } }, onInit : function(options) { if(typeof(this.element.onclick)=="function") { this._elementOnClick = this.element.onclick.bind(this.element); this.element.onclick = null; } Event.observe(this.element, "click", this.elementClicked.bindEvent(this,options)); }, elementClicked : function(event, options) { var src = Event.element(event); var doPostBack = true; var onclicked = null; if(this._elementOnClick) { var onclicked = this._elementOnClick(event); if(typeof(onclicked) == "boolean") doPostBack = onclicked; } if(doPostBack && !Prado.Element.isDisabled(src)) this.onPostBack(event,options); if(typeof(onclicked) == "boolean" && !onclicked) Event.stop(event); }, onPostBack : function(event, options) { Prado.PostBack(event,options); } }; Prado.WebUI.TButton = Class.extend(Prado.WebUI.PostBackControl); Prado.WebUI.TLinkButton = Class.extend(Prado.WebUI.PostBackControl); Prado.WebUI.TCheckBox = Class.extend(Prado.WebUI.PostBackControl); Prado.WebUI.TBulletedList = Class.extend(Prado.WebUI.PostBackControl); Prado.WebUI.TImageMap = Class.extend(Prado.WebUI.PostBackControl); /** * TImageButton client-side behaviour. With validation, Firefox needs * to capture the x,y point of the clicked image in hidden form fields. */ Prado.WebUI.TImageButton = Class.extend(Prado.WebUI.PostBackControl); Object.extend(Prado.WebUI.TImageButton.prototype, { /** * Override parent onPostBack function, tried to add hidden forms * inputs to capture x,y clicked point. */ onPostBack : function(event, options) { if(!this.hasXYInput) { this.addXYInput(event,options); this.hasXYInput = true; } Prado.PostBack(event, options); }, /** * Add hidden inputs to capture the x,y point clicked on the image. * @param event DOM click event. * @param array image button options. */ addXYInput : function(event,options) { imagePos = Position.cumulativeOffset(this.element); clickedPos = [event.clientX, event.clientY]; x = clickedPos[0]-imagePos[0]+1; y = clickedPos[1]-imagePos[1]+1; x = x < 0 ? 0 : x; y = y < 0 ? 0 : y; id = options['EventTarget']; x_input = $(id+"_x"); y_input = $(id+"_y"); if(x_input) { x_input.value = x; } else { x_input = INPUT({type:'hidden',name:id+'_x','id':id+'_x',value:x}); this.element.parentNode.appendChild(x_input); } if(y_input) { y_input.value = y; } else { y_input = INPUT({type:'hidden',name:id+'_y','id':id+'_y',value:y}); this.element.parentNode.appendChild(y_input); } } }); /** * Radio button, only initialize if not already checked. */ Prado.WebUI.TRadioButton = Class.extend(Prado.WebUI.PostBackControl); Prado.WebUI.TRadioButton.prototype.onRadioButtonInitialize = Prado.WebUI.TRadioButton.prototype.initialize; Object.extend(Prado.WebUI.TRadioButton.prototype, { initialize : function(options) { this.element = $(options['ID']); if(this.element) { if(!this.element.checked) this.onRadioButtonInitialize(options); } } }); Prado.WebUI.TTextBox = Class.extend(Prado.WebUI.PostBackControl, { onInit : function(options) { this.options=options; if(options['TextMode'] != 'MultiLine') Event.observe(this.element, "keydown", this.handleReturnKey.bind(this)); if(this.options['AutoPostBack']==true) Event.observe(this.element, "change", Prado.PostBack.bindEvent(this,options)); }, handleReturnKey : function(e) { if(Event.keyCode(e) == Event.KEY_RETURN) { var target = Event.element(e); if(target) { if(this.options['AutoPostBack']==true) { Event.fireEvent(target, "change"); Event.stop(e); } else { if(this.options['CausesValidation'] && typeof(Prado.Validation) != "undefined") { if(!Prado.Validation.validate(this.options['FormID'], this.options['ValidationGroup'], $(this.options['ID']))) return Event.stop(e); } } } } } }); Prado.WebUI.TListControl = Class.extend(Prado.WebUI.PostBackControl, { onInit : function(options) { Event.observe(this.element, "change", Prado.PostBack.bindEvent(this,options)); } }); Prado.WebUI.TListBox = Class.extend(Prado.WebUI.TListControl); Prado.WebUI.TDropDownList = Class.extend(Prado.WebUI.TListControl); Prado.WebUI.DefaultButton = Class.create(); Prado.WebUI.DefaultButton.prototype = { initialize : function(options) { this.options = options; this._event = this.triggerEvent.bindEvent(this); Event.observe(options['Panel'], 'keydown', this._event); }, triggerEvent : function(ev, target) { var enterPressed = Event.keyCode(ev) == Event.KEY_RETURN; var isTextArea = Event.element(ev).tagName.toLowerCase() == "textarea"; if(enterPressed && !isTextArea) { var defaultButton = $(this.options['Target']); if(defaultButton) { this.triggered = true; $('PRADO_POSTBACK_TARGET').value = this.options.EventTarget; Event.fireEvent(defaultButton, this.options['Event']); Event.stop(ev); } } } }; Prado.WebUI.TTextHighlighter=Class.create(); Prado.WebUI.TTextHighlighter.prototype= { initialize:function(id) { if(!window.clipboardData) return; var options = { href : 'javascript:;/'+'/copy code to clipboard', onclick : 'Prado.WebUI.TTextHighlighter.copy(this)', onmouseover : 'Prado.WebUI.TTextHighlighter.hover(this)', onmouseout : 'Prado.WebUI.TTextHighlighter.out(this)' } var div = DIV({className:'copycode'}, A(options, 'Copy Code')); document.write(DIV(null,div).innerHTML); } }; Object.extend(Prado.WebUI.TTextHighlighter, { copy : function(obj) { var parent = obj.parentNode.parentNode.parentNode; var text = ''; for(var i = 0; i < parent.childNodes.length; i++) { var node = parent.childNodes[i]; if(node.innerText) text += node.innerText == 'Copy Code' ? '' : node.innerText; else text += node.nodeValue; } if(text.length > 0) window.clipboardData.setData("Text", text); }, hover : function(obj) { obj.parentNode.className = "copycode copycode_hover"; }, out : function(obj) { obj.parentNode.className = "copycode"; } }); Prado.WebUI.TCheckBoxList = Base.extend( { constructor : function(options) { for(var i = 0; i -1 ? this.radios[index].value : this.options.caption; } }; /** * Prado client-side javascript validation fascade. * * There are 4 basic classes, Validation, ValidationManager, ValidationSummary * and TBaseValidator, that interact together to perform validation. * The Prado.Validation class co-ordinates together the * validation scheme and is responsible for maintaining references * to ValidationManagers. * * The ValidationManager class is responsible for maintaining refereneces * to individual validators, validation summaries and their associated * groupings. * * The ValidationSummary take cares of display the validator error messages * as html output or an alert output. * * The TBaseValidator is the base class for all validators and contains * methods to interact with the actual inputs, data type conversion. * * An instance of ValidationManager must be instantiated first for a * particular form before instantiating validators and summaries. * * Usage example: adding a required field to a text box input with * ID "input1" in a form with ID "form1". * * * *
*
* * * * *
*
*
*/ Prado.Validation = Class.create(); /** * A global validation manager. * To validate the inputs of a particular form, call * Prado.Validation.validate(formID, groupID) * where formID is the HTML form ID, and the optional * groupID if present will only validate the validators * in a particular group. */ Object.extend(Prado.Validation, { managers : {}, /** * Validate the validators (those that DO NOT * belong to a particular group) the form specified by the * formID parameter. If groupID is specified * then only validators belonging to that group will be validated. * @param string ID of the form to validate * @param string ID of the group to validate. * @param HTMLElement element that calls for validation */ validate : function(formID, groupID, invoker) { formID = formID || this.getForm(); if(this.managers[formID]) { return this.managers[formID].validate(groupID, invoker); } else { throw new Error("Form '"+form+"' is not registered with Prado.Validation"); } }, /** * @return string first form ID. */ getForm : function() { var keys = $H(this.managers).keys(); return keys[0]; }, /** * Check if the validators are valid for a particular form (and group). * The validators states will not be changed. * The validate function should be called first. * @param string ID of the form to validate * @param string ID of the group to validate. */ isValid : function(formID, groupID) { formID = formID || this.getForm(); if(this.managers[formID]) return this.managers[formID].isValid(groupID); return true; }, /** * Reset the validators for a given group. */ reset : function(groupID) { var formID = this.getForm(); if(this.managers[formID]) this.managers[formID].reset(groupID); }, /** * Add a new validator to a particular form. * @param string the form that the validator belongs. * @param object a validator * @return object the manager */ addValidator : function(formID, validator) { if(this.managers[formID]) this.managers[formID].addValidator(validator); else throw new Error("A validation manager for form '"+formID+"' needs to be created first."); return this.managers[formID]; }, /** * Add a new validation summary. * @param string the form that the validation summary belongs. * @param object a validation summary * @return object manager */ addSummary : function(formID, validator) { if(this.managers[formID]) this.managers[formID].addSummary(validator); else throw new Error("A validation manager for form '"+formID+"' needs to be created first."); return this.managers[formID]; }, setErrorMessage : function(validatorID, message) { $H(Prado.Validation.managers).each(function(manager) { manager[1].validators.each(function(validator) { if(validator.options.ID == validatorID) { validator.options.ErrorMessage = message; $(validatorID).innerHTML = message; } }); }); } }); Prado.ValidationManager = Class.create(); /** * Validation manager instances. Manages validators for a particular * HTML form. The manager contains references to all the validators * summaries, and their groupings for a particular form. * Generally, Prado.Validation methods should be called rather * than calling directly the ValidationManager. */ Prado.ValidationManager.prototype = { /** * * options['FormID']* The ID of HTML form to manage. * */ initialize : function(options) { this.validators = []; // list of validators this.summaries = []; // validation summaries this.groups = []; // validation groups this.options = {}; this.options = options; if(!Prado.Validation.managers[options.FormID]) Prado.Validation.managers[options.FormID] = this; }, /** * Reset all validators in the given group (if group is null, validators without a group are used). */ reset : function(group) { this.validatorPartition(group)[0].invoke('reset'); this.updateSummary(group, true); }, /** * Validate the validators managed by this validation manager. * @param string only validate validators belonging to a group (optional) * @param HTMLElement element that calls for validation * @return boolean true if all validators are valid, false otherwise. */ validate : function(group, source) { var partition = this.validatorPartition(group); var valid = partition[0].invoke('validate', source).all(); this.focusOnError(partition[0]); partition[1].invoke('hide'); this.updateSummary(group, true); return valid; }, /** * Focus on the first validator that is invalid and options.FocusOnError is true. */ focusOnError : function(validators) { for(var i = 0; i < validators.length; i++) { if(!validators[i].isValid && validators[i].options.FocusOnError) return Prado.Element.focus(validators[i].options.FocusElementID); } }, /** * @return array[0] validators belong to a group if group is given, otherwise validators * not belongining to any group. array[1] the opposite of array[0]. */ validatorPartition : function(group) { return group ? this.validatorsInGroup(group) : this.validatorsWithoutGroup(); }, /** * @return array validatiors in a given group in first array and * validators not belonging to the group in 2nd array. */ validatorsInGroup : function(groupID) { if(this.groups.include(groupID)) { return this.validators.partition(function(val) { return val.group == groupID; }); } else return [[],[]]; }, /** * @return array validators without any group in first array, and those * with groups in 2nd array. */ validatorsWithoutGroup : function() { return this.validators.partition(function(val) { return !val.group; }); }, /** * Gets the state of all the validators, true if they are all valid. * @return boolean true if the validators are valid. */ isValid : function(group) { return this.validatorPartition(group)[0].pluck('isValid').all(); }, /** * Add a validator to this manager. * @param Prado.WebUI.TBaseValidator a new validator */ addValidator : function(validator) { this.validators.push(validator); if(validator.group && !this.groups.include(validator.group)) this.groups.push(validator.group); }, /** * Add a validation summary. * @param Prado.WebUI.TValidationSummary validation summary. */ addSummary : function(summary) { this.summaries.push(summary); }, /** * Gets all validators that belong to a group or that the validator * group is null and the validator validation was false. * @return array list of validators with error. */ getValidatorsWithError : function(group) { return this.validatorPartition(group)[0].findAll(function(validator) { return !validator.isValid; }); }, /** * Update the summary of a particular group. * @param string validation group to update. */ updateSummary : function(group, refresh) { var validators = this.getValidatorsWithError(group); this.summaries.each(function(summary) { var inGroup = group && summary.group == group; var noGroup = !group && !summary.group; if(inGroup || noGroup) summary.updateSummary(validators, refresh); else summary.hideSummary(true); }); } }; /** * TValidationSummary displays a summary of validation errors inline on a Web page, * in a message box, or both. By default, a validation summary will collect * ErrorMessage of all failed validators on the page. If * ValidationGroup is not empty, only those validators who belong * to the group will show their error messages in the summary. * * The summary can be displayed as a list, as a bulleted list, or as a single * paragraph based on the DisplayMode option. * The messages shown can be prefixed with HeaderText. * * The summary can be displayed on the Web page and in a message box by setting * the ShowSummary and ShowMessageBox * options, respectively. */ Prado.WebUI.TValidationSummary = Class.create(); Prado.WebUI.TValidationSummary.prototype = { /** * * options['ID']* Validation summary ID, i.e., an HTML element ID * options['FormID']* HTML form that this summary belongs. * options['ShowMessageBox'] True to show the summary in an alert box. * options['ShowSummary'] True to show the inline summary. * options['HeaderText'] Summary header text * options['DisplayMode'] Summary display style, 'BulletList', 'List', 'SingleParagraph' * options['Refresh'] True to update the summary upon validator state change. * options['ValidationGroup'] Validation summary group * options['Display'] Display mode, 'None', 'Fixed', 'Dynamic'. * options['ScrollToSummary'] True to scroll to the validation summary upon refresh. * */ initialize : function(options) { this.options = options; this.group = options.ValidationGroup; this.messages = $(options.ID); if(this.messages) { this.visible = this.messages.style.visibility != "hidden" this.visible = this.visible && this.messages.style.display != "none"; Prado.Validation.addSummary(options.FormID, this); } }, /** * Update the validation summary to show the error message from * validators that failed validation. * @param array list of validators that failed validation. * @param boolean update the summary; */ updateSummary : function(validators, update) { if(validators.length <= 0) { if(update || this.options.Refresh != false) { return this.hideSummary(validators); } return; } var refresh = update || this.visible == false || this.options.Refresh != false; if(this.options.ShowSummary != false && refresh) { this.updateHTMLMessages(this.getMessages(validators)); this.showSummary(validators); } if(this.options.ScrollToSummary != false && refresh) window.scrollTo(this.messages.offsetLeft-20, this.messages.offsetTop-20); if(this.options.ShowMessageBox == true && refresh) { this.alertMessages(this.getMessages(validators)); this.visible = true; } }, /** * Display the validator error messages as inline HTML. */ updateHTMLMessages : function(messages) { while(this.messages.childNodes.length > 0) this.messages.removeChild(this.messages.lastChild); new Insertion.Bottom(this.messages, this.formatSummary(messages)); }, /** * Display the validator error messages as an alert box. */ alertMessages : function(messages) { var text = this.formatMessageBox(messages); setTimeout(function(){ alert(text); },20); }, /** * @return array list of validator error messages. */ getMessages : function(validators) { var messages = []; validators.each(function(validator) { var message = validator.getErrorMessage(); if(typeof(message) == 'string' && message.length > 0) messages.push(message); }) return messages; }, /** * Hides the validation summary. */ hideSummary : function(validators) { if(typeof(this.options.OnHideSummary) == "function") { this.messages.style.visibility="visible"; this.options.OnHideSummary(this,validators) } else { this.messages.style.visibility="hidden"; if(this.options.Display == "None" || this.options.Display == "Dynamic") this.messages.hide(); } this.visible = false; }, /** * Shows the validation summary. */ showSummary : function(validators) { this.messages.style.visibility="visible"; if(typeof(this.options.OnShowSummary) == "function") this.options.OnShowSummary(this,validators); else this.messages.show(); this.visible = true; }, /** * Return the format parameters for the summary. * @param string format type, "List", "SingleParagraph" or "BulletList" * @type array formatting parameters */ formats : function(type) { switch(type) { case "List": return { header : "
", first : "", pre : "", post : "
", last : ""}; case "SingleParagraph": return { header : " ", first : "", pre : "", post : " ", last : "
"}; case "BulletList": default: return { header : "", first : "
    ", pre : "
  • ", post : "
  • ", last : "
"}; } }, /** * Format the message summary. * @param array list of error messages. * @type string formatted message */ formatSummary : function(messages) { var format = this.formats(this.options.DisplayMode); var output = this.options.HeaderText ? this.options.HeaderText + format.header : ""; output += format.first; messages.each(function(message) { output += message.length > 0 ? format.pre + message + format.post : ""; }); // for(var i = 0; i < messages.length; i++) // output += (messages[i].length>0) ? format.pre + messages[i] + format.post : ""; output += format.last; return output; }, /** * Format the message alert box. * @param array a list of error messages. * @type string format message for alert. */ formatMessageBox : function(messages) { var output = this.options.HeaderText ? this.options.HeaderText + "\n" : ""; for(var i = 0; i < messages.length; i++) { switch(this.options.DisplayMode) { case "List": output += messages[i] + "\n"; break; case "BulletList": default: output += " - " + messages[i] + "\n"; break; case "SingleParagraph": output += messages[i] + " "; break; } } return output; } }; /** * TBaseValidator serves as the base class for validator controls. * * Validation is performed when a postback control, such as a TButton, * a TLinkButton or a TTextBox (under AutoPostBack mode) is submitting * the page and its CausesValidation option is true. * The input control to be validated is specified by ControlToValidate * option. */ Prado.WebUI.TBaseValidator = Class.create(); Prado.WebUI.TBaseValidator.prototype = { /** * * options['ID']* Validator ID, e.g. span with message * options['FormID']* HTML form that the validator belongs * options['ControlToValidate']*HTML form input to validate * options['Display'] Display mode, 'None', 'Fixed', 'Dynamic' * options['ErrorMessage'] Validation error message * options['FocusOnError'] True to focus on validation error * options['FocusElementID'] Element to focus on error * options['ValidationGroup'] Validation group * options['ControlCssClass'] Css class to use on the input upon error * options['OnValidate'] Function to call immediately after validation * options['OnValidationSuccess'] Function to call upon after successful validation * options['OnValidationError'] Function to call upon after error in validation. * options['ObserveChanges'] True to observe changes in input * */ initialize : function(options) { /* options.OnValidate = options.OnValidate || Prototype.emptyFunction; options.OnSuccess = options.OnSuccess || Prototype.emptyFunction; options.OnError = options.OnError || Prototype.emptyFunction; */ this.enabled = true; this.visible = false; this.isValid = true; this._isObserving = {}; this.group = null; this.requestDispatched = false; this.options = options; this.control = $(options.ControlToValidate); this.message = $(options.ID); if(this.control && this.message) { this.group = options.ValidationGroup; this.manager = Prado.Validation.addValidator(options.FormID, this); } }, /** * @return string validation error message. */ getErrorMessage : function() { return this.options.ErrorMessage; }, /** * Update the validator span, input CSS class, and focus particular * element. Updating the validator control will set the validator * visible property to true. */ updateControl: function(focus) { this.refreshControlAndMessage(); //if(this.options.FocusOnError && !this.isValid ) // Prado.Element.focus(this.options.FocusElementID); this.visible = true; }, refreshControlAndMessage : function() { this.visible = true; if(this.message) { if(this.options.Display == "Dynamic") { var msg=this.message; this.isValid ? setTimeout(function() { msg.hide(); }, 250) : msg.show(); } this.message.style.visibility = this.isValid ? "hidden" : "visible"; } if(this.control) this.updateControlCssClass(this.control, this.isValid); }, /** * Add a css class to the input control if validator is invalid, * removes the css class if valid. * @param object html control element * @param boolean true to remove the css class, false to add. */ updateControlCssClass : function(control, valid) { var CssClass = this.options.ControlCssClass; if(typeof(CssClass) == "string" && CssClass.length > 0) { if(valid) control.removeClassName(CssClass); else control.addClassName(CssClass); } }, /** * Hides the validator messages and remove any validation changes. */ hide : function() { this.reset(); this.visible = false; }, /** * Sets isValid = true and updates the validator display. */ reset : function() { this.isValid = true; this.updateControl(); }, /** * Calls evaluateIsValid() function to set the value of isValid property. * Triggers onValidate event and onSuccess or onError event. * @param HTMLElement element that calls for validation * @return boolean true if valid. */ validate : function(invoker) { //try to find the control. if(!this.control) this.control = $(this.options.ControlToValidate); if(!this.control || this.control.disabled) { this.isValid = true; return this.isValid; } if(typeof(this.options.OnValidate) == "function") { if(this.requestDispatched == false) this.options.OnValidate(this, invoker); } if(this.enabled && !this.control.getAttribute('disabled')) this.isValid = this.evaluateIsValid(); else this.isValid = true; this.updateValidationDisplay(invoker); this.observeChanges(this.control); return this.isValid; }, /** * Updates the validation messages, update the control to be validated. */ updateValidationDisplay : function(invoker) { if(this.isValid) { if(typeof(this.options.OnValidationSuccess) == "function") { if(this.requestDispatched == false) { this.refreshControlAndMessage(); this.options.OnValidationSuccess(this, invoker); } } else this.updateControl(); } else { if(typeof(this.options.OnValidationError) == "function") { if(this.requestDispatched == false) { this.refreshControlAndMessage(); this.options.OnValidationError(this, invoker) } } else this.updateControl(); } }, /** * Observe changes to the control input, re-validate upon change. If * the validator is not visible, no updates are propagated. * @param HTMLElement element that calls for validation */ observeChanges : function(control) { if(!control) return; var canObserveChanges = this.options.ObserveChanges != false; var currentlyObserving = this._isObserving[control.id+this.options.ID]; if(canObserveChanges && !currentlyObserving) { var validator = this; Event.observe(control, 'change', function() { if(validator.visible) { validator.validate(); validator.manager.updateSummary(validator.group); } }); this._isObserving[control.id+this.options.ID] = true; } }, /** * @return string trims the string value, empty string if value is not string. */ trim : function(value) { return typeof(value) == "string" ? value.trim() : ""; }, /** * Convert the value to a specific data type. * @param {string} the data type, "Integer", "Double", "Date" or "String" * @param {string} the value to convert. * @type {mixed|null} the converted data value. */ convert : function(dataType, value) { if(typeof(value) == "undefined") value = this.getValidationValue(); var string = new String(value); switch(dataType) { case "Integer": return string.toInteger(); case "Double" : case "Float" : return string.toDouble(this.options.DecimalChar); case "Date": if(typeof(value) != "string") return value; else { var value = string.toDate(this.options.DateFormat); if(value && typeof(value.getTime) == "function") return value.getTime(); else return null; } case "String": return string.toString(); } return value; }, /** * The ControlType property comes from TBaseValidator::getClientControlClass() * Be sure to update the TBaseValidator::$_clientClass if new cases are added. * @return mixed control value to validate */ getValidationValue : function(control) { if(!control) control = this.control switch(this.options.ControlType) { case 'TDatePicker': if(control.type == "text") { value = this.trim($F(control)); if(this.options.DateFormat) { date = value.toDate(this.options.DateFormat); return date == null ? value : date; } else return value; } else { this.observeDatePickerChanges(); return Prado.WebUI.TDatePicker.getDropDownDate(control);//.getTime(); } case 'THtmlArea': if(typeof tinyMCE != "undefined") tinyMCE.triggerSave(); return this.trim($F(control)); case 'TRadioButton': if(this.options.GroupName) return this.getRadioButtonGroupValue(); default: if(this.isListControlType()) return this.getFirstSelectedListValue(); else return this.trim($F(control)); } }, getRadioButtonGroupValue : function() { name = this.control.name; value = ""; $A(document.getElementsByName(name)).each(function(el) { if(el.checked) value = el.value; }); return value; }, /** * Observe changes in the drop down list date picker, IE only. */ observeDatePickerChanges : function() { if(Prado.Browser().ie) { var DatePicker = Prado.WebUI.TDatePicker; this.observeChanges(DatePicker.getDayListControl(this.control)); this.observeChanges(DatePicker.getMonthListControl(this.control)); this.observeChanges(DatePicker.getYearListControl(this.control)); } }, /** * Gets numeber selections and their values. * @return object returns selected values in values property * and number of selections in checks property. */ getSelectedValuesAndChecks : function(elements, initialValue) { var checked = 0; var values = []; var isSelected = this.isCheckBoxType(elements[0]) ? 'checked' : 'selected'; elements.each(function(element) { if(element[isSelected] && element.value != initialValue) { checked++; values.push(element.value); } }); return {'checks' : checked, 'values' : values}; }, /** * Gets an array of the list control item input elements, for TCheckBoxList * checkbox inputs are returned, for TListBox HTML option elements are returned. * @return array list control option elements. */ getListElements : function() { switch(this.options.ControlType) { case 'TCheckBoxList': case 'TRadioButtonList': var elements = []; for(var i = 0; i < this.options.TotalItems; i++) { var element = $(this.options.ControlToValidate+"_c"+i); if(this.isCheckBoxType(element)) elements.push(element); } return elements; case 'TListBox': var elements = []; var element = $(this.options.ControlToValidate); if(element && (type = element.type.toLowerCase())) { if(type == "select-one" || type == "select-multiple") elements = $A(element.options); } return elements; default: return []; } }, /** * @return boolean true if element is of checkbox or radio type. */ isCheckBoxType : function(element) { if(element && element.type) { var type = element.type.toLowerCase(); return type == "checkbox" || type == "radio"; } return false; }, /** * @return boolean true if control to validate is of some of the TListControl type. */ isListControlType : function() { var list = ['TCheckBoxList', 'TRadioButtonList', 'TListBox']; return list.include(this.options.ControlType); }, /** * @return string gets the first selected list value, initial value if none found. */ getFirstSelectedListValue : function() { var initial = ""; if(typeof(this.options.InitialValue) != "undefined") initial = this.options.InitialValue; var elements = this.getListElements(); var selection = this.getSelectedValuesAndChecks(elements, initial); return selection.values.length > 0 ? selection.values[0] : initial; } } /** * TRequiredFieldValidator makes the associated input control a required field. * The input control fails validation if its value does not change from * the InitialValue option upon losing focus. * * options['InitialValue'] Validation fails if control input equals initial value. * */ Prado.WebUI.TRequiredFieldValidator = Class.extend(Prado.WebUI.TBaseValidator, { /** * @return boolean true if the input value is not empty nor equal to the initial value. */ evaluateIsValid : function() { var inputType = this.control.getAttribute("type"); if(inputType == 'file') { return true; } else { var a = this.getValidationValue(); var b = this.trim(this.options.InitialValue); return(a != b); } } }); /** * TCompareValidator compares the value entered by the user into an input * control with the value entered into another input control or a constant value. * To compare the associated input control with another input control, * set the ControlToCompare option to the ID path * of the control to compare with. To compare the associated input control with * a constant value, specify the constant value to compare with by setting the * ValueToCompare option. * * The DataType property is used to specify the data type * of both comparison values. Both values are automatically converted to this data * type before the comparison operation is performed. The following value types are supported: * - Integer A 32-bit signed integer data type. * - Float A double-precision floating point number data type. * - Date A date data type. The format can be by the DateFormat option. * - String A string data type. * * Use the Operator property to specify the type of comparison * to perform. Valid operators include Equal, NotEqual, GreaterThan, GreaterThanEqual, * LessThan and LessThanEqual. * * options['ControlToCompare'] * options['ValueToCompare'] * options['Operator'] * options['Type'] * options['DateFormat'] * */ Prado.WebUI.TCompareValidator = Class.extend(Prado.WebUI.TBaseValidator, { //_observingComparee : false, /** * Compares the input to another input or a given value. */ evaluateIsValid : function() { var value = this.getValidationValue(); if (value.length <= 0) return true; var comparee = $(this.options.ControlToCompare); if(comparee) var compareTo = this.getValidationValue(comparee); else var compareTo = this.options.ValueToCompare || ""; var isValid = this.compare(value, compareTo); if(comparee) { this.updateControlCssClass(comparee, isValid); this.observeChanges(comparee); } return isValid; }, /** * Compares two values, their values are casted to type defined * by DataType option. False is returned if the first * operand converts to null. Returns true if the second operand * converts to null. The comparision is done based on the * Operator option. */ compare : function(operand1, operand2) { var op1, op2; if((op1 = this.convert(this.options.DataType, operand1)) == null) return false; if ((op2 = this.convert(this.options.DataType, operand2)) == null) return true; switch (this.options.Operator) { case "NotEqual": return (op1 != op2); case "GreaterThan": return (op1 > op2); case "GreaterThanEqual": return (op1 >= op2); case "LessThan": return (op1 < op2); case "LessThanEqual": return (op1 <= op2); default: return (op1 == op2); } } }); /** * TCustomValidator performs user-defined client-side validation on an * input component. * * To create a client-side validation function, add the client-side * validation javascript function to the page template. * The function should have the following signature: * * * * Use the ClientValidationFunction option * to specify the name of the client-side validation script function associated * with the TCustomValidator. * * options['ClientValidationFunction'] custom validation function. * */ Prado.WebUI.TCustomValidator = Class.extend(Prado.WebUI.TBaseValidator, { /** * Calls custom validation function. */ evaluateIsValid : function() { var value = this.getValidationValue(); var clientFunction = this.options.ClientValidationFunction; if(typeof(clientFunction) == "string" && clientFunction.length > 0) { validate = clientFunction.toFunction(); return validate(this, value); } return true; } }); /** * Uses callback request to perform validation. */ Prado.WebUI.TActiveCustomValidator = Class.extend(Prado.WebUI.TBaseValidator, { validatingValue : null, /** * Calls custom validation function. */ evaluateIsValid : function() { value = this.getValidationValue(); if(!this.requestDispatched && (""+value) != (""+this.validatingValue)) //if((""+value) != (""+this.validatingValue)) { this.validatingValue = value; request = new Prado.CallbackRequest(this.options.EventTarget, this.options); if(this.options.DateFormat && value instanceof Date) //change date to string with formatting. value = value.SimpleFormat(this.options.DateFormat); request.setCallbackParameter(value); request.setCausesValidation(false); request.options.onSuccess = this.callbackOnSuccess.bind(this); request.options.onFailure = this.callbackOnFailure.bind(this); request.dispatch(); this.requestDispatched = true; return false; } return this.isValid; }, callbackOnSuccess : function(request, data) { this.isValid = data; this.requestDispatched = false; if(typeof(this.options.onSuccess) == "function") this.options.onSuccess(request,data); this.updateValidationDisplay(); }, callbackOnFailure : function(request, data) { this.requestDispatched = false; if(typeof(this.options.onFailure) == "function") this.options.onFailure(request,data); } }); /** * TRangeValidator tests whether an input value is within a specified range. * * TRangeValidator uses three key properties to perform its validation. * The MinValue and MaxValue options specify the minimum * and maximum values of the valid range. The DataType option is * used to specify the data type of the value and the minimum and maximum range values. * These values are converted to this data type before the validation * operation is performed. The following value types are supported: * - Integer A 32-bit signed integer data type. * - Float A double-precision floating point number data type. * - Date A date data type. The date format can be specified by * setting DateFormat option, which must be recognizable * by Date.SimpleParse javascript function. * - String A string data type. * * options['MinValue'] Minimum range value * options['MaxValue'] Maximum range value * options['DataType'] Value data type * options['DateFormat'] Date format for date data type. * */ Prado.WebUI.TRangeValidator = Class.extend(Prado.WebUI.TBaseValidator, { /** * Compares the input value with a minimum and/or maximum value. * @return boolean true if the value is empty, returns false if conversion fails. */ evaluateIsValid : function() { var value = this.getValidationValue(); if(value.length <= 0) return true; if(typeof(this.options.DataType) == "undefined") this.options.DataType = "String"; if(this.options.DataType != "StringLength") { var min = this.convert(this.options.DataType, this.options.MinValue || null); var max = this.convert(this.options.DataType, this.options.MaxValue || null); value = this.convert(this.options.DataType, value); } else { var min = this.options.MinValue || 0; var max = this.options.MaxValue || Number.POSITIVE_INFINITY; value = value.length; } if(value == null) return false; var valid = true; if(min != null) valid = valid && (this.options.StrictComparison ? value > min : value >= min); if(max != null) valid = valid && (this.options.StrictComparison ? value < max : value <= max); return valid; } }); /** * TRegularExpressionValidator validates whether the value of an associated * input component matches the pattern specified by a regular expression. * * options['ValidationExpression'] regular expression to match against. * */ Prado.WebUI.TRegularExpressionValidator = Class.extend(Prado.WebUI.TBaseValidator, { /** * Compare the control input against a regular expression. */ evaluateIsValid : function() { var value = this.getValidationValue(); if (value.length <= 0) return true; var rx = new RegExp(this.options.ValidationExpression); var matches = rx.exec(value); return (matches != null && value == matches[0]); } }); /** * TEmailAddressValidator validates whether the value of an associated * input component is a valid email address. */ Prado.WebUI.TEmailAddressValidator = Prado.WebUI.TRegularExpressionValidator; /** * TListControlValidator checks the number of selection and their values * for a TListControl that allows multiple selections. */ Prado.WebUI.TListControlValidator = Class.extend(Prado.WebUI.TBaseValidator, { /** * @return true if the number of selections and/or their values * match the requirements. */ evaluateIsValid : function() { var elements = this.getListElements(); if(elements && elements.length <= 0) return true; this.observeListElements(elements); var selection = this.getSelectedValuesAndChecks(elements); return this.isValidList(selection.checks, selection.values); }, /** * Observe list elements for IE browsers of changes */ observeListElements : function(elements) { if(Prado.Browser().ie && this.isCheckBoxType(elements[0])) { var validator = this; elements.each(function(element) { validator.observeChanges(element); }); } }, /** * Determine if the number of checked and the checked values * satisfy the required number of checks and/or the checked values * equal to the required values. * @return boolean true if checked values and number of checks are satisfied. */ isValidList : function(checked, values) { var exists = true; //check the required values var required = this.getRequiredValues(); if(required.length > 0) { if(values.length < required.length) return false; required.each(function(requiredValue) { exists = exists && values.include(requiredValue); }); } var min = typeof(this.options.Min) == "undefined" ? Number.NEGATIVE_INFINITY : this.options.Min; var max = typeof(this.options.Max) == "undefined" ? Number.POSITIVE_INFINITY : this.options.Max; return exists && checked >= min && checked <= max; }, /** * @return array list of required options that must be selected. */ getRequiredValues : function() { var required = []; if(this.options.Required && this.options.Required.length > 0) required = this.options.Required.split(/,\s*/); return required; } }); /** * TDataTypeValidator verifies if the input data is of the type specified * by DataType option. * The following data types are supported: * - Integer A 32-bit signed integer data type. * - Float A double-precision floating point number data type. * - Date A date data type. * - String A string data type. * For Date type, the option DateFormat * will be used to determine how to parse the date string. */ Prado.WebUI.TDataTypeValidator = Class.extend(Prado.WebUI.TBaseValidator, { evaluateIsValid : function() { value = this.getValidationValue(); if(value.length <= 0) return true; return this.convert(this.options.DataType, value) != null; } }); /** * TCaptchaValidator verifies if the input data is the same as the token shown in the associated CAPTCHA contorl. */ Prado.WebUI.TCaptchaValidator = Class.extend(Prado.WebUI.TBaseValidator, { /** * @return boolean true if the input value is not empty nor equal to the initial value. */ evaluateIsValid : function() { var a = this.getValidationValue(); var h = 0; for(var i = a.length-1; i >= 0; --i) h += a.charCodeAt(i); return h == this.options.TokenHash; }, crc32 : function(str) { function Utf8Encode(string) { string = string.replace(/\r\n/g,"\n"); var utftext = ""; for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if((c > 127) && (c < 2048)) { utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c & 63) | 128); } else { utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode((c & 63) | 128); } } return utftext; }; str = Utf8Encode(str); var table = "00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 9E6495A3 0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 90BF1D91 1DB71064 6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 83D385C7 136C9856 646BA8C0 FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 8D080DF5 3B6E20C8 4C69105E D56041E4 A2677172 3C03E4D1 4B04D447 D20D85FD A50AB56B 35B5A8FA 42B2986C DBBBC9D6 ACBCF940 32D86CE3 45DF5C75 DCD60DCF ABD13D59 26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 56B3C423 CFBA9599 B8BDA50F 2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 C1611DAB B6662D3D 76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 E8B8D433 7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 E6635C01 6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 F50FC457 65B0D9C6 12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 FBD44C65 4DB26158 3AB551CE A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D D3D6F4FB 4369E96A 346ED9FC AD678846 DA60B8D0 44042D73 33031DE5 AA0A4C5F DD0D7CC9 5005713C 270241AA BE0B1010 C90C2086 5768B525 206F85B3 B966D409 CE61E49F 5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 2EB40D81 B7BD5C3B C0BA6CAD EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF 04DB2615 73DC1683 E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 7D079EB1 F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 6E6B06E7 FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5 D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD 48B2364B D80D2BDA AF0A1B4C 36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF 4669BE79 CB61B38C BC66831A 256FD2A0 5268E236 CC0C7795 BB0B4703 220216B9 5505262F C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D 9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 EB0E363F 72076785 05005713 95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D 7CDCEFB7 0BDBDF21 86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 18B74777 88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 166CCF45 A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D 3E6E77DB AED16A4A D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9 BDBDF21C CABAC28A 53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 23D967BF B3667A2E C4614AB8 5D681B02 2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D"; var crc = 0; var x = 0; var y = 0; crc = crc ^ (-1); for( var i = 0, iTop = str.length; i < iTop; i++ ) { y = ( crc ^ str.charCodeAt( i ) ) & 0xFF; x = "0x" + table.substr( y * 9, 8 ); crc = ( crc >>> 8 ) ^ x; } return crc ^ (-1); } });