1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/src/devtools/jint/v8/raytrace.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,3418 @@ 1.4 +// The ray tracer code in this file is written by Adam Burmister. It 1.5 +// is available in its original form from: 1.6 +// 1.7 +// http://labs.flog.nz.co/raytracer/ 1.8 +// 1.9 +// It has been modified slightly by Google to work as a standalone 1.10 +// benchmark, but the all the computational code remains 1.11 +// untouched. This file also contains a copy of the Prototype 1.12 +// JavaScript framework which is used by the ray tracer. 1.13 + 1.14 +var RayTrace = new BenchmarkSuite('RayTrace', 932666, [ 1.15 + new Benchmark('RayTrace', renderScene) 1.16 +]); 1.17 + 1.18 + 1.19 +// Create dummy objects if we're not running in a browser. 1.20 +if (typeof document == 'undefined') { 1.21 + document = { }; 1.22 + window = { opera: null }; 1.23 + navigator = { userAgent: null, appVersion: "" }; 1.24 +} 1.25 + 1.26 + 1.27 +// ------------------------------------------------------------------------ 1.28 +// ------------------------------------------------------------------------ 1.29 + 1.30 + 1.31 +/* Prototype JavaScript framework, version 1.5.0 1.32 + * (c) 2005-2007 Sam Stephenson 1.33 + * 1.34 + * Prototype is freely distributable under the terms of an MIT-style license. 1.35 + * For details, see the Prototype web site: http://prototype.conio.net/ 1.36 + * 1.37 +/*--------------------------------------------------------------------------*/ 1.38 + 1.39 +//-------------------- 1.40 +var Prototype = { 1.41 + Version: '1.5.0', 1.42 + BrowserFeatures: { 1.43 + XPath: !!document.evaluate 1.44 + }, 1.45 + 1.46 + ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)', 1.47 + emptyFunction: function() {}, 1.48 + K: function(x) { return x } 1.49 +} 1.50 + 1.51 +var Class = { 1.52 + create: function() { 1.53 + return function() { 1.54 + this.initialize.apply(this, arguments); 1.55 + } 1.56 + } 1.57 +} 1.58 + 1.59 +var Abstract = new Object(); 1.60 + 1.61 +Object.extend = function(destination, source) { 1.62 + for (var property in source) { 1.63 + destination[property] = source[property]; 1.64 + } 1.65 + return destination; 1.66 +} 1.67 + 1.68 +Object.extend(Object, { 1.69 + inspect: function(object) { 1.70 + try { 1.71 + if (object === undefined) return 'undefined'; 1.72 + if (object === null) return 'null'; 1.73 + return object.inspect ? object.inspect() : object.toString(); 1.74 + } catch (e) { 1.75 + if (e instanceof RangeError) return '...'; 1.76 + throw e; 1.77 + } 1.78 + }, 1.79 + 1.80 + keys: function(object) { 1.81 + var keys = []; 1.82 + for (var property in object) 1.83 + keys.push(property); 1.84 + return keys; 1.85 + }, 1.86 + 1.87 + values: function(object) { 1.88 + var values = []; 1.89 + for (var property in object) 1.90 + values.push(object[property]); 1.91 + return values; 1.92 + }, 1.93 + 1.94 + clone: function(object) { 1.95 + return Object.extend({}, object); 1.96 + } 1.97 +}); 1.98 + 1.99 +Function.prototype.bind = function() { 1.100 + var __method = this, args = $A(arguments), object = args.shift(); 1.101 + return function() { 1.102 + return __method.apply(object, args.concat($A(arguments))); 1.103 + } 1.104 +} 1.105 + 1.106 +Function.prototype.bindAsEventListener = function(object) { 1.107 + var __method = this, args = $A(arguments), object = args.shift(); 1.108 + return function(event) { 1.109 + return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments))); 1.110 + } 1.111 +} 1.112 + 1.113 +Object.extend(Number.prototype, { 1.114 + toColorPart: function() { 1.115 + var digits = this.toString(16); 1.116 + if (this < 16) return '0' + digits; 1.117 + return digits; 1.118 + }, 1.119 + 1.120 + succ: function() { 1.121 + return this + 1; 1.122 + }, 1.123 + 1.124 + times: function(iterator) { 1.125 + $R(0, this, true).each(iterator); 1.126 + return this; 1.127 + } 1.128 +}); 1.129 + 1.130 +var Try = { 1.131 + these: function() { 1.132 + var returnValue; 1.133 + 1.134 + for (var i = 0, length = arguments.length; i < length; i++) { 1.135 + var lambda = arguments[i]; 1.136 + try { 1.137 + returnValue = lambda(); 1.138 + break; 1.139 + } catch (e) {} 1.140 + } 1.141 + 1.142 + return returnValue; 1.143 + } 1.144 +} 1.145 + 1.146 +/*--------------------------------------------------------------------------*/ 1.147 + 1.148 +var PeriodicalExecuter = Class.create(); 1.149 +PeriodicalExecuter.prototype = { 1.150 + initialize: function(callback, frequency) { 1.151 + this.callback = callback; 1.152 + this.frequency = frequency; 1.153 + this.currentlyExecuting = false; 1.154 + 1.155 + this.registerCallback(); 1.156 + }, 1.157 + 1.158 + registerCallback: function() { 1.159 + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); 1.160 + }, 1.161 + 1.162 + stop: function() { 1.163 + if (!this.timer) return; 1.164 + clearInterval(this.timer); 1.165 + this.timer = null; 1.166 + }, 1.167 + 1.168 + onTimerEvent: function() { 1.169 + if (!this.currentlyExecuting) { 1.170 + try { 1.171 + this.currentlyExecuting = true; 1.172 + this.callback(this); 1.173 + } finally { 1.174 + this.currentlyExecuting = false; 1.175 + } 1.176 + } 1.177 + } 1.178 +} 1.179 +String.interpret = function(value){ 1.180 + return value == null ? '' : String(value); 1.181 +} 1.182 + 1.183 +Object.extend(String.prototype, { 1.184 + gsub: function(pattern, replacement) { 1.185 + var result = '', source = this, match; 1.186 + replacement = arguments.callee.prepareReplacement(replacement); 1.187 + 1.188 + while (source.length > 0) { 1.189 + if (match = source.match(pattern)) { 1.190 + result += source.slice(0, match.index); 1.191 + result += String.interpret(replacement(match)); 1.192 + source = source.slice(match.index + match[0].length); 1.193 + } else { 1.194 + result += source, source = ''; 1.195 + } 1.196 + } 1.197 + return result; 1.198 + }, 1.199 + 1.200 + sub: function(pattern, replacement, count) { 1.201 + replacement = this.gsub.prepareReplacement(replacement); 1.202 + count = count === undefined ? 1 : count; 1.203 + 1.204 + return this.gsub(pattern, function(match) { 1.205 + if (--count < 0) return match[0]; 1.206 + return replacement(match); 1.207 + }); 1.208 + }, 1.209 + 1.210 + scan: function(pattern, iterator) { 1.211 + this.gsub(pattern, iterator); 1.212 + return this; 1.213 + }, 1.214 + 1.215 + truncate: function(length, truncation) { 1.216 + length = length || 30; 1.217 + truncation = truncation === undefined ? '...' : truncation; 1.218 + return this.length > length ? 1.219 + this.slice(0, length - truncation.length) + truncation : this; 1.220 + }, 1.221 + 1.222 + strip: function() { 1.223 + return this.replace(/^\s+/, '').replace(/\s+$/, ''); 1.224 + }, 1.225 + 1.226 + stripTags: function() { 1.227 + return this.replace(/<\/?[^>]+>/gi, ''); 1.228 + }, 1.229 + 1.230 + stripScripts: function() { 1.231 + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); 1.232 + }, 1.233 + 1.234 + extractScripts: function() { 1.235 + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); 1.236 + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); 1.237 + return (this.match(matchAll) || []).map(function(scriptTag) { 1.238 + return (scriptTag.match(matchOne) || ['', ''])[1]; 1.239 + }); 1.240 + }, 1.241 + 1.242 + evalScripts: function() { 1.243 + return this.extractScripts().map(function(script) { return eval(script) }); 1.244 + }, 1.245 + 1.246 + escapeHTML: function() { 1.247 + var div = document.createElement('div'); 1.248 + var text = document.createTextNode(this); 1.249 + div.appendChild(text); 1.250 + return div.innerHTML; 1.251 + }, 1.252 + 1.253 + unescapeHTML: function() { 1.254 + var div = document.createElement('div'); 1.255 + div.innerHTML = this.stripTags(); 1.256 + return div.childNodes[0] ? (div.childNodes.length > 1 ? 1.257 + $A(div.childNodes).inject('',function(memo,node){ return memo+node.nodeValue }) : 1.258 + div.childNodes[0].nodeValue) : ''; 1.259 + }, 1.260 + 1.261 + toQueryParams: function(separator) { 1.262 + var match = this.strip().match(/([^?#]*)(#.*)?$/); 1.263 + if (!match) return {}; 1.264 + 1.265 + return match[1].split(separator || '&').inject({}, function(hash, pair) { 1.266 + if ((pair = pair.split('='))[0]) { 1.267 + var name = decodeURIComponent(pair[0]); 1.268 + var value = pair[1] ? decodeURIComponent(pair[1]) : undefined; 1.269 + 1.270 + if (hash[name] !== undefined) { 1.271 + if (hash[name].constructor != Array) 1.272 + hash[name] = [hash[name]]; 1.273 + if (value) hash[name].push(value); 1.274 + } 1.275 + else hash[name] = value; 1.276 + } 1.277 + return hash; 1.278 + }); 1.279 + }, 1.280 + 1.281 + toArray: function() { 1.282 + return this.split(''); 1.283 + }, 1.284 + 1.285 + succ: function() { 1.286 + return this.slice(0, this.length - 1) + 1.287 + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); 1.288 + }, 1.289 + 1.290 + camelize: function() { 1.291 + var parts = this.split('-'), len = parts.length; 1.292 + if (len == 1) return parts[0]; 1.293 + 1.294 + var camelized = this.charAt(0) == '-' 1.295 + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) 1.296 + : parts[0]; 1.297 + 1.298 + for (var i = 1; i < len; i++) 1.299 + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); 1.300 + 1.301 + return camelized; 1.302 + }, 1.303 + 1.304 + capitalize: function(){ 1.305 + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); 1.306 + }, 1.307 + 1.308 + underscore: function() { 1.309 + return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); 1.310 + }, 1.311 + 1.312 + dasherize: function() { 1.313 + return this.gsub(/_/,'-'); 1.314 + }, 1.315 + 1.316 + inspect: function(useDoubleQuotes) { 1.317 + var escapedString = this.replace(/\\/g, '\\\\'); 1.318 + if (useDoubleQuotes) 1.319 + return '"' + escapedString.replace(/"/g, '\\"') + '"'; 1.320 + else 1.321 + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; 1.322 + } 1.323 +}); 1.324 + 1.325 +String.prototype.gsub.prepareReplacement = function(replacement) { 1.326 + if (typeof replacement == 'function') return replacement; 1.327 + var template = new Template(replacement); 1.328 + return function(match) { return template.evaluate(match) }; 1.329 +} 1.330 + 1.331 +String.prototype.parseQuery = String.prototype.toQueryParams; 1.332 + 1.333 +var Template = Class.create(); 1.334 +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; 1.335 +Template.prototype = { 1.336 + initialize: function(template, pattern) { 1.337 + this.template = template.toString(); 1.338 + this.pattern = pattern || Template.Pattern; 1.339 + }, 1.340 + 1.341 + evaluate: function(object) { 1.342 + return this.template.gsub(this.pattern, function(match) { 1.343 + var before = match[1]; 1.344 + if (before == '\\') return match[2]; 1.345 + return before + String.interpret(object[match[3]]); 1.346 + }); 1.347 + } 1.348 +} 1.349 + 1.350 +var $break = new Object(); 1.351 +var $continue = new Object(); 1.352 + 1.353 +var Enumerable = { 1.354 + each: function(iterator) { 1.355 + var index = 0; 1.356 + try { 1.357 + this._each(function(value) { 1.358 + try { 1.359 + iterator(value, index++); 1.360 + } catch (e) { 1.361 + if (e != $continue) throw e; 1.362 + } 1.363 + }); 1.364 + } catch (e) { 1.365 + if (e != $break) throw e; 1.366 + } 1.367 + return this; 1.368 + }, 1.369 + 1.370 + eachSlice: function(number, iterator) { 1.371 + var index = -number, slices = [], array = this.toArray(); 1.372 + while ((index += number) < array.length) 1.373 + slices.push(array.slice(index, index+number)); 1.374 + return slices.map(iterator); 1.375 + }, 1.376 + 1.377 + all: function(iterator) { 1.378 + var result = true; 1.379 + this.each(function(value, index) { 1.380 + result = result && !!(iterator || Prototype.K)(value, index); 1.381 + if (!result) throw $break; 1.382 + }); 1.383 + return result; 1.384 + }, 1.385 + 1.386 + any: function(iterator) { 1.387 + var result = false; 1.388 + this.each(function(value, index) { 1.389 + if (result = !!(iterator || Prototype.K)(value, index)) 1.390 + throw $break; 1.391 + }); 1.392 + return result; 1.393 + }, 1.394 + 1.395 + collect: function(iterator) { 1.396 + var results = []; 1.397 + this.each(function(value, index) { 1.398 + results.push((iterator || Prototype.K)(value, index)); 1.399 + }); 1.400 + return results; 1.401 + }, 1.402 + 1.403 + detect: function(iterator) { 1.404 + var result; 1.405 + this.each(function(value, index) { 1.406 + if (iterator(value, index)) { 1.407 + result = value; 1.408 + throw $break; 1.409 + } 1.410 + }); 1.411 + return result; 1.412 + }, 1.413 + 1.414 + findAll: function(iterator) { 1.415 + var results = []; 1.416 + this.each(function(value, index) { 1.417 + if (iterator(value, index)) 1.418 + results.push(value); 1.419 + }); 1.420 + return results; 1.421 + }, 1.422 + 1.423 + grep: function(pattern, iterator) { 1.424 + var results = []; 1.425 + this.each(function(value, index) { 1.426 + var stringValue = value.toString(); 1.427 + if (stringValue.match(pattern)) 1.428 + results.push((iterator || Prototype.K)(value, index)); 1.429 + }) 1.430 + return results; 1.431 + }, 1.432 + 1.433 + include: function(object) { 1.434 + var found = false; 1.435 + this.each(function(value) { 1.436 + if (value == object) { 1.437 + found = true; 1.438 + throw $break; 1.439 + } 1.440 + }); 1.441 + return found; 1.442 + }, 1.443 + 1.444 + inGroupsOf: function(number, fillWith) { 1.445 + fillWith = fillWith === undefined ? null : fillWith; 1.446 + return this.eachSlice(number, function(slice) { 1.447 + while(slice.length < number) slice.push(fillWith); 1.448 + return slice; 1.449 + }); 1.450 + }, 1.451 + 1.452 + inject: function(memo, iterator) { 1.453 + this.each(function(value, index) { 1.454 + memo = iterator(memo, value, index); 1.455 + }); 1.456 + return memo; 1.457 + }, 1.458 + 1.459 + invoke: function(method) { 1.460 + var args = $A(arguments).slice(1); 1.461 + return this.map(function(value) { 1.462 + return value[method].apply(value, args); 1.463 + }); 1.464 + }, 1.465 + 1.466 + max: function(iterator) { 1.467 + var result; 1.468 + this.each(function(value, index) { 1.469 + value = (iterator || Prototype.K)(value, index); 1.470 + if (result == undefined || value >= result) 1.471 + result = value; 1.472 + }); 1.473 + return result; 1.474 + }, 1.475 + 1.476 + min: function(iterator) { 1.477 + var result; 1.478 + this.each(function(value, index) { 1.479 + value = (iterator || Prototype.K)(value, index); 1.480 + if (result == undefined || value < result) 1.481 + result = value; 1.482 + }); 1.483 + return result; 1.484 + }, 1.485 + 1.486 + partition: function(iterator) { 1.487 + var trues = [], falses = []; 1.488 + this.each(function(value, index) { 1.489 + ((iterator || Prototype.K)(value, index) ? 1.490 + trues : falses).push(value); 1.491 + }); 1.492 + return [trues, falses]; 1.493 + }, 1.494 + 1.495 + pluck: function(property) { 1.496 + var results = []; 1.497 + this.each(function(value, index) { 1.498 + results.push(value[property]); 1.499 + }); 1.500 + return results; 1.501 + }, 1.502 + 1.503 + reject: function(iterator) { 1.504 + var results = []; 1.505 + this.each(function(value, index) { 1.506 + if (!iterator(value, index)) 1.507 + results.push(value); 1.508 + }); 1.509 + return results; 1.510 + }, 1.511 + 1.512 + sortBy: function(iterator) { 1.513 + return this.map(function(value, index) { 1.514 + return {value: value, criteria: iterator(value, index)}; 1.515 + }).sort(function(left, right) { 1.516 + var a = left.criteria, b = right.criteria; 1.517 + return a < b ? -1 : a > b ? 1 : 0; 1.518 + }).pluck('value'); 1.519 + }, 1.520 + 1.521 + toArray: function() { 1.522 + return this.map(); 1.523 + }, 1.524 + 1.525 + zip: function() { 1.526 + var iterator = Prototype.K, args = $A(arguments); 1.527 + if (typeof args.last() == 'function') 1.528 + iterator = args.pop(); 1.529 + 1.530 + var collections = [this].concat(args).map($A); 1.531 + return this.map(function(value, index) { 1.532 + return iterator(collections.pluck(index)); 1.533 + }); 1.534 + }, 1.535 + 1.536 + size: function() { 1.537 + return this.toArray().length; 1.538 + }, 1.539 + 1.540 + inspect: function() { 1.541 + return '#<Enumerable:' + this.toArray().inspect() + '>'; 1.542 + } 1.543 +} 1.544 + 1.545 +Object.extend(Enumerable, { 1.546 + map: Enumerable.collect, 1.547 + find: Enumerable.detect, 1.548 + select: Enumerable.findAll, 1.549 + member: Enumerable.include, 1.550 + entries: Enumerable.toArray 1.551 +}); 1.552 +var $A = Array.from = function(iterable) { 1.553 + if (!iterable) return []; 1.554 + if (iterable.toArray) { 1.555 + return iterable.toArray(); 1.556 + } else { 1.557 + var results = []; 1.558 + for (var i = 0, length = iterable.length; i < length; i++) 1.559 + results.push(iterable[i]); 1.560 + return results; 1.561 + } 1.562 +} 1.563 + 1.564 +Object.extend(Array.prototype, Enumerable); 1.565 + 1.566 +if (!Array.prototype._reverse) 1.567 + Array.prototype._reverse = Array.prototype.reverse; 1.568 + 1.569 +Object.extend(Array.prototype, { 1.570 + _each: function(iterator) { 1.571 + for (var i = 0, length = this.length; i < length; i++) 1.572 + iterator(this[i]); 1.573 + }, 1.574 + 1.575 + clear: function() { 1.576 + this.length = 0; 1.577 + return this; 1.578 + }, 1.579 + 1.580 + first: function() { 1.581 + return this[0]; 1.582 + }, 1.583 + 1.584 + last: function() { 1.585 + return this[this.length - 1]; 1.586 + }, 1.587 + 1.588 + compact: function() { 1.589 + return this.select(function(value) { 1.590 + return value != null; 1.591 + }); 1.592 + }, 1.593 + 1.594 + flatten: function() { 1.595 + return this.inject([], function(array, value) { 1.596 + return array.concat(value && value.constructor == Array ? 1.597 + value.flatten() : [value]); 1.598 + }); 1.599 + }, 1.600 + 1.601 + without: function() { 1.602 + var values = $A(arguments); 1.603 + return this.select(function(value) { 1.604 + return !values.include(value); 1.605 + }); 1.606 + }, 1.607 + 1.608 + indexOf: function(object) { 1.609 + for (var i = 0, length = this.length; i < length; i++) 1.610 + if (this[i] == object) return i; 1.611 + return -1; 1.612 + }, 1.613 + 1.614 + reverse: function(inline) { 1.615 + return (inline !== false ? this : this.toArray())._reverse(); 1.616 + }, 1.617 + 1.618 + reduce: function() { 1.619 + return this.length > 1 ? this : this[0]; 1.620 + }, 1.621 + 1.622 + uniq: function() { 1.623 + return this.inject([], function(array, value) { 1.624 + return array.include(value) ? array : array.concat([value]); 1.625 + }); 1.626 + }, 1.627 + 1.628 + clone: function() { 1.629 + return [].concat(this); 1.630 + }, 1.631 + 1.632 + size: function() { 1.633 + return this.length; 1.634 + }, 1.635 + 1.636 + inspect: function() { 1.637 + return '[' + this.map(Object.inspect).join(', ') + ']'; 1.638 + } 1.639 +}); 1.640 + 1.641 +Array.prototype.toArray = Array.prototype.clone; 1.642 + 1.643 +function $w(string){ 1.644 + string = string.strip(); 1.645 + return string ? string.split(/\s+/) : []; 1.646 +} 1.647 + 1.648 +if(window.opera){ 1.649 + Array.prototype.concat = function(){ 1.650 + var array = []; 1.651 + for(var i = 0, length = this.length; i < length; i++) array.push(this[i]); 1.652 + for(var i = 0, length = arguments.length; i < length; i++) { 1.653 + if(arguments[i].constructor == Array) { 1.654 + for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) 1.655 + array.push(arguments[i][j]); 1.656 + } else { 1.657 + array.push(arguments[i]); 1.658 + } 1.659 + } 1.660 + return array; 1.661 + } 1.662 +} 1.663 +var Hash = function(obj) { 1.664 + Object.extend(this, obj || {}); 1.665 +}; 1.666 + 1.667 +Object.extend(Hash, { 1.668 + toQueryString: function(obj) { 1.669 + var parts = []; 1.670 + 1.671 + this.prototype._each.call(obj, function(pair) { 1.672 + if (!pair.key) return; 1.673 + 1.674 + if (pair.value && pair.value.constructor == Array) { 1.675 + var values = pair.value.compact(); 1.676 + if (values.length < 2) pair.value = values.reduce(); 1.677 + else { 1.678 + key = encodeURIComponent(pair.key); 1.679 + values.each(function(value) { 1.680 + value = value != undefined ? encodeURIComponent(value) : ''; 1.681 + parts.push(key + '=' + encodeURIComponent(value)); 1.682 + }); 1.683 + return; 1.684 + } 1.685 + } 1.686 + if (pair.value == undefined) pair[1] = ''; 1.687 + parts.push(pair.map(encodeURIComponent).join('=')); 1.688 + }); 1.689 + 1.690 + return parts.join('&'); 1.691 + } 1.692 +}); 1.693 + 1.694 +Object.extend(Hash.prototype, Enumerable); 1.695 +Object.extend(Hash.prototype, { 1.696 + _each: function(iterator) { 1.697 + for (var key in this) { 1.698 + var value = this[key]; 1.699 + if (value && value == Hash.prototype[key]) continue; 1.700 + 1.701 + var pair = [key, value]; 1.702 + pair.key = key; 1.703 + pair.value = value; 1.704 + iterator(pair); 1.705 + } 1.706 + }, 1.707 + 1.708 + keys: function() { 1.709 + return this.pluck('key'); 1.710 + }, 1.711 + 1.712 + values: function() { 1.713 + return this.pluck('value'); 1.714 + }, 1.715 + 1.716 + merge: function(hash) { 1.717 + return $H(hash).inject(this, function(mergedHash, pair) { 1.718 + mergedHash[pair.key] = pair.value; 1.719 + return mergedHash; 1.720 + }); 1.721 + }, 1.722 + 1.723 + remove: function() { 1.724 + var result; 1.725 + for(var i = 0, length = arguments.length; i < length; i++) { 1.726 + var value = this[arguments[i]]; 1.727 + if (value !== undefined){ 1.728 + if (result === undefined) result = value; 1.729 + else { 1.730 + if (result.constructor != Array) result = [result]; 1.731 + result.push(value) 1.732 + } 1.733 + } 1.734 + delete this[arguments[i]]; 1.735 + } 1.736 + return result; 1.737 + }, 1.738 + 1.739 + toQueryString: function() { 1.740 + return Hash.toQueryString(this); 1.741 + }, 1.742 + 1.743 + inspect: function() { 1.744 + return '#<Hash:{' + this.map(function(pair) { 1.745 + return pair.map(Object.inspect).join(': '); 1.746 + }).join(', ') + '}>'; 1.747 + } 1.748 +}); 1.749 + 1.750 +function $H(object) { 1.751 + if (object && object.constructor == Hash) return object; 1.752 + return new Hash(object); 1.753 +}; 1.754 +ObjectRange = Class.create(); 1.755 +Object.extend(ObjectRange.prototype, Enumerable); 1.756 +Object.extend(ObjectRange.prototype, { 1.757 + initialize: function(start, end, exclusive) { 1.758 + this.start = start; 1.759 + this.end = end; 1.760 + this.exclusive = exclusive; 1.761 + }, 1.762 + 1.763 + _each: function(iterator) { 1.764 + var value = this.start; 1.765 + while (this.include(value)) { 1.766 + iterator(value); 1.767 + value = value.succ(); 1.768 + } 1.769 + }, 1.770 + 1.771 + include: function(value) { 1.772 + if (value < this.start) 1.773 + return false; 1.774 + if (this.exclusive) 1.775 + return value < this.end; 1.776 + return value <= this.end; 1.777 + } 1.778 +}); 1.779 + 1.780 +var $R = function(start, end, exclusive) { 1.781 + return new ObjectRange(start, end, exclusive); 1.782 +} 1.783 + 1.784 +var Ajax = { 1.785 + getTransport: function() { 1.786 + return Try.these( 1.787 + function() {return new XMLHttpRequest()}, 1.788 + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, 1.789 + function() {return new ActiveXObject('Microsoft.XMLHTTP')} 1.790 + ) || false; 1.791 + }, 1.792 + 1.793 + activeRequestCount: 0 1.794 +} 1.795 + 1.796 +Ajax.Responders = { 1.797 + responders: [], 1.798 + 1.799 + _each: function(iterator) { 1.800 + this.responders._each(iterator); 1.801 + }, 1.802 + 1.803 + register: function(responder) { 1.804 + if (!this.include(responder)) 1.805 + this.responders.push(responder); 1.806 + }, 1.807 + 1.808 + unregister: function(responder) { 1.809 + this.responders = this.responders.without(responder); 1.810 + }, 1.811 + 1.812 + dispatch: function(callback, request, transport, json) { 1.813 + this.each(function(responder) { 1.814 + if (typeof responder[callback] == 'function') { 1.815 + try { 1.816 + responder[callback].apply(responder, [request, transport, json]); 1.817 + } catch (e) {} 1.818 + } 1.819 + }); 1.820 + } 1.821 +}; 1.822 + 1.823 +Object.extend(Ajax.Responders, Enumerable); 1.824 + 1.825 +Ajax.Responders.register({ 1.826 + onCreate: function() { 1.827 + Ajax.activeRequestCount++; 1.828 + }, 1.829 + onComplete: function() { 1.830 + Ajax.activeRequestCount--; 1.831 + } 1.832 +}); 1.833 + 1.834 +Ajax.Base = function() {}; 1.835 +Ajax.Base.prototype = { 1.836 + setOptions: function(options) { 1.837 + this.options = { 1.838 + method: 'post', 1.839 + asynchronous: true, 1.840 + contentType: 'application/x-www-form-urlencoded', 1.841 + encoding: 'UTF-8', 1.842 + parameters: '' 1.843 + } 1.844 + Object.extend(this.options, options || {}); 1.845 + 1.846 + this.options.method = this.options.method.toLowerCase(); 1.847 + if (typeof this.options.parameters == 'string') 1.848 + this.options.parameters = this.options.parameters.toQueryParams(); 1.849 + } 1.850 +} 1.851 + 1.852 +Ajax.Request = Class.create(); 1.853 +Ajax.Request.Events = 1.854 + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; 1.855 + 1.856 +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { 1.857 + _complete: false, 1.858 + 1.859 + initialize: function(url, options) { 1.860 + this.transport = Ajax.getTransport(); 1.861 + this.setOptions(options); 1.862 + this.request(url); 1.863 + }, 1.864 + 1.865 + request: function(url) { 1.866 + this.url = url; 1.867 + this.method = this.options.method; 1.868 + var params = this.options.parameters; 1.869 + 1.870 + if (!['get', 'post'].include(this.method)) { 1.871 + // simulate other verbs over post 1.872 + params['_method'] = this.method; 1.873 + this.method = 'post'; 1.874 + } 1.875 + 1.876 + params = Hash.toQueryString(params); 1.877 + if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_=' 1.878 + 1.879 + // when GET, append parameters to URL 1.880 + if (this.method == 'get' && params) 1.881 + this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params; 1.882 + 1.883 + try { 1.884 + Ajax.Responders.dispatch('onCreate', this, this.transport); 1.885 + 1.886 + this.transport.open(this.method.toUpperCase(), this.url, 1.887 + this.options.asynchronous); 1.888 + 1.889 + if (this.options.asynchronous) 1.890 + setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); 1.891 + 1.892 + this.transport.onreadystatechange = this.onStateChange.bind(this); 1.893 + this.setRequestHeaders(); 1.894 + 1.895 + var body = this.method == 'post' ? (this.options.postBody || params) : null; 1.896 + 1.897 + this.transport.send(body); 1.898 + 1.899 + /* Force Firefox to handle ready state 4 for synchronous requests */ 1.900 + if (!this.options.asynchronous && this.transport.overrideMimeType) 1.901 + this.onStateChange(); 1.902 + 1.903 + } 1.904 + catch (e) { 1.905 + this.dispatchException(e); 1.906 + } 1.907 + }, 1.908 + 1.909 + onStateChange: function() { 1.910 + var readyState = this.transport.readyState; 1.911 + if (readyState > 1 && !((readyState == 4) && this._complete)) 1.912 + this.respondToReadyState(this.transport.readyState); 1.913 + }, 1.914 + 1.915 + setRequestHeaders: function() { 1.916 + var headers = { 1.917 + 'X-Requested-With': 'XMLHttpRequest', 1.918 + 'X-Prototype-Version': Prototype.Version, 1.919 + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' 1.920 + }; 1.921 + 1.922 + if (this.method == 'post') { 1.923 + headers['Content-type'] = this.options.contentType + 1.924 + (this.options.encoding ? '; charset=' + this.options.encoding : ''); 1.925 + 1.926 + /* Force "Connection: close" for older Mozilla browsers to work 1.927 + * around a bug where XMLHttpRequest sends an incorrect 1.928 + * Content-length header. See Mozilla Bugzilla #246651. 1.929 + */ 1.930 + if (this.transport.overrideMimeType && 1.931 + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) 1.932 + headers['Connection'] = 'close'; 1.933 + } 1.934 + 1.935 + // user-defined headers 1.936 + if (typeof this.options.requestHeaders == 'object') { 1.937 + var extras = this.options.requestHeaders; 1.938 + 1.939 + if (typeof extras.push == 'function') 1.940 + for (var i = 0, length = extras.length; i < length; i += 2) 1.941 + headers[extras[i]] = extras[i+1]; 1.942 + else 1.943 + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); 1.944 + } 1.945 + 1.946 + for (var name in headers) 1.947 + this.transport.setRequestHeader(name, headers[name]); 1.948 + }, 1.949 + 1.950 + success: function() { 1.951 + return !this.transport.status 1.952 + || (this.transport.status >= 200 && this.transport.status < 300); 1.953 + }, 1.954 + 1.955 + respondToReadyState: function(readyState) { 1.956 + var state = Ajax.Request.Events[readyState]; 1.957 + var transport = this.transport, json = this.evalJSON(); 1.958 + 1.959 + if (state == 'Complete') { 1.960 + try { 1.961 + this._complete = true; 1.962 + (this.options['on' + this.transport.status] 1.963 + || this.options['on' + (this.success() ? 'Success' : 'Failure')] 1.964 + || Prototype.emptyFunction)(transport, json); 1.965 + } catch (e) { 1.966 + this.dispatchException(e); 1.967 + } 1.968 + 1.969 + if ((this.getHeader('Content-type') || 'text/javascript').strip(). 1.970 + match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) 1.971 + this.evalResponse(); 1.972 + } 1.973 + 1.974 + try { 1.975 + (this.options['on' + state] || Prototype.emptyFunction)(transport, json); 1.976 + Ajax.Responders.dispatch('on' + state, this, transport, json); 1.977 + } catch (e) { 1.978 + this.dispatchException(e); 1.979 + } 1.980 + 1.981 + if (state == 'Complete') { 1.982 + // avoid memory leak in MSIE: clean up 1.983 + this.transport.onreadystatechange = Prototype.emptyFunction; 1.984 + } 1.985 + }, 1.986 + 1.987 + getHeader: function(name) { 1.988 + try { 1.989 + return this.transport.getResponseHeader(name); 1.990 + } catch (e) { return null } 1.991 + }, 1.992 + 1.993 + evalJSON: function() { 1.994 + try { 1.995 + var json = this.getHeader('X-JSON'); 1.996 + return json ? eval('(' + json + ')') : null; 1.997 + } catch (e) { return null } 1.998 + }, 1.999 + 1.1000 + evalResponse: function() { 1.1001 + try { 1.1002 + return eval(this.transport.responseText); 1.1003 + } catch (e) { 1.1004 + this.dispatchException(e); 1.1005 + } 1.1006 + }, 1.1007 + 1.1008 + dispatchException: function(exception) { 1.1009 + (this.options.onException || Prototype.emptyFunction)(this, exception); 1.1010 + Ajax.Responders.dispatch('onException', this, exception); 1.1011 + } 1.1012 +}); 1.1013 + 1.1014 +Ajax.Updater = Class.create(); 1.1015 + 1.1016 +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { 1.1017 + initialize: function(container, url, options) { 1.1018 + this.container = { 1.1019 + success: (container.success || container), 1.1020 + failure: (container.failure || (container.success ? null : container)) 1.1021 + } 1.1022 + 1.1023 + this.transport = Ajax.getTransport(); 1.1024 + this.setOptions(options); 1.1025 + 1.1026 + var onComplete = this.options.onComplete || Prototype.emptyFunction; 1.1027 + this.options.onComplete = (function(transport, param) { 1.1028 + this.updateContent(); 1.1029 + onComplete(transport, param); 1.1030 + }).bind(this); 1.1031 + 1.1032 + this.request(url); 1.1033 + }, 1.1034 + 1.1035 + updateContent: function() { 1.1036 + var receiver = this.container[this.success() ? 'success' : 'failure']; 1.1037 + var response = this.transport.responseText; 1.1038 + 1.1039 + if (!this.options.evalScripts) response = response.stripScripts(); 1.1040 + 1.1041 + if (receiver = $(receiver)) { 1.1042 + if (this.options.insertion) 1.1043 + new this.options.insertion(receiver, response); 1.1044 + else 1.1045 + receiver.update(response); 1.1046 + } 1.1047 + 1.1048 + if (this.success()) { 1.1049 + if (this.onComplete) 1.1050 + setTimeout(this.onComplete.bind(this), 10); 1.1051 + } 1.1052 + } 1.1053 +}); 1.1054 + 1.1055 +Ajax.PeriodicalUpdater = Class.create(); 1.1056 +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { 1.1057 + initialize: function(container, url, options) { 1.1058 + this.setOptions(options); 1.1059 + this.onComplete = this.options.onComplete; 1.1060 + 1.1061 + this.frequency = (this.options.frequency || 2); 1.1062 + this.decay = (this.options.decay || 1); 1.1063 + 1.1064 + this.updater = {}; 1.1065 + this.container = container; 1.1066 + this.url = url; 1.1067 + 1.1068 + this.start(); 1.1069 + }, 1.1070 + 1.1071 + start: function() { 1.1072 + this.options.onComplete = this.updateComplete.bind(this); 1.1073 + this.onTimerEvent(); 1.1074 + }, 1.1075 + 1.1076 + stop: function() { 1.1077 + this.updater.options.onComplete = undefined; 1.1078 + clearTimeout(this.timer); 1.1079 + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); 1.1080 + }, 1.1081 + 1.1082 + updateComplete: function(request) { 1.1083 + if (this.options.decay) { 1.1084 + this.decay = (request.responseText == this.lastText ? 1.1085 + this.decay * this.options.decay : 1); 1.1086 + 1.1087 + this.lastText = request.responseText; 1.1088 + } 1.1089 + this.timer = setTimeout(this.onTimerEvent.bind(this), 1.1090 + this.decay * this.frequency * 1000); 1.1091 + }, 1.1092 + 1.1093 + onTimerEvent: function() { 1.1094 + this.updater = new Ajax.Updater(this.container, this.url, this.options); 1.1095 + } 1.1096 +}); 1.1097 +function $(element) { 1.1098 + if (arguments.length > 1) { 1.1099 + for (var i = 0, elements = [], length = arguments.length; i < length; i++) 1.1100 + elements.push($(arguments[i])); 1.1101 + return elements; 1.1102 + } 1.1103 + if (typeof element == 'string') 1.1104 + element = document.getElementById(element); 1.1105 + return Element.extend(element); 1.1106 +} 1.1107 + 1.1108 +if (Prototype.BrowserFeatures.XPath) { 1.1109 + document._getElementsByXPath = function(expression, parentElement) { 1.1110 + var results = []; 1.1111 + var query = document.evaluate(expression, $(parentElement) || document, 1.1112 + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); 1.1113 + for (var i = 0, length = query.snapshotLength; i < length; i++) 1.1114 + results.push(query.snapshotItem(i)); 1.1115 + return results; 1.1116 + }; 1.1117 +} 1.1118 + 1.1119 +document.getElementsByClassName = function(className, parentElement) { 1.1120 + if (Prototype.BrowserFeatures.XPath) { 1.1121 + var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; 1.1122 + return document._getElementsByXPath(q, parentElement); 1.1123 + } else { 1.1124 + var children = ($(parentElement) || document.body).getElementsByTagName('*'); 1.1125 + var elements = [], child; 1.1126 + for (var i = 0, length = children.length; i < length; i++) { 1.1127 + child = children[i]; 1.1128 + if (Element.hasClassName(child, className)) 1.1129 + elements.push(Element.extend(child)); 1.1130 + } 1.1131 + return elements; 1.1132 + } 1.1133 +}; 1.1134 + 1.1135 +/*--------------------------------------------------------------------------*/ 1.1136 + 1.1137 +if (!window.Element) 1.1138 + var Element = new Object(); 1.1139 + 1.1140 +Element.extend = function(element) { 1.1141 + if (!element || _nativeExtensions || element.nodeType == 3) return element; 1.1142 + 1.1143 + if (!element._extended && element.tagName && element != window) { 1.1144 + var methods = Object.clone(Element.Methods), cache = Element.extend.cache; 1.1145 + 1.1146 + if (element.tagName == 'FORM') 1.1147 + Object.extend(methods, Form.Methods); 1.1148 + if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName)) 1.1149 + Object.extend(methods, Form.Element.Methods); 1.1150 + 1.1151 + Object.extend(methods, Element.Methods.Simulated); 1.1152 + 1.1153 + for (var property in methods) { 1.1154 + var value = methods[property]; 1.1155 + if (typeof value == 'function' && !(property in element)) 1.1156 + element[property] = cache.findOrStore(value); 1.1157 + } 1.1158 + } 1.1159 + 1.1160 + element._extended = true; 1.1161 + return element; 1.1162 +}; 1.1163 + 1.1164 +Element.extend.cache = { 1.1165 + findOrStore: function(value) { 1.1166 + return this[value] = this[value] || function() { 1.1167 + return value.apply(null, [this].concat($A(arguments))); 1.1168 + } 1.1169 + } 1.1170 +}; 1.1171 + 1.1172 +Element.Methods = { 1.1173 + visible: function(element) { 1.1174 + return $(element).style.display != 'none'; 1.1175 + }, 1.1176 + 1.1177 + toggle: function(element) { 1.1178 + element = $(element); 1.1179 + Element[Element.visible(element) ? 'hide' : 'show'](element); 1.1180 + return element; 1.1181 + }, 1.1182 + 1.1183 + hide: function(element) { 1.1184 + $(element).style.display = 'none'; 1.1185 + return element; 1.1186 + }, 1.1187 + 1.1188 + show: function(element) { 1.1189 + $(element).style.display = ''; 1.1190 + return element; 1.1191 + }, 1.1192 + 1.1193 + remove: function(element) { 1.1194 + element = $(element); 1.1195 + element.parentNode.removeChild(element); 1.1196 + return element; 1.1197 + }, 1.1198 + 1.1199 + update: function(element, html) { 1.1200 + html = typeof html == 'undefined' ? '' : html.toString(); 1.1201 + $(element).innerHTML = html.stripScripts(); 1.1202 + setTimeout(function() {html.evalScripts()}, 10); 1.1203 + return element; 1.1204 + }, 1.1205 + 1.1206 + replace: function(element, html) { 1.1207 + element = $(element); 1.1208 + html = typeof html == 'undefined' ? '' : html.toString(); 1.1209 + if (element.outerHTML) { 1.1210 + element.outerHTML = html.stripScripts(); 1.1211 + } else { 1.1212 + var range = element.ownerDocument.createRange(); 1.1213 + range.selectNodeContents(element); 1.1214 + element.parentNode.replaceChild( 1.1215 + range.createContextualFragment(html.stripScripts()), element); 1.1216 + } 1.1217 + setTimeout(function() {html.evalScripts()}, 10); 1.1218 + return element; 1.1219 + }, 1.1220 + 1.1221 + inspect: function(element) { 1.1222 + element = $(element); 1.1223 + var result = '<' + element.tagName.toLowerCase(); 1.1224 + $H({'id': 'id', 'className': 'class'}).each(function(pair) { 1.1225 + var property = pair.first(), attribute = pair.last(); 1.1226 + var value = (element[property] || '').toString(); 1.1227 + if (value) result += ' ' + attribute + '=' + value.inspect(true); 1.1228 + }); 1.1229 + return result + '>'; 1.1230 + }, 1.1231 + 1.1232 + recursivelyCollect: function(element, property) { 1.1233 + element = $(element); 1.1234 + var elements = []; 1.1235 + while (element = element[property]) 1.1236 + if (element.nodeType == 1) 1.1237 + elements.push(Element.extend(element)); 1.1238 + return elements; 1.1239 + }, 1.1240 + 1.1241 + ancestors: function(element) { 1.1242 + return $(element).recursivelyCollect('parentNode'); 1.1243 + }, 1.1244 + 1.1245 + descendants: function(element) { 1.1246 + return $A($(element).getElementsByTagName('*')); 1.1247 + }, 1.1248 + 1.1249 + immediateDescendants: function(element) { 1.1250 + if (!(element = $(element).firstChild)) return []; 1.1251 + while (element && element.nodeType != 1) element = element.nextSibling; 1.1252 + if (element) return [element].concat($(element).nextSiblings()); 1.1253 + return []; 1.1254 + }, 1.1255 + 1.1256 + previousSiblings: function(element) { 1.1257 + return $(element).recursivelyCollect('previousSibling'); 1.1258 + }, 1.1259 + 1.1260 + nextSiblings: function(element) { 1.1261 + return $(element).recursivelyCollect('nextSibling'); 1.1262 + }, 1.1263 + 1.1264 + siblings: function(element) { 1.1265 + element = $(element); 1.1266 + return element.previousSiblings().reverse().concat(element.nextSiblings()); 1.1267 + }, 1.1268 + 1.1269 + match: function(element, selector) { 1.1270 + if (typeof selector == 'string') 1.1271 + selector = new Selector(selector); 1.1272 + return selector.match($(element)); 1.1273 + }, 1.1274 + 1.1275 + up: function(element, expression, index) { 1.1276 + return Selector.findElement($(element).ancestors(), expression, index); 1.1277 + }, 1.1278 + 1.1279 + down: function(element, expression, index) { 1.1280 + return Selector.findElement($(element).descendants(), expression, index); 1.1281 + }, 1.1282 + 1.1283 + previous: function(element, expression, index) { 1.1284 + return Selector.findElement($(element).previousSiblings(), expression, index); 1.1285 + }, 1.1286 + 1.1287 + next: function(element, expression, index) { 1.1288 + return Selector.findElement($(element).nextSiblings(), expression, index); 1.1289 + }, 1.1290 + 1.1291 + getElementsBySelector: function() { 1.1292 + var args = $A(arguments), element = $(args.shift()); 1.1293 + return Selector.findChildElements(element, args); 1.1294 + }, 1.1295 + 1.1296 + getElementsByClassName: function(element, className) { 1.1297 + return document.getElementsByClassName(className, element); 1.1298 + }, 1.1299 + 1.1300 + readAttribute: function(element, name) { 1.1301 + element = $(element); 1.1302 + if (document.all && !window.opera) { 1.1303 + var t = Element._attributeTranslations; 1.1304 + if (t.values[name]) return t.values[name](element, name); 1.1305 + if (t.names[name]) name = t.names[name]; 1.1306 + var attribute = element.attributes[name]; 1.1307 + if(attribute) return attribute.nodeValue; 1.1308 + } 1.1309 + return element.getAttribute(name); 1.1310 + }, 1.1311 + 1.1312 + getHeight: function(element) { 1.1313 + return $(element).getDimensions().height; 1.1314 + }, 1.1315 + 1.1316 + getWidth: function(element) { 1.1317 + return $(element).getDimensions().width; 1.1318 + }, 1.1319 + 1.1320 + classNames: function(element) { 1.1321 + return new Element.ClassNames(element); 1.1322 + }, 1.1323 + 1.1324 + hasClassName: function(element, className) { 1.1325 + if (!(element = $(element))) return; 1.1326 + var elementClassName = element.className; 1.1327 + if (elementClassName.length == 0) return false; 1.1328 + if (elementClassName == className || 1.1329 + elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) 1.1330 + return true; 1.1331 + return false; 1.1332 + }, 1.1333 + 1.1334 + addClassName: function(element, className) { 1.1335 + if (!(element = $(element))) return; 1.1336 + Element.classNames(element).add(className); 1.1337 + return element; 1.1338 + }, 1.1339 + 1.1340 + removeClassName: function(element, className) { 1.1341 + if (!(element = $(element))) return; 1.1342 + Element.classNames(element).remove(className); 1.1343 + return element; 1.1344 + }, 1.1345 + 1.1346 + toggleClassName: function(element, className) { 1.1347 + if (!(element = $(element))) return; 1.1348 + Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); 1.1349 + return element; 1.1350 + }, 1.1351 + 1.1352 + observe: function() { 1.1353 + Event.observe.apply(Event, arguments); 1.1354 + return $A(arguments).first(); 1.1355 + }, 1.1356 + 1.1357 + stopObserving: function() { 1.1358 + Event.stopObserving.apply(Event, arguments); 1.1359 + return $A(arguments).first(); 1.1360 + }, 1.1361 + 1.1362 + // removes whitespace-only text node children 1.1363 + cleanWhitespace: function(element) { 1.1364 + element = $(element); 1.1365 + var node = element.firstChild; 1.1366 + while (node) { 1.1367 + var nextNode = node.nextSibling; 1.1368 + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) 1.1369 + element.removeChild(node); 1.1370 + node = nextNode; 1.1371 + } 1.1372 + return element; 1.1373 + }, 1.1374 + 1.1375 + empty: function(element) { 1.1376 + return $(element).innerHTML.match(/^\s*$/); 1.1377 + }, 1.1378 + 1.1379 + descendantOf: function(element, ancestor) { 1.1380 + element = $(element), ancestor = $(ancestor); 1.1381 + while (element = element.parentNode) 1.1382 + if (element == ancestor) return true; 1.1383 + return false; 1.1384 + }, 1.1385 + 1.1386 + scrollTo: function(element) { 1.1387 + element = $(element); 1.1388 + var pos = Position.cumulativeOffset(element); 1.1389 + window.scrollTo(pos[0], pos[1]); 1.1390 + return element; 1.1391 + }, 1.1392 + 1.1393 + getStyle: function(element, style) { 1.1394 + element = $(element); 1.1395 + if (['float','cssFloat'].include(style)) 1.1396 + style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat'); 1.1397 + style = style.camelize(); 1.1398 + var value = element.style[style]; 1.1399 + if (!value) { 1.1400 + if (document.defaultView && document.defaultView.getComputedStyle) { 1.1401 + var css = document.defaultView.getComputedStyle(element, null); 1.1402 + value = css ? css[style] : null; 1.1403 + } else if (element.currentStyle) { 1.1404 + value = element.currentStyle[style]; 1.1405 + } 1.1406 + } 1.1407 + 1.1408 + if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none')) 1.1409 + value = element['offset'+style.capitalize()] + 'px'; 1.1410 + 1.1411 + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) 1.1412 + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; 1.1413 + if(style == 'opacity') { 1.1414 + if(value) return parseFloat(value); 1.1415 + if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) 1.1416 + if(value[1]) return parseFloat(value[1]) / 100; 1.1417 + return 1.0; 1.1418 + } 1.1419 + return value == 'auto' ? null : value; 1.1420 + }, 1.1421 + 1.1422 + setStyle: function(element, style) { 1.1423 + element = $(element); 1.1424 + for (var name in style) { 1.1425 + var value = style[name]; 1.1426 + if(name == 'opacity') { 1.1427 + if (value == 1) { 1.1428 + value = (/Gecko/.test(navigator.userAgent) && 1.1429 + !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0; 1.1430 + if(/MSIE/.test(navigator.userAgent) && !window.opera) 1.1431 + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); 1.1432 + } else if(value == '') { 1.1433 + if(/MSIE/.test(navigator.userAgent) && !window.opera) 1.1434 + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); 1.1435 + } else { 1.1436 + if(value < 0.00001) value = 0; 1.1437 + if(/MSIE/.test(navigator.userAgent) && !window.opera) 1.1438 + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') + 1.1439 + 'alpha(opacity='+value*100+')'; 1.1440 + } 1.1441 + } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat'; 1.1442 + element.style[name.camelize()] = value; 1.1443 + } 1.1444 + return element; 1.1445 + }, 1.1446 + 1.1447 + getDimensions: function(element) { 1.1448 + element = $(element); 1.1449 + var display = $(element).getStyle('display'); 1.1450 + if (display != 'none' && display != null) // Safari bug 1.1451 + return {width: element.offsetWidth, height: element.offsetHeight}; 1.1452 + 1.1453 + // All *Width and *Height properties give 0 on elements with display none, 1.1454 + // so enable the element temporarily 1.1455 + var els = element.style; 1.1456 + var originalVisibility = els.visibility; 1.1457 + var originalPosition = els.position; 1.1458 + var originalDisplay = els.display; 1.1459 + els.visibility = 'hidden'; 1.1460 + els.position = 'absolute'; 1.1461 + els.display = 'block'; 1.1462 + var originalWidth = element.clientWidth; 1.1463 + var originalHeight = element.clientHeight; 1.1464 + els.display = originalDisplay; 1.1465 + els.position = originalPosition; 1.1466 + els.visibility = originalVisibility; 1.1467 + return {width: originalWidth, height: originalHeight}; 1.1468 + }, 1.1469 + 1.1470 + makePositioned: function(element) { 1.1471 + element = $(element); 1.1472 + var pos = Element.getStyle(element, 'position'); 1.1473 + if (pos == 'static' || !pos) { 1.1474 + element._madePositioned = true; 1.1475 + element.style.position = 'relative'; 1.1476 + // Opera returns the offset relative to the positioning context, when an 1.1477 + // element is position relative but top and left have not been defined 1.1478 + if (window.opera) { 1.1479 + element.style.top = 0; 1.1480 + element.style.left = 0; 1.1481 + } 1.1482 + } 1.1483 + return element; 1.1484 + }, 1.1485 + 1.1486 + undoPositioned: function(element) { 1.1487 + element = $(element); 1.1488 + if (element._madePositioned) { 1.1489 + element._madePositioned = undefined; 1.1490 + element.style.position = 1.1491 + element.style.top = 1.1492 + element.style.left = 1.1493 + element.style.bottom = 1.1494 + element.style.right = ''; 1.1495 + } 1.1496 + return element; 1.1497 + }, 1.1498 + 1.1499 + makeClipping: function(element) { 1.1500 + element = $(element); 1.1501 + if (element._overflow) return element; 1.1502 + element._overflow = element.style.overflow || 'auto'; 1.1503 + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') 1.1504 + element.style.overflow = 'hidden'; 1.1505 + return element; 1.1506 + }, 1.1507 + 1.1508 + undoClipping: function(element) { 1.1509 + element = $(element); 1.1510 + if (!element._overflow) return element; 1.1511 + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; 1.1512 + element._overflow = null; 1.1513 + return element; 1.1514 + } 1.1515 +}; 1.1516 + 1.1517 +Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf}); 1.1518 + 1.1519 +Element._attributeTranslations = {}; 1.1520 + 1.1521 +Element._attributeTranslations.names = { 1.1522 + colspan: "colSpan", 1.1523 + rowspan: "rowSpan", 1.1524 + valign: "vAlign", 1.1525 + datetime: "dateTime", 1.1526 + accesskey: "accessKey", 1.1527 + tabindex: "tabIndex", 1.1528 + enctype: "encType", 1.1529 + maxlength: "maxLength", 1.1530 + readonly: "readOnly", 1.1531 + longdesc: "longDesc" 1.1532 +}; 1.1533 + 1.1534 +Element._attributeTranslations.values = { 1.1535 + _getAttr: function(element, attribute) { 1.1536 + return element.getAttribute(attribute, 2); 1.1537 + }, 1.1538 + 1.1539 + _flag: function(element, attribute) { 1.1540 + return $(element).hasAttribute(attribute) ? attribute : null; 1.1541 + }, 1.1542 + 1.1543 + style: function(element) { 1.1544 + return element.style.cssText.toLowerCase(); 1.1545 + }, 1.1546 + 1.1547 + title: function(element) { 1.1548 + var node = element.getAttributeNode('title'); 1.1549 + return node.specified ? node.nodeValue : null; 1.1550 + } 1.1551 +}; 1.1552 + 1.1553 +Object.extend(Element._attributeTranslations.values, { 1.1554 + href: Element._attributeTranslations.values._getAttr, 1.1555 + src: Element._attributeTranslations.values._getAttr, 1.1556 + disabled: Element._attributeTranslations.values._flag, 1.1557 + checked: Element._attributeTranslations.values._flag, 1.1558 + readonly: Element._attributeTranslations.values._flag, 1.1559 + multiple: Element._attributeTranslations.values._flag 1.1560 +}); 1.1561 + 1.1562 +Element.Methods.Simulated = { 1.1563 + hasAttribute: function(element, attribute) { 1.1564 + var t = Element._attributeTranslations; 1.1565 + attribute = t.names[attribute] || attribute; 1.1566 + return $(element).getAttributeNode(attribute).specified; 1.1567 + } 1.1568 +}; 1.1569 + 1.1570 +// IE is missing .innerHTML support for TABLE-related elements 1.1571 +if (document.all && !window.opera){ 1.1572 + Element.Methods.update = function(element, html) { 1.1573 + element = $(element); 1.1574 + html = typeof html == 'undefined' ? '' : html.toString(); 1.1575 + var tagName = element.tagName.toUpperCase(); 1.1576 + if (['THEAD','TBODY','TR','TD'].include(tagName)) { 1.1577 + var div = document.createElement('div'); 1.1578 + switch (tagName) { 1.1579 + case 'THEAD': 1.1580 + case 'TBODY': 1.1581 + div.innerHTML = '<table><tbody>' + html.stripScripts() + '</tbody></table>'; 1.1582 + depth = 2; 1.1583 + break; 1.1584 + case 'TR': 1.1585 + div.innerHTML = '<table><tbody><tr>' + html.stripScripts() + '</tr></tbody></table>'; 1.1586 + depth = 3; 1.1587 + break; 1.1588 + case 'TD': 1.1589 + div.innerHTML = '<table><tbody><tr><td>' + html.stripScripts() + '</td></tr></tbody></table>'; 1.1590 + depth = 4; 1.1591 + } 1.1592 + $A(element.childNodes).each(function(node){ 1.1593 + element.removeChild(node) 1.1594 + }); 1.1595 + depth.times(function(){ div = div.firstChild }); 1.1596 + 1.1597 + $A(div.childNodes).each( 1.1598 + function(node){ element.appendChild(node) }); 1.1599 + } else { 1.1600 + element.innerHTML = html.stripScripts(); 1.1601 + } 1.1602 + setTimeout(function() {html.evalScripts()}, 10); 1.1603 + return element; 1.1604 + } 1.1605 +}; 1.1606 + 1.1607 +Object.extend(Element, Element.Methods); 1.1608 + 1.1609 +var _nativeExtensions = false; 1.1610 + 1.1611 +if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) 1.1612 + ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) { 1.1613 + var className = 'HTML' + tag + 'Element'; 1.1614 + if(window[className]) return; 1.1615 + var klass = window[className] = {}; 1.1616 + klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__; 1.1617 + }); 1.1618 + 1.1619 +Element.addMethods = function(methods) { 1.1620 + Object.extend(Element.Methods, methods || {}); 1.1621 + 1.1622 + function copy(methods, destination, onlyIfAbsent) { 1.1623 + onlyIfAbsent = onlyIfAbsent || false; 1.1624 + var cache = Element.extend.cache; 1.1625 + for (var property in methods) { 1.1626 + var value = methods[property]; 1.1627 + if (!onlyIfAbsent || !(property in destination)) 1.1628 + destination[property] = cache.findOrStore(value); 1.1629 + } 1.1630 + } 1.1631 + 1.1632 + if (typeof HTMLElement != 'undefined') { 1.1633 + copy(Element.Methods, HTMLElement.prototype); 1.1634 + copy(Element.Methods.Simulated, HTMLElement.prototype, true); 1.1635 + copy(Form.Methods, HTMLFormElement.prototype); 1.1636 + [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) { 1.1637 + copy(Form.Element.Methods, klass.prototype); 1.1638 + }); 1.1639 + _nativeExtensions = true; 1.1640 + } 1.1641 +} 1.1642 + 1.1643 +var Toggle = new Object(); 1.1644 +Toggle.display = Element.toggle; 1.1645 + 1.1646 +/*--------------------------------------------------------------------------*/ 1.1647 + 1.1648 +Abstract.Insertion = function(adjacency) { 1.1649 + this.adjacency = adjacency; 1.1650 +} 1.1651 + 1.1652 +Abstract.Insertion.prototype = { 1.1653 + initialize: function(element, content) { 1.1654 + this.element = $(element); 1.1655 + this.content = content.stripScripts(); 1.1656 + 1.1657 + if (this.adjacency && this.element.insertAdjacentHTML) { 1.1658 + try { 1.1659 + this.element.insertAdjacentHTML(this.adjacency, this.content); 1.1660 + } catch (e) { 1.1661 + var tagName = this.element.tagName.toUpperCase(); 1.1662 + if (['TBODY', 'TR'].include(tagName)) { 1.1663 + this.insertContent(this.contentFromAnonymousTable()); 1.1664 + } else { 1.1665 + throw e; 1.1666 + } 1.1667 + } 1.1668 + } else { 1.1669 + this.range = this.element.ownerDocument.createRange(); 1.1670 + if (this.initializeRange) this.initializeRange(); 1.1671 + this.insertContent([this.range.createContextualFragment(this.content)]); 1.1672 + } 1.1673 + 1.1674 + setTimeout(function() {content.evalScripts()}, 10); 1.1675 + }, 1.1676 + 1.1677 + contentFromAnonymousTable: function() { 1.1678 + var div = document.createElement('div'); 1.1679 + div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>'; 1.1680 + return $A(div.childNodes[0].childNodes[0].childNodes); 1.1681 + } 1.1682 +} 1.1683 + 1.1684 +var Insertion = new Object(); 1.1685 + 1.1686 +Insertion.Before = Class.create(); 1.1687 +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { 1.1688 + initializeRange: function() { 1.1689 + this.range.setStartBefore(this.element); 1.1690 + }, 1.1691 + 1.1692 + insertContent: function(fragments) { 1.1693 + fragments.each((function(fragment) { 1.1694 + this.element.parentNode.insertBefore(fragment, this.element); 1.1695 + }).bind(this)); 1.1696 + } 1.1697 +}); 1.1698 + 1.1699 +Insertion.Top = Class.create(); 1.1700 +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { 1.1701 + initializeRange: function() { 1.1702 + this.range.selectNodeContents(this.element); 1.1703 + this.range.collapse(true); 1.1704 + }, 1.1705 + 1.1706 + insertContent: function(fragments) { 1.1707 + fragments.reverse(false).each((function(fragment) { 1.1708 + this.element.insertBefore(fragment, this.element.firstChild); 1.1709 + }).bind(this)); 1.1710 + } 1.1711 +}); 1.1712 + 1.1713 +Insertion.Bottom = Class.create(); 1.1714 +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { 1.1715 + initializeRange: function() { 1.1716 + this.range.selectNodeContents(this.element); 1.1717 + this.range.collapse(this.element); 1.1718 + }, 1.1719 + 1.1720 + insertContent: function(fragments) { 1.1721 + fragments.each((function(fragment) { 1.1722 + this.element.appendChild(fragment); 1.1723 + }).bind(this)); 1.1724 + } 1.1725 +}); 1.1726 + 1.1727 +Insertion.After = Class.create(); 1.1728 +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { 1.1729 + initializeRange: function() { 1.1730 + this.range.setStartAfter(this.element); 1.1731 + }, 1.1732 + 1.1733 + insertContent: function(fragments) { 1.1734 + fragments.each((function(fragment) { 1.1735 + this.element.parentNode.insertBefore(fragment, 1.1736 + this.element.nextSibling); 1.1737 + }).bind(this)); 1.1738 + } 1.1739 +}); 1.1740 + 1.1741 +/*--------------------------------------------------------------------------*/ 1.1742 + 1.1743 +Element.ClassNames = Class.create(); 1.1744 +Element.ClassNames.prototype = { 1.1745 + initialize: function(element) { 1.1746 + this.element = $(element); 1.1747 + }, 1.1748 + 1.1749 + _each: function(iterator) { 1.1750 + this.element.className.split(/\s+/).select(function(name) { 1.1751 + return name.length > 0; 1.1752 + })._each(iterator); 1.1753 + }, 1.1754 + 1.1755 + set: function(className) { 1.1756 + this.element.className = className; 1.1757 + }, 1.1758 + 1.1759 + add: function(classNameToAdd) { 1.1760 + if (this.include(classNameToAdd)) return; 1.1761 + this.set($A(this).concat(classNameToAdd).join(' ')); 1.1762 + }, 1.1763 + 1.1764 + remove: function(classNameToRemove) { 1.1765 + if (!this.include(classNameToRemove)) return; 1.1766 + this.set($A(this).without(classNameToRemove).join(' ')); 1.1767 + }, 1.1768 + 1.1769 + toString: function() { 1.1770 + return $A(this).join(' '); 1.1771 + } 1.1772 +}; 1.1773 + 1.1774 +Object.extend(Element.ClassNames.prototype, Enumerable); 1.1775 +var Selector = Class.create(); 1.1776 +Selector.prototype = { 1.1777 + initialize: function(expression) { 1.1778 + this.params = {classNames: []}; 1.1779 + this.expression = expression.toString().strip(); 1.1780 + this.parseExpression(); 1.1781 + this.compileMatcher(); 1.1782 + }, 1.1783 + 1.1784 + parseExpression: function() { 1.1785 + function abort(message) { throw 'Parse error in selector: ' + message; } 1.1786 + 1.1787 + if (this.expression == '') abort('empty expression'); 1.1788 + 1.1789 + var params = this.params, expr = this.expression, match, modifier, clause, rest; 1.1790 + while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { 1.1791 + params.attributes = params.attributes || []; 1.1792 + params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); 1.1793 + expr = match[1]; 1.1794 + } 1.1795 + 1.1796 + if (expr == '*') return this.params.wildcard = true; 1.1797 + 1.1798 + while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { 1.1799 + modifier = match[1], clause = match[2], rest = match[3]; 1.1800 + switch (modifier) { 1.1801 + case '#': params.id = clause; break; 1.1802 + case '.': params.classNames.push(clause); break; 1.1803 + case '': 1.1804 + case undefined: params.tagName = clause.toUpperCase(); break; 1.1805 + default: abort(expr.inspect()); 1.1806 + } 1.1807 + expr = rest; 1.1808 + } 1.1809 + 1.1810 + if (expr.length > 0) abort(expr.inspect()); 1.1811 + }, 1.1812 + 1.1813 + buildMatchExpression: function() { 1.1814 + var params = this.params, conditions = [], clause; 1.1815 + 1.1816 + if (params.wildcard) 1.1817 + conditions.push('true'); 1.1818 + if (clause = params.id) 1.1819 + conditions.push('element.readAttribute("id") == ' + clause.inspect()); 1.1820 + if (clause = params.tagName) 1.1821 + conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); 1.1822 + if ((clause = params.classNames).length > 0) 1.1823 + for (var i = 0, length = clause.length; i < length; i++) 1.1824 + conditions.push('element.hasClassName(' + clause[i].inspect() + ')'); 1.1825 + if (clause = params.attributes) { 1.1826 + clause.each(function(attribute) { 1.1827 + var value = 'element.readAttribute(' + attribute.name.inspect() + ')'; 1.1828 + var splitValueBy = function(delimiter) { 1.1829 + return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; 1.1830 + } 1.1831 + 1.1832 + switch (attribute.operator) { 1.1833 + case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break; 1.1834 + case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; 1.1835 + case '|=': conditions.push( 1.1836 + splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() 1.1837 + ); break; 1.1838 + case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break; 1.1839 + case '': 1.1840 + case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break; 1.1841 + default: throw 'Unknown operator ' + attribute.operator + ' in selector'; 1.1842 + } 1.1843 + }); 1.1844 + } 1.1845 + 1.1846 + return conditions.join(' && '); 1.1847 + }, 1.1848 + 1.1849 + compileMatcher: function() { 1.1850 + this.match = new Function('element', 'if (!element.tagName) return false; \ 1.1851 + element = $(element); \ 1.1852 + return ' + this.buildMatchExpression()); 1.1853 + }, 1.1854 + 1.1855 + findElements: function(scope) { 1.1856 + var element; 1.1857 + 1.1858 + if (element = $(this.params.id)) 1.1859 + if (this.match(element)) 1.1860 + if (!scope || Element.childOf(element, scope)) 1.1861 + return [element]; 1.1862 + 1.1863 + scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); 1.1864 + 1.1865 + var results = []; 1.1866 + for (var i = 0, length = scope.length; i < length; i++) 1.1867 + if (this.match(element = scope[i])) 1.1868 + results.push(Element.extend(element)); 1.1869 + 1.1870 + return results; 1.1871 + }, 1.1872 + 1.1873 + toString: function() { 1.1874 + return this.expression; 1.1875 + } 1.1876 +} 1.1877 + 1.1878 +Object.extend(Selector, { 1.1879 + matchElements: function(elements, expression) { 1.1880 + var selector = new Selector(expression); 1.1881 + return elements.select(selector.match.bind(selector)).map(Element.extend); 1.1882 + }, 1.1883 + 1.1884 + findElement: function(elements, expression, index) { 1.1885 + if (typeof expression == 'number') index = expression, expression = false; 1.1886 + return Selector.matchElements(elements, expression || '*')[index || 0]; 1.1887 + }, 1.1888 + 1.1889 + findChildElements: function(element, expressions) { 1.1890 + return expressions.map(function(expression) { 1.1891 + return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) { 1.1892 + var selector = new Selector(expr); 1.1893 + return results.inject([], function(elements, result) { 1.1894 + return elements.concat(selector.findElements(result || element)); 1.1895 + }); 1.1896 + }); 1.1897 + }).flatten(); 1.1898 + } 1.1899 +}); 1.1900 + 1.1901 +function $$() { 1.1902 + return Selector.findChildElements(document, $A(arguments)); 1.1903 +} 1.1904 +var Form = { 1.1905 + reset: function(form) { 1.1906 + $(form).reset(); 1.1907 + return form; 1.1908 + }, 1.1909 + 1.1910 + serializeElements: function(elements, getHash) { 1.1911 + var data = elements.inject({}, function(result, element) { 1.1912 + if (!element.disabled && element.name) { 1.1913 + var key = element.name, value = $(element).getValue(); 1.1914 + if (value != undefined) { 1.1915 + if (result[key]) { 1.1916 + if (result[key].constructor != Array) result[key] = [result[key]]; 1.1917 + result[key].push(value); 1.1918 + } 1.1919 + else result[key] = value; 1.1920 + } 1.1921 + } 1.1922 + return result; 1.1923 + }); 1.1924 + 1.1925 + return getHash ? data : Hash.toQueryString(data); 1.1926 + } 1.1927 +}; 1.1928 + 1.1929 +Form.Methods = { 1.1930 + serialize: function(form, getHash) { 1.1931 + return Form.serializeElements(Form.getElements(form), getHash); 1.1932 + }, 1.1933 + 1.1934 + getElements: function(form) { 1.1935 + return $A($(form).getElementsByTagName('*')).inject([], 1.1936 + function(elements, child) { 1.1937 + if (Form.Element.Serializers[child.tagName.toLowerCase()]) 1.1938 + elements.push(Element.extend(child)); 1.1939 + return elements; 1.1940 + } 1.1941 + ); 1.1942 + }, 1.1943 + 1.1944 + getInputs: function(form, typeName, name) { 1.1945 + form = $(form); 1.1946 + var inputs = form.getElementsByTagName('input'); 1.1947 + 1.1948 + if (!typeName && !name) return $A(inputs).map(Element.extend); 1.1949 + 1.1950 + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { 1.1951 + var input = inputs[i]; 1.1952 + if ((typeName && input.type != typeName) || (name && input.name != name)) 1.1953 + continue; 1.1954 + matchingInputs.push(Element.extend(input)); 1.1955 + } 1.1956 + 1.1957 + return matchingInputs; 1.1958 + }, 1.1959 + 1.1960 + disable: function(form) { 1.1961 + form = $(form); 1.1962 + form.getElements().each(function(element) { 1.1963 + element.blur(); 1.1964 + element.disabled = 'true'; 1.1965 + }); 1.1966 + return form; 1.1967 + }, 1.1968 + 1.1969 + enable: function(form) { 1.1970 + form = $(form); 1.1971 + form.getElements().each(function(element) { 1.1972 + element.disabled = ''; 1.1973 + }); 1.1974 + return form; 1.1975 + }, 1.1976 + 1.1977 + findFirstElement: function(form) { 1.1978 + return $(form).getElements().find(function(element) { 1.1979 + return element.type != 'hidden' && !element.disabled && 1.1980 + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); 1.1981 + }); 1.1982 + }, 1.1983 + 1.1984 + focusFirstElement: function(form) { 1.1985 + form = $(form); 1.1986 + form.findFirstElement().activate(); 1.1987 + return form; 1.1988 + } 1.1989 +} 1.1990 + 1.1991 +Object.extend(Form, Form.Methods); 1.1992 + 1.1993 +/*--------------------------------------------------------------------------*/ 1.1994 + 1.1995 +Form.Element = { 1.1996 + focus: function(element) { 1.1997 + $(element).focus(); 1.1998 + return element; 1.1999 + }, 1.2000 + 1.2001 + select: function(element) { 1.2002 + $(element).select(); 1.2003 + return element; 1.2004 + } 1.2005 +} 1.2006 + 1.2007 +Form.Element.Methods = { 1.2008 + serialize: function(element) { 1.2009 + element = $(element); 1.2010 + if (!element.disabled && element.name) { 1.2011 + var value = element.getValue(); 1.2012 + if (value != undefined) { 1.2013 + var pair = {}; 1.2014 + pair[element.name] = value; 1.2015 + return Hash.toQueryString(pair); 1.2016 + } 1.2017 + } 1.2018 + return ''; 1.2019 + }, 1.2020 + 1.2021 + getValue: function(element) { 1.2022 + element = $(element); 1.2023 + var method = element.tagName.toLowerCase(); 1.2024 + return Form.Element.Serializers[method](element); 1.2025 + }, 1.2026 + 1.2027 + clear: function(element) { 1.2028 + $(element).value = ''; 1.2029 + return element; 1.2030 + }, 1.2031 + 1.2032 + present: function(element) { 1.2033 + return $(element).value != ''; 1.2034 + }, 1.2035 + 1.2036 + activate: function(element) { 1.2037 + element = $(element); 1.2038 + element.focus(); 1.2039 + if (element.select && ( element.tagName.toLowerCase() != 'input' || 1.2040 + !['button', 'reset', 'submit'].include(element.type) ) ) 1.2041 + element.select(); 1.2042 + return element; 1.2043 + }, 1.2044 + 1.2045 + disable: function(element) { 1.2046 + element = $(element); 1.2047 + element.disabled = true; 1.2048 + return element; 1.2049 + }, 1.2050 + 1.2051 + enable: function(element) { 1.2052 + element = $(element); 1.2053 + element.blur(); 1.2054 + element.disabled = false; 1.2055 + return element; 1.2056 + } 1.2057 +} 1.2058 + 1.2059 +Object.extend(Form.Element, Form.Element.Methods); 1.2060 +var Field = Form.Element; 1.2061 +var $F = Form.Element.getValue; 1.2062 + 1.2063 +/*--------------------------------------------------------------------------*/ 1.2064 + 1.2065 +Form.Element.Serializers = { 1.2066 + input: function(element) { 1.2067 + switch (element.type.toLowerCase()) { 1.2068 + case 'checkbox': 1.2069 + case 'radio': 1.2070 + return Form.Element.Serializers.inputSelector(element); 1.2071 + default: 1.2072 + return Form.Element.Serializers.textarea(element); 1.2073 + } 1.2074 + }, 1.2075 + 1.2076 + inputSelector: function(element) { 1.2077 + return element.checked ? element.value : null; 1.2078 + }, 1.2079 + 1.2080 + textarea: function(element) { 1.2081 + return element.value; 1.2082 + }, 1.2083 + 1.2084 + select: function(element) { 1.2085 + return this[element.type == 'select-one' ? 1.2086 + 'selectOne' : 'selectMany'](element); 1.2087 + }, 1.2088 + 1.2089 + selectOne: function(element) { 1.2090 + var index = element.selectedIndex; 1.2091 + return index >= 0 ? this.optionValue(element.options[index]) : null; 1.2092 + }, 1.2093 + 1.2094 + selectMany: function(element) { 1.2095 + var values, length = element.length; 1.2096 + if (!length) return null; 1.2097 + 1.2098 + for (var i = 0, values = []; i < length; i++) { 1.2099 + var opt = element.options[i]; 1.2100 + if (opt.selected) values.push(this.optionValue(opt)); 1.2101 + } 1.2102 + return values; 1.2103 + }, 1.2104 + 1.2105 + optionValue: function(opt) { 1.2106 + // extend element because hasAttribute may not be native 1.2107 + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; 1.2108 + } 1.2109 +} 1.2110 + 1.2111 +/*--------------------------------------------------------------------------*/ 1.2112 + 1.2113 +Abstract.TimedObserver = function() {} 1.2114 +Abstract.TimedObserver.prototype = { 1.2115 + initialize: function(element, frequency, callback) { 1.2116 + this.frequency = frequency; 1.2117 + this.element = $(element); 1.2118 + this.callback = callback; 1.2119 + 1.2120 + this.lastValue = this.getValue(); 1.2121 + this.registerCallback(); 1.2122 + }, 1.2123 + 1.2124 + registerCallback: function() { 1.2125 + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); 1.2126 + }, 1.2127 + 1.2128 + onTimerEvent: function() { 1.2129 + var value = this.getValue(); 1.2130 + var changed = ('string' == typeof this.lastValue && 'string' == typeof value 1.2131 + ? this.lastValue != value : String(this.lastValue) != String(value)); 1.2132 + if (changed) { 1.2133 + this.callback(this.element, value); 1.2134 + this.lastValue = value; 1.2135 + } 1.2136 + } 1.2137 +} 1.2138 + 1.2139 +Form.Element.Observer = Class.create(); 1.2140 +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { 1.2141 + getValue: function() { 1.2142 + return Form.Element.getValue(this.element); 1.2143 + } 1.2144 +}); 1.2145 + 1.2146 +Form.Observer = Class.create(); 1.2147 +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { 1.2148 + getValue: function() { 1.2149 + return Form.serialize(this.element); 1.2150 + } 1.2151 +}); 1.2152 + 1.2153 +/*--------------------------------------------------------------------------*/ 1.2154 + 1.2155 +Abstract.EventObserver = function() {} 1.2156 +Abstract.EventObserver.prototype = { 1.2157 + initialize: function(element, callback) { 1.2158 + this.element = $(element); 1.2159 + this.callback = callback; 1.2160 + 1.2161 + this.lastValue = this.getValue(); 1.2162 + if (this.element.tagName.toLowerCase() == 'form') 1.2163 + this.registerFormCallbacks(); 1.2164 + else 1.2165 + this.registerCallback(this.element); 1.2166 + }, 1.2167 + 1.2168 + onElementEvent: function() { 1.2169 + var value = this.getValue(); 1.2170 + if (this.lastValue != value) { 1.2171 + this.callback(this.element, value); 1.2172 + this.lastValue = value; 1.2173 + } 1.2174 + }, 1.2175 + 1.2176 + registerFormCallbacks: function() { 1.2177 + Form.getElements(this.element).each(this.registerCallback.bind(this)); 1.2178 + }, 1.2179 + 1.2180 + registerCallback: function(element) { 1.2181 + if (element.type) { 1.2182 + switch (element.type.toLowerCase()) { 1.2183 + case 'checkbox': 1.2184 + case 'radio': 1.2185 + Event.observe(element, 'click', this.onElementEvent.bind(this)); 1.2186 + break; 1.2187 + default: 1.2188 + Event.observe(element, 'change', this.onElementEvent.bind(this)); 1.2189 + break; 1.2190 + } 1.2191 + } 1.2192 + } 1.2193 +} 1.2194 + 1.2195 +Form.Element.EventObserver = Class.create(); 1.2196 +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { 1.2197 + getValue: function() { 1.2198 + return Form.Element.getValue(this.element); 1.2199 + } 1.2200 +}); 1.2201 + 1.2202 +Form.EventObserver = Class.create(); 1.2203 +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { 1.2204 + getValue: function() { 1.2205 + return Form.serialize(this.element); 1.2206 + } 1.2207 +}); 1.2208 +if (!window.Event) { 1.2209 + var Event = new Object(); 1.2210 +} 1.2211 + 1.2212 +Object.extend(Event, { 1.2213 + KEY_BACKSPACE: 8, 1.2214 + KEY_TAB: 9, 1.2215 + KEY_RETURN: 13, 1.2216 + KEY_ESC: 27, 1.2217 + KEY_LEFT: 37, 1.2218 + KEY_UP: 38, 1.2219 + KEY_RIGHT: 39, 1.2220 + KEY_DOWN: 40, 1.2221 + KEY_DELETE: 46, 1.2222 + KEY_HOME: 36, 1.2223 + KEY_END: 35, 1.2224 + KEY_PAGEUP: 33, 1.2225 + KEY_PAGEDOWN: 34, 1.2226 + 1.2227 + element: function(event) { 1.2228 + return event.target || event.srcElement; 1.2229 + }, 1.2230 + 1.2231 + isLeftClick: function(event) { 1.2232 + return (((event.which) && (event.which == 1)) || 1.2233 + ((event.button) && (event.button == 1))); 1.2234 + }, 1.2235 + 1.2236 + pointerX: function(event) { 1.2237 + return event.pageX || (event.clientX + 1.2238 + (document.documentElement.scrollLeft || document.body.scrollLeft)); 1.2239 + }, 1.2240 + 1.2241 + pointerY: function(event) { 1.2242 + return event.pageY || (event.clientY + 1.2243 + (document.documentElement.scrollTop || document.body.scrollTop)); 1.2244 + }, 1.2245 + 1.2246 + stop: function(event) { 1.2247 + if (event.preventDefault) { 1.2248 + event.preventDefault(); 1.2249 + event.stopPropagation(); 1.2250 + } else { 1.2251 + event.returnValue = false; 1.2252 + event.cancelBubble = true; 1.2253 + } 1.2254 + }, 1.2255 + 1.2256 + // find the first node with the given tagName, starting from the 1.2257 + // node the event was triggered on; traverses the DOM upwards 1.2258 + findElement: function(event, tagName) { 1.2259 + var element = Event.element(event); 1.2260 + while (element.parentNode && (!element.tagName || 1.2261 + (element.tagName.toUpperCase() != tagName.toUpperCase()))) 1.2262 + element = element.parentNode; 1.2263 + return element; 1.2264 + }, 1.2265 + 1.2266 + observers: false, 1.2267 + 1.2268 + _observeAndCache: function(element, name, observer, useCapture) { 1.2269 + if (!this.observers) this.observers = []; 1.2270 + if (element.addEventListener) { 1.2271 + this.observers.push([element, name, observer, useCapture]); 1.2272 + element.addEventListener(name, observer, useCapture); 1.2273 + } else if (element.attachEvent) { 1.2274 + this.observers.push([element, name, observer, useCapture]); 1.2275 + element.attachEvent('on' + name, observer); 1.2276 + } 1.2277 + }, 1.2278 + 1.2279 + unloadCache: function() { 1.2280 + if (!Event.observers) return; 1.2281 + for (var i = 0, length = Event.observers.length; i < length; i++) { 1.2282 + Event.stopObserving.apply(this, Event.observers[i]); 1.2283 + Event.observers[i][0] = null; 1.2284 + } 1.2285 + Event.observers = false; 1.2286 + }, 1.2287 + 1.2288 + observe: function(element, name, observer, useCapture) { 1.2289 + element = $(element); 1.2290 + useCapture = useCapture || false; 1.2291 + 1.2292 + if (name == 'keypress' && 1.2293 + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) 1.2294 + || element.attachEvent)) 1.2295 + name = 'keydown'; 1.2296 + 1.2297 + Event._observeAndCache(element, name, observer, useCapture); 1.2298 + }, 1.2299 + 1.2300 + stopObserving: function(element, name, observer, useCapture) { 1.2301 + element = $(element); 1.2302 + useCapture = useCapture || false; 1.2303 + 1.2304 + if (name == 'keypress' && 1.2305 + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) 1.2306 + || element.detachEvent)) 1.2307 + name = 'keydown'; 1.2308 + 1.2309 + if (element.removeEventListener) { 1.2310 + element.removeEventListener(name, observer, useCapture); 1.2311 + } else if (element.detachEvent) { 1.2312 + try { 1.2313 + element.detachEvent('on' + name, observer); 1.2314 + } catch (e) {} 1.2315 + } 1.2316 + } 1.2317 +}); 1.2318 + 1.2319 +/* prevent memory leaks in IE */ 1.2320 +if (navigator.appVersion.match(/\bMSIE\b/)) 1.2321 + Event.observe(window, 'unload', Event.unloadCache, false); 1.2322 +var Position = { 1.2323 + // set to true if needed, warning: firefox performance problems 1.2324 + // NOT neeeded for page scrolling, only if draggable contained in 1.2325 + // scrollable elements 1.2326 + includeScrollOffsets: false, 1.2327 + 1.2328 + // must be called before calling withinIncludingScrolloffset, every time the 1.2329 + // page is scrolled 1.2330 + prepare: function() { 1.2331 + this.deltaX = window.pageXOffset 1.2332 + || document.documentElement.scrollLeft 1.2333 + || document.body.scrollLeft 1.2334 + || 0; 1.2335 + this.deltaY = window.pageYOffset 1.2336 + || document.documentElement.scrollTop 1.2337 + || document.body.scrollTop 1.2338 + || 0; 1.2339 + }, 1.2340 + 1.2341 + realOffset: function(element) { 1.2342 + var valueT = 0, valueL = 0; 1.2343 + do { 1.2344 + valueT += element.scrollTop || 0; 1.2345 + valueL += element.scrollLeft || 0; 1.2346 + element = element.parentNode; 1.2347 + } while (element); 1.2348 + return [valueL, valueT]; 1.2349 + }, 1.2350 + 1.2351 + cumulativeOffset: function(element) { 1.2352 + var valueT = 0, valueL = 0; 1.2353 + do { 1.2354 + valueT += element.offsetTop || 0; 1.2355 + valueL += element.offsetLeft || 0; 1.2356 + element = element.offsetParent; 1.2357 + } while (element); 1.2358 + return [valueL, valueT]; 1.2359 + }, 1.2360 + 1.2361 + positionedOffset: function(element) { 1.2362 + var valueT = 0, valueL = 0; 1.2363 + do { 1.2364 + valueT += element.offsetTop || 0; 1.2365 + valueL += element.offsetLeft || 0; 1.2366 + element = element.offsetParent; 1.2367 + if (element) { 1.2368 + if(element.tagName=='BODY') break; 1.2369 + var p = Element.getStyle(element, 'position'); 1.2370 + if (p == 'relative' || p == 'absolute') break; 1.2371 + } 1.2372 + } while (element); 1.2373 + return [valueL, valueT]; 1.2374 + }, 1.2375 + 1.2376 + offsetParent: function(element) { 1.2377 + if (element.offsetParent) return element.offsetParent; 1.2378 + if (element == document.body) return element; 1.2379 + 1.2380 + while ((element = element.parentNode) && element != document.body) 1.2381 + if (Element.getStyle(element, 'position') != 'static') 1.2382 + return element; 1.2383 + 1.2384 + return document.body; 1.2385 + }, 1.2386 + 1.2387 + // caches x/y coordinate pair to use with overlap 1.2388 + within: function(element, x, y) { 1.2389 + if (this.includeScrollOffsets) 1.2390 + return this.withinIncludingScrolloffsets(element, x, y); 1.2391 + this.xcomp = x; 1.2392 + this.ycomp = y; 1.2393 + this.offset = this.cumulativeOffset(element); 1.2394 + 1.2395 + return (y >= this.offset[1] && 1.2396 + y < this.offset[1] + element.offsetHeight && 1.2397 + x >= this.offset[0] && 1.2398 + x < this.offset[0] + element.offsetWidth); 1.2399 + }, 1.2400 + 1.2401 + withinIncludingScrolloffsets: function(element, x, y) { 1.2402 + var offsetcache = this.realOffset(element); 1.2403 + 1.2404 + this.xcomp = x + offsetcache[0] - this.deltaX; 1.2405 + this.ycomp = y + offsetcache[1] - this.deltaY; 1.2406 + this.offset = this.cumulativeOffset(element); 1.2407 + 1.2408 + return (this.ycomp >= this.offset[1] && 1.2409 + this.ycomp < this.offset[1] + element.offsetHeight && 1.2410 + this.xcomp >= this.offset[0] && 1.2411 + this.xcomp < this.offset[0] + element.offsetWidth); 1.2412 + }, 1.2413 + 1.2414 + // within must be called directly before 1.2415 + overlap: function(mode, element) { 1.2416 + if (!mode) return 0; 1.2417 + if (mode == 'vertical') 1.2418 + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / 1.2419 + element.offsetHeight; 1.2420 + if (mode == 'horizontal') 1.2421 + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / 1.2422 + element.offsetWidth; 1.2423 + }, 1.2424 + 1.2425 + page: function(forElement) { 1.2426 + var valueT = 0, valueL = 0; 1.2427 + 1.2428 + var element = forElement; 1.2429 + do { 1.2430 + valueT += element.offsetTop || 0; 1.2431 + valueL += element.offsetLeft || 0; 1.2432 + 1.2433 + // Safari fix 1.2434 + if (element.offsetParent==document.body) 1.2435 + if (Element.getStyle(element,'position')=='absolute') break; 1.2436 + 1.2437 + } while (element = element.offsetParent); 1.2438 + 1.2439 + element = forElement; 1.2440 + do { 1.2441 + if (!window.opera || element.tagName=='BODY') { 1.2442 + valueT -= element.scrollTop || 0; 1.2443 + valueL -= element.scrollLeft || 0; 1.2444 + } 1.2445 + } while (element = element.parentNode); 1.2446 + 1.2447 + return [valueL, valueT]; 1.2448 + }, 1.2449 + 1.2450 + clone: function(source, target) { 1.2451 + var options = Object.extend({ 1.2452 + setLeft: true, 1.2453 + setTop: true, 1.2454 + setWidth: true, 1.2455 + setHeight: true, 1.2456 + offsetTop: 0, 1.2457 + offsetLeft: 0 1.2458 + }, arguments[2] || {}) 1.2459 + 1.2460 + // find page position of source 1.2461 + source = $(source); 1.2462 + var p = Position.page(source); 1.2463 + 1.2464 + // find coordinate system to use 1.2465 + target = $(target); 1.2466 + var delta = [0, 0]; 1.2467 + var parent = null; 1.2468 + // delta [0,0] will do fine with position: fixed elements, 1.2469 + // position:absolute needs offsetParent deltas 1.2470 + if (Element.getStyle(target,'position') == 'absolute') { 1.2471 + parent = Position.offsetParent(target); 1.2472 + delta = Position.page(parent); 1.2473 + } 1.2474 + 1.2475 + // correct by body offsets (fixes Safari) 1.2476 + if (parent == document.body) { 1.2477 + delta[0] -= document.body.offsetLeft; 1.2478 + delta[1] -= document.body.offsetTop; 1.2479 + } 1.2480 + 1.2481 + // set position 1.2482 + if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; 1.2483 + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; 1.2484 + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; 1.2485 + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; 1.2486 + }, 1.2487 + 1.2488 + absolutize: function(element) { 1.2489 + element = $(element); 1.2490 + if (element.style.position == 'absolute') return; 1.2491 + Position.prepare(); 1.2492 + 1.2493 + var offsets = Position.positionedOffset(element); 1.2494 + var top = offsets[1]; 1.2495 + var left = offsets[0]; 1.2496 + var width = element.clientWidth; 1.2497 + var height = element.clientHeight; 1.2498 + 1.2499 + element._originalLeft = left - parseFloat(element.style.left || 0); 1.2500 + element._originalTop = top - parseFloat(element.style.top || 0); 1.2501 + element._originalWidth = element.style.width; 1.2502 + element._originalHeight = element.style.height; 1.2503 + 1.2504 + element.style.position = 'absolute'; 1.2505 + element.style.top = top + 'px'; 1.2506 + element.style.left = left + 'px'; 1.2507 + element.style.width = width + 'px'; 1.2508 + element.style.height = height + 'px'; 1.2509 + }, 1.2510 + 1.2511 + relativize: function(element) { 1.2512 + element = $(element); 1.2513 + if (element.style.position == 'relative') return; 1.2514 + Position.prepare(); 1.2515 + 1.2516 + element.style.position = 'relative'; 1.2517 + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); 1.2518 + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); 1.2519 + 1.2520 + element.style.top = top + 'px'; 1.2521 + element.style.left = left + 'px'; 1.2522 + element.style.height = element._originalHeight; 1.2523 + element.style.width = element._originalWidth; 1.2524 + } 1.2525 +} 1.2526 + 1.2527 +// Safari returns margins on body which is incorrect if the child is absolutely 1.2528 +// positioned. For performance reasons, redefine Position.cumulativeOffset for 1.2529 +// KHTML/WebKit only. 1.2530 +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { 1.2531 + Position.cumulativeOffset = function(element) { 1.2532 + var valueT = 0, valueL = 0; 1.2533 + do { 1.2534 + valueT += element.offsetTop || 0; 1.2535 + valueL += element.offsetLeft || 0; 1.2536 + if (element.offsetParent == document.body) 1.2537 + if (Element.getStyle(element, 'position') == 'absolute') break; 1.2538 + 1.2539 + element = element.offsetParent; 1.2540 + } while (element); 1.2541 + 1.2542 + return [valueL, valueT]; 1.2543 + } 1.2544 +} 1.2545 + 1.2546 +Element.addMethods(); 1.2547 + 1.2548 + 1.2549 +// ------------------------------------------------------------------------ 1.2550 +// ------------------------------------------------------------------------ 1.2551 + 1.2552 +// The rest of this file is the actual ray tracer written by Adam 1.2553 +// Burmister. It's a concatenation of the following files: 1.2554 +// 1.2555 +// flog/color.js 1.2556 +// flog/light.js 1.2557 +// flog/vector.js 1.2558 +// flog/ray.js 1.2559 +// flog/scene.js 1.2560 +// flog/material/basematerial.js 1.2561 +// flog/material/solid.js 1.2562 +// flog/material/chessboard.js 1.2563 +// flog/shape/baseshape.js 1.2564 +// flog/shape/sphere.js 1.2565 +// flog/shape/plane.js 1.2566 +// flog/intersectioninfo.js 1.2567 +// flog/camera.js 1.2568 +// flog/background.js 1.2569 +// flog/engine.js 1.2570 + 1.2571 + 1.2572 +/* Fake a Flog.* namespace */ 1.2573 +if(typeof(Flog) == 'undefined') var Flog = {}; 1.2574 +if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; 1.2575 + 1.2576 +Flog.RayTracer.Color = Class.create(); 1.2577 + 1.2578 +Flog.RayTracer.Color.prototype = { 1.2579 + red : 0.0, 1.2580 + green : 0.0, 1.2581 + blue : 0.0, 1.2582 + 1.2583 + initialize : function(r, g, b) { 1.2584 + if(!r) r = 0.0; 1.2585 + if(!g) g = 0.0; 1.2586 + if(!b) b = 0.0; 1.2587 + 1.2588 + this.red = r; 1.2589 + this.green = g; 1.2590 + this.blue = b; 1.2591 + }, 1.2592 + 1.2593 + add : function(c1, c2){ 1.2594 + var result = new Flog.RayTracer.Color(0,0,0); 1.2595 + 1.2596 + result.red = c1.red + c2.red; 1.2597 + result.green = c1.green + c2.green; 1.2598 + result.blue = c1.blue + c2.blue; 1.2599 + 1.2600 + return result; 1.2601 + }, 1.2602 + 1.2603 + addScalar: function(c1, s){ 1.2604 + var result = new Flog.RayTracer.Color(0,0,0); 1.2605 + 1.2606 + result.red = c1.red + s; 1.2607 + result.green = c1.green + s; 1.2608 + result.blue = c1.blue + s; 1.2609 + 1.2610 + result.limit(); 1.2611 + 1.2612 + return result; 1.2613 + }, 1.2614 + 1.2615 + subtract: function(c1, c2){ 1.2616 + var result = new Flog.RayTracer.Color(0,0,0); 1.2617 + 1.2618 + result.red = c1.red - c2.red; 1.2619 + result.green = c1.green - c2.green; 1.2620 + result.blue = c1.blue - c2.blue; 1.2621 + 1.2622 + return result; 1.2623 + }, 1.2624 + 1.2625 + multiply : function(c1, c2) { 1.2626 + var result = new Flog.RayTracer.Color(0,0,0); 1.2627 + 1.2628 + result.red = c1.red * c2.red; 1.2629 + result.green = c1.green * c2.green; 1.2630 + result.blue = c1.blue * c2.blue; 1.2631 + 1.2632 + return result; 1.2633 + }, 1.2634 + 1.2635 + multiplyScalar : function(c1, f) { 1.2636 + var result = new Flog.RayTracer.Color(0,0,0); 1.2637 + 1.2638 + result.red = c1.red * f; 1.2639 + result.green = c1.green * f; 1.2640 + result.blue = c1.blue * f; 1.2641 + 1.2642 + return result; 1.2643 + }, 1.2644 + 1.2645 + divideFactor : function(c1, f) { 1.2646 + var result = new Flog.RayTracer.Color(0,0,0); 1.2647 + 1.2648 + result.red = c1.red / f; 1.2649 + result.green = c1.green / f; 1.2650 + result.blue = c1.blue / f; 1.2651 + 1.2652 + return result; 1.2653 + }, 1.2654 + 1.2655 + limit: function(){ 1.2656 + this.red = (this.red > 0.0) ? ( (this.red > 1.0) ? 1.0 : this.red ) : 0.0; 1.2657 + this.green = (this.green > 0.0) ? ( (this.green > 1.0) ? 1.0 : this.green ) : 0.0; 1.2658 + this.blue = (this.blue > 0.0) ? ( (this.blue > 1.0) ? 1.0 : this.blue ) : 0.0; 1.2659 + }, 1.2660 + 1.2661 + distance : function(color) { 1.2662 + var d = Math.abs(this.red - color.red) + Math.abs(this.green - color.green) + Math.abs(this.blue - color.blue); 1.2663 + return d; 1.2664 + }, 1.2665 + 1.2666 + blend: function(c1, c2, w){ 1.2667 + var result = new Flog.RayTracer.Color(0,0,0); 1.2668 + result = Flog.RayTracer.Color.prototype.add( 1.2669 + Flog.RayTracer.Color.prototype.multiplyScalar(c1, 1 - w), 1.2670 + Flog.RayTracer.Color.prototype.multiplyScalar(c2, w) 1.2671 + ); 1.2672 + return result; 1.2673 + }, 1.2674 + 1.2675 + toString : function () { 1.2676 + var r = Math.floor(this.red*255); 1.2677 + var g = Math.floor(this.green*255); 1.2678 + var b = Math.floor(this.blue*255); 1.2679 + 1.2680 + return "rgb("+ r +","+ g +","+ b +")"; 1.2681 + } 1.2682 +} 1.2683 +/* Fake a Flog.* namespace */ 1.2684 +if(typeof(Flog) == 'undefined') var Flog = {}; 1.2685 +if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; 1.2686 + 1.2687 +Flog.RayTracer.Light = Class.create(); 1.2688 + 1.2689 +Flog.RayTracer.Light.prototype = { 1.2690 + position: null, 1.2691 + color: null, 1.2692 + intensity: 10.0, 1.2693 + 1.2694 + initialize : function(pos, color, intensity) { 1.2695 + this.position = pos; 1.2696 + this.color = color; 1.2697 + this.intensity = (intensity ? intensity : 10.0); 1.2698 + }, 1.2699 + 1.2700 + getIntensity: function(distance){ 1.2701 + if(distance >= intensity) return 0; 1.2702 + 1.2703 + return Math.pow((intensity - distance) / strength, 0.2); 1.2704 + }, 1.2705 + 1.2706 + toString : function () { 1.2707 + return 'Light [' + this.position.x + ',' + this.position.y + ',' + this.position.z + ']'; 1.2708 + } 1.2709 +} 1.2710 +/* Fake a Flog.* namespace */ 1.2711 +if(typeof(Flog) == 'undefined') var Flog = {}; 1.2712 +if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; 1.2713 + 1.2714 +Flog.RayTracer.Vector = Class.create(); 1.2715 + 1.2716 +Flog.RayTracer.Vector.prototype = { 1.2717 + x : 0.0, 1.2718 + y : 0.0, 1.2719 + z : 0.0, 1.2720 + 1.2721 + initialize : function(x, y, z) { 1.2722 + this.x = (x ? x : 0); 1.2723 + this.y = (y ? y : 0); 1.2724 + this.z = (z ? z : 0); 1.2725 + }, 1.2726 + 1.2727 + copy: function(vector){ 1.2728 + this.x = vector.x; 1.2729 + this.y = vector.y; 1.2730 + this.z = vector.z; 1.2731 + }, 1.2732 + 1.2733 + normalize : function() { 1.2734 + var m = this.magnitude(); 1.2735 + return new Flog.RayTracer.Vector(this.x / m, this.y / m, this.z / m); 1.2736 + }, 1.2737 + 1.2738 + magnitude : function() { 1.2739 + return Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z)); 1.2740 + }, 1.2741 + 1.2742 + cross : function(w) { 1.2743 + return new Flog.RayTracer.Vector( 1.2744 + -this.z * w.y + this.y * w.z, 1.2745 + this.z * w.x - this.x * w.z, 1.2746 + -this.y * w.x + this.x * w.y); 1.2747 + }, 1.2748 + 1.2749 + dot : function(w) { 1.2750 + return this.x * w.x + this.y * w.y + this.z * w.z; 1.2751 + }, 1.2752 + 1.2753 + add : function(v, w) { 1.2754 + return new Flog.RayTracer.Vector(w.x + v.x, w.y + v.y, w.z + v.z); 1.2755 + }, 1.2756 + 1.2757 + subtract : function(v, w) { 1.2758 + if(!w || !v) throw 'Vectors must be defined [' + v + ',' + w + ']'; 1.2759 + return new Flog.RayTracer.Vector(v.x - w.x, v.y - w.y, v.z - w.z); 1.2760 + }, 1.2761 + 1.2762 + multiplyVector : function(v, w) { 1.2763 + return new Flog.RayTracer.Vector(v.x * w.x, v.y * w.y, v.z * w.z); 1.2764 + }, 1.2765 + 1.2766 + multiplyScalar : function(v, w) { 1.2767 + return new Flog.RayTracer.Vector(v.x * w, v.y * w, v.z * w); 1.2768 + }, 1.2769 + 1.2770 + toString : function () { 1.2771 + return 'Vector [' + this.x + ',' + this.y + ',' + this.z + ']'; 1.2772 + } 1.2773 +} 1.2774 +/* Fake a Flog.* namespace */ 1.2775 +if(typeof(Flog) == 'undefined') var Flog = {}; 1.2776 +if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; 1.2777 + 1.2778 +Flog.RayTracer.Ray = Class.create(); 1.2779 + 1.2780 +Flog.RayTracer.Ray.prototype = { 1.2781 + position : null, 1.2782 + direction : null, 1.2783 + initialize : function(pos, dir) { 1.2784 + this.position = pos; 1.2785 + this.direction = dir; 1.2786 + }, 1.2787 + 1.2788 + toString : function () { 1.2789 + return 'Ray [' + this.position + ',' + this.direction + ']'; 1.2790 + } 1.2791 +} 1.2792 +/* Fake a Flog.* namespace */ 1.2793 +if(typeof(Flog) == 'undefined') var Flog = {}; 1.2794 +if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; 1.2795 + 1.2796 +Flog.RayTracer.Scene = Class.create(); 1.2797 + 1.2798 +Flog.RayTracer.Scene.prototype = { 1.2799 + camera : null, 1.2800 + shapes : [], 1.2801 + lights : [], 1.2802 + background : null, 1.2803 + 1.2804 + initialize : function() { 1.2805 + this.camera = new Flog.RayTracer.Camera( 1.2806 + new Flog.RayTracer.Vector(0,0,-5), 1.2807 + new Flog.RayTracer.Vector(0,0,1), 1.2808 + new Flog.RayTracer.Vector(0,1,0) 1.2809 + ); 1.2810 + this.shapes = new Array(); 1.2811 + this.lights = new Array(); 1.2812 + this.background = new Flog.RayTracer.Background(new Flog.RayTracer.Color(0,0,0.5), 0.2); 1.2813 + } 1.2814 +} 1.2815 +/* Fake a Flog.* namespace */ 1.2816 +if(typeof(Flog) == 'undefined') var Flog = {}; 1.2817 +if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; 1.2818 +if(typeof(Flog.RayTracer.Material) == 'undefined') Flog.RayTracer.Material = {}; 1.2819 + 1.2820 +Flog.RayTracer.Material.BaseMaterial = Class.create(); 1.2821 + 1.2822 +Flog.RayTracer.Material.BaseMaterial.prototype = { 1.2823 + 1.2824 + gloss: 2.0, // [0...infinity] 0 = matt 1.2825 + transparency: 0.0, // 0=opaque 1.2826 + reflection: 0.0, // [0...infinity] 0 = no reflection 1.2827 + refraction: 0.50, 1.2828 + hasTexture: false, 1.2829 + 1.2830 + initialize : function() { 1.2831 + 1.2832 + }, 1.2833 + 1.2834 + getColor: function(u, v){ 1.2835 + 1.2836 + }, 1.2837 + 1.2838 + wrapUp: function(t){ 1.2839 + t = t % 2.0; 1.2840 + if(t < -1) t += 2.0; 1.2841 + if(t >= 1) t -= 2.0; 1.2842 + return t; 1.2843 + }, 1.2844 + 1.2845 + toString : function () { 1.2846 + return 'Material [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']'; 1.2847 + } 1.2848 +} 1.2849 +/* Fake a Flog.* namespace */ 1.2850 +if(typeof(Flog) == 'undefined') var Flog = {}; 1.2851 +if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; 1.2852 + 1.2853 +Flog.RayTracer.Material.Solid = Class.create(); 1.2854 + 1.2855 +Flog.RayTracer.Material.Solid.prototype = Object.extend( 1.2856 + new Flog.RayTracer.Material.BaseMaterial(), { 1.2857 + initialize : function(color, reflection, refraction, transparency, gloss) { 1.2858 + this.color = color; 1.2859 + this.reflection = reflection; 1.2860 + this.transparency = transparency; 1.2861 + this.gloss = gloss; 1.2862 + this.hasTexture = false; 1.2863 + }, 1.2864 + 1.2865 + getColor: function(u, v){ 1.2866 + return this.color; 1.2867 + }, 1.2868 + 1.2869 + toString : function () { 1.2870 + return 'SolidMaterial [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']'; 1.2871 + } 1.2872 + } 1.2873 +); 1.2874 +/* Fake a Flog.* namespace */ 1.2875 +if(typeof(Flog) == 'undefined') var Flog = {}; 1.2876 +if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; 1.2877 + 1.2878 +Flog.RayTracer.Material.Chessboard = Class.create(); 1.2879 + 1.2880 +Flog.RayTracer.Material.Chessboard.prototype = Object.extend( 1.2881 + new Flog.RayTracer.Material.BaseMaterial(), { 1.2882 + colorEven: null, 1.2883 + colorOdd: null, 1.2884 + density: 0.5, 1.2885 + 1.2886 + initialize : function(colorEven, colorOdd, reflection, transparency, gloss, density) { 1.2887 + this.colorEven = colorEven; 1.2888 + this.colorOdd = colorOdd; 1.2889 + this.reflection = reflection; 1.2890 + this.transparency = transparency; 1.2891 + this.gloss = gloss; 1.2892 + this.density = density; 1.2893 + this.hasTexture = true; 1.2894 + }, 1.2895 + 1.2896 + getColor: function(u, v){ 1.2897 + var t = this.wrapUp(u * this.density) * this.wrapUp(v * this.density); 1.2898 + 1.2899 + if(t < 0.0) 1.2900 + return this.colorEven; 1.2901 + else 1.2902 + return this.colorOdd; 1.2903 + }, 1.2904 + 1.2905 + toString : function () { 1.2906 + return 'ChessMaterial [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']'; 1.2907 + } 1.2908 + } 1.2909 +); 1.2910 +/* Fake a Flog.* namespace */ 1.2911 +if(typeof(Flog) == 'undefined') var Flog = {}; 1.2912 +if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; 1.2913 +if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {}; 1.2914 + 1.2915 +Flog.RayTracer.Shape.BaseShape = Class.create(); 1.2916 + 1.2917 +Flog.RayTracer.Shape.BaseShape.prototype = { 1.2918 + position: null, 1.2919 + material: null, 1.2920 + 1.2921 + initialize : function() { 1.2922 + this.position = new Vector(0,0,0); 1.2923 + this.material = new Flog.RayTracer.Material.SolidMaterial( 1.2924 + new Flog.RayTracer.Color(1,0,1), 1.2925 + 0, 1.2926 + 0, 1.2927 + 0 1.2928 + ); 1.2929 + }, 1.2930 + 1.2931 + toString : function () { 1.2932 + return 'Material [gloss=' + this.gloss + ', transparency=' + this.transparency + ', hasTexture=' + this.hasTexture +']'; 1.2933 + } 1.2934 +} 1.2935 +/* Fake a Flog.* namespace */ 1.2936 +if(typeof(Flog) == 'undefined') var Flog = {}; 1.2937 +if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; 1.2938 +if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {}; 1.2939 + 1.2940 +Flog.RayTracer.Shape.Sphere = Class.create(); 1.2941 + 1.2942 +Flog.RayTracer.Shape.Sphere.prototype = { 1.2943 + initialize : function(pos, radius, material) { 1.2944 + this.radius = radius; 1.2945 + this.position = pos; 1.2946 + this.material = material; 1.2947 + }, 1.2948 + 1.2949 + intersect: function(ray){ 1.2950 + var info = new Flog.RayTracer.IntersectionInfo(); 1.2951 + info.shape = this; 1.2952 + 1.2953 + var dst = Flog.RayTracer.Vector.prototype.subtract(ray.position, this.position); 1.2954 + 1.2955 + var B = dst.dot(ray.direction); 1.2956 + var C = dst.dot(dst) - (this.radius * this.radius); 1.2957 + var D = (B * B) - C; 1.2958 + 1.2959 + if(D > 0){ // intersection! 1.2960 + info.isHit = true; 1.2961 + info.distance = (-B) - Math.sqrt(D); 1.2962 + info.position = Flog.RayTracer.Vector.prototype.add( 1.2963 + ray.position, 1.2964 + Flog.RayTracer.Vector.prototype.multiplyScalar( 1.2965 + ray.direction, 1.2966 + info.distance 1.2967 + ) 1.2968 + ); 1.2969 + info.normal = Flog.RayTracer.Vector.prototype.subtract( 1.2970 + info.position, 1.2971 + this.position 1.2972 + ).normalize(); 1.2973 + 1.2974 + info.color = this.material.getColor(0,0); 1.2975 + } else { 1.2976 + info.isHit = false; 1.2977 + } 1.2978 + return info; 1.2979 + }, 1.2980 + 1.2981 + toString : function () { 1.2982 + return 'Sphere [position=' + this.position + ', radius=' + this.radius + ']'; 1.2983 + } 1.2984 +} 1.2985 +/* Fake a Flog.* namespace */ 1.2986 +if(typeof(Flog) == 'undefined') var Flog = {}; 1.2987 +if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; 1.2988 +if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {}; 1.2989 + 1.2990 +Flog.RayTracer.Shape.Plane = Class.create(); 1.2991 + 1.2992 +Flog.RayTracer.Shape.Plane.prototype = { 1.2993 + d: 0.0, 1.2994 + 1.2995 + initialize : function(pos, d, material) { 1.2996 + this.position = pos; 1.2997 + this.d = d; 1.2998 + this.material = material; 1.2999 + }, 1.3000 + 1.3001 + intersect: function(ray){ 1.3002 + var info = new Flog.RayTracer.IntersectionInfo(); 1.3003 + 1.3004 + var Vd = this.position.dot(ray.direction); 1.3005 + if(Vd == 0) return info; // no intersection 1.3006 + 1.3007 + var t = -(this.position.dot(ray.position) + this.d) / Vd; 1.3008 + if(t <= 0) return info; 1.3009 + 1.3010 + info.shape = this; 1.3011 + info.isHit = true; 1.3012 + info.position = Flog.RayTracer.Vector.prototype.add( 1.3013 + ray.position, 1.3014 + Flog.RayTracer.Vector.prototype.multiplyScalar( 1.3015 + ray.direction, 1.3016 + t 1.3017 + ) 1.3018 + ); 1.3019 + info.normal = this.position; 1.3020 + info.distance = t; 1.3021 + 1.3022 + if(this.material.hasTexture){ 1.3023 + var vU = new Flog.RayTracer.Vector(this.position.y, this.position.z, -this.position.x); 1.3024 + var vV = vU.cross(this.position); 1.3025 + var u = info.position.dot(vU); 1.3026 + var v = info.position.dot(vV); 1.3027 + info.color = this.material.getColor(u,v); 1.3028 + } else { 1.3029 + info.color = this.material.getColor(0,0); 1.3030 + } 1.3031 + 1.3032 + return info; 1.3033 + }, 1.3034 + 1.3035 + toString : function () { 1.3036 + return 'Plane [' + this.position + ', d=' + this.d + ']'; 1.3037 + } 1.3038 +} 1.3039 +/* Fake a Flog.* namespace */ 1.3040 +if(typeof(Flog) == 'undefined') var Flog = {}; 1.3041 +if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; 1.3042 + 1.3043 +Flog.RayTracer.IntersectionInfo = Class.create(); 1.3044 + 1.3045 +Flog.RayTracer.IntersectionInfo.prototype = { 1.3046 + isHit: false, 1.3047 + hitCount: 0, 1.3048 + shape: null, 1.3049 + position: null, 1.3050 + normal: null, 1.3051 + color: null, 1.3052 + distance: null, 1.3053 + 1.3054 + initialize : function() { 1.3055 + this.color = new Flog.RayTracer.Color(0,0,0); 1.3056 + }, 1.3057 + 1.3058 + toString : function () { 1.3059 + return 'Intersection [' + this.position + ']'; 1.3060 + } 1.3061 +} 1.3062 +/* Fake a Flog.* namespace */ 1.3063 +if(typeof(Flog) == 'undefined') var Flog = {}; 1.3064 +if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; 1.3065 + 1.3066 +Flog.RayTracer.Camera = Class.create(); 1.3067 + 1.3068 +Flog.RayTracer.Camera.prototype = { 1.3069 + position: null, 1.3070 + lookAt: null, 1.3071 + equator: null, 1.3072 + up: null, 1.3073 + screen: null, 1.3074 + 1.3075 + initialize : function(pos, lookAt, up) { 1.3076 + this.position = pos; 1.3077 + this.lookAt = lookAt; 1.3078 + this.up = up; 1.3079 + this.equator = lookAt.normalize().cross(this.up); 1.3080 + this.screen = Flog.RayTracer.Vector.prototype.add(this.position, this.lookAt); 1.3081 + }, 1.3082 + 1.3083 + getRay: function(vx, vy){ 1.3084 + var pos = Flog.RayTracer.Vector.prototype.subtract( 1.3085 + this.screen, 1.3086 + Flog.RayTracer.Vector.prototype.subtract( 1.3087 + Flog.RayTracer.Vector.prototype.multiplyScalar(this.equator, vx), 1.3088 + Flog.RayTracer.Vector.prototype.multiplyScalar(this.up, vy) 1.3089 + ) 1.3090 + ); 1.3091 + pos.y = pos.y * -1; 1.3092 + var dir = Flog.RayTracer.Vector.prototype.subtract( 1.3093 + pos, 1.3094 + this.position 1.3095 + ); 1.3096 + 1.3097 + var ray = new Flog.RayTracer.Ray(pos, dir.normalize()); 1.3098 + 1.3099 + return ray; 1.3100 + }, 1.3101 + 1.3102 + toString : function () { 1.3103 + return 'Ray []'; 1.3104 + } 1.3105 +} 1.3106 +/* Fake a Flog.* namespace */ 1.3107 +if(typeof(Flog) == 'undefined') var Flog = {}; 1.3108 +if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; 1.3109 + 1.3110 +Flog.RayTracer.Background = Class.create(); 1.3111 + 1.3112 +Flog.RayTracer.Background.prototype = { 1.3113 + color : null, 1.3114 + ambience : 0.0, 1.3115 + 1.3116 + initialize : function(color, ambience) { 1.3117 + this.color = color; 1.3118 + this.ambience = ambience; 1.3119 + } 1.3120 +} 1.3121 +/* Fake a Flog.* namespace */ 1.3122 +if(typeof(Flog) == 'undefined') var Flog = {}; 1.3123 +if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; 1.3124 + 1.3125 +Flog.RayTracer.Engine = Class.create(); 1.3126 + 1.3127 +Flog.RayTracer.Engine.prototype = { 1.3128 + canvas: null, /* 2d context we can render to */ 1.3129 + 1.3130 + initialize: function(options){ 1.3131 + this.options = Object.extend({ 1.3132 + canvasHeight: 100, 1.3133 + canvasWidth: 100, 1.3134 + pixelWidth: 2, 1.3135 + pixelHeight: 2, 1.3136 + renderDiffuse: false, 1.3137 + renderShadows: false, 1.3138 + renderHighlights: false, 1.3139 + renderReflections: false, 1.3140 + rayDepth: 2 1.3141 + }, options || {}); 1.3142 + 1.3143 + this.options.canvasHeight /= this.options.pixelHeight; 1.3144 + this.options.canvasWidth /= this.options.pixelWidth; 1.3145 + 1.3146 + /* TODO: dynamically include other scripts */ 1.3147 + }, 1.3148 + 1.3149 + setPixel: function(x, y, color){ 1.3150 + var pxW, pxH; 1.3151 + pxW = this.options.pixelWidth; 1.3152 + pxH = this.options.pixelHeight; 1.3153 + 1.3154 + if (this.canvas) { 1.3155 + this.canvas.fillStyle = color.toString(); 1.3156 + this.canvas.fillRect (x * pxW, y * pxH, pxW, pxH); 1.3157 + } else { 1.3158 + // print(x * pxW, y * pxH, pxW, pxH); 1.3159 + } 1.3160 + }, 1.3161 + 1.3162 + renderScene: function(scene, canvas){ 1.3163 + /* Get canvas */ 1.3164 + if (canvas) { 1.3165 + this.canvas = canvas.getContext("2d"); 1.3166 + } else { 1.3167 + this.canvas = null; 1.3168 + } 1.3169 + 1.3170 + var canvasHeight = this.options.canvasHeight; 1.3171 + var canvasWidth = this.options.canvasWidth; 1.3172 + 1.3173 + for(var y=0; y < canvasHeight; y++){ 1.3174 + for(var x=0; x < canvasWidth; x++){ 1.3175 + var yp = y * 1.0 / canvasHeight * 2 - 1; 1.3176 + var xp = x * 1.0 / canvasWidth * 2 - 1; 1.3177 + 1.3178 + var ray = scene.camera.getRay(xp, yp); 1.3179 + 1.3180 + var color = this.getPixelColor(ray, scene); 1.3181 + 1.3182 + this.setPixel(x, y, color); 1.3183 + } 1.3184 + } 1.3185 + }, 1.3186 + 1.3187 + getPixelColor: function(ray, scene){ 1.3188 + var info = this.testIntersection(ray, scene, null); 1.3189 + if(info.isHit){ 1.3190 + var color = this.rayTrace(info, ray, scene, 0); 1.3191 + return color; 1.3192 + } 1.3193 + return scene.background.color; 1.3194 + }, 1.3195 + 1.3196 + testIntersection: function(ray, scene, exclude){ 1.3197 + var hits = 0; 1.3198 + var best = new Flog.RayTracer.IntersectionInfo(); 1.3199 + best.distance = 2000; 1.3200 + 1.3201 + for(var i=0; i<scene.shapes.length; i++){ 1.3202 + var shape = scene.shapes[i]; 1.3203 + 1.3204 + if(shape != exclude){ 1.3205 + var info = shape.intersect(ray); 1.3206 + if(info.isHit && info.distance >= 0 && info.distance < best.distance){ 1.3207 + best = info; 1.3208 + hits++; 1.3209 + } 1.3210 + } 1.3211 + } 1.3212 + best.hitCount = hits; 1.3213 + return best; 1.3214 + }, 1.3215 + 1.3216 + getReflectionRay: function(P,N,V){ 1.3217 + var c1 = -N.dot(V); 1.3218 + var R1 = Flog.RayTracer.Vector.prototype.add( 1.3219 + Flog.RayTracer.Vector.prototype.multiplyScalar(N, 2*c1), 1.3220 + V 1.3221 + ); 1.3222 + return new Flog.RayTracer.Ray(P, R1); 1.3223 + }, 1.3224 + 1.3225 + rayTrace: function(info, ray, scene, depth){ 1.3226 + // Calc ambient 1.3227 + var color = Flog.RayTracer.Color.prototype.multiplyScalar(info.color, scene.background.ambience); 1.3228 + var oldColor = color; 1.3229 + var shininess = Math.pow(10, info.shape.material.gloss + 1); 1.3230 + 1.3231 + for(var i=0; i<scene.lights.length; i++){ 1.3232 + var light = scene.lights[i]; 1.3233 + 1.3234 + // Calc diffuse lighting 1.3235 + var v = Flog.RayTracer.Vector.prototype.subtract( 1.3236 + light.position, 1.3237 + info.position 1.3238 + ).normalize(); 1.3239 + 1.3240 + if(this.options.renderDiffuse){ 1.3241 + var L = v.dot(info.normal); 1.3242 + if(L > 0.0){ 1.3243 + color = Flog.RayTracer.Color.prototype.add( 1.3244 + color, 1.3245 + Flog.RayTracer.Color.prototype.multiply( 1.3246 + info.color, 1.3247 + Flog.RayTracer.Color.prototype.multiplyScalar( 1.3248 + light.color, 1.3249 + L 1.3250 + ) 1.3251 + ) 1.3252 + ); 1.3253 + } 1.3254 + } 1.3255 + 1.3256 + // The greater the depth the more accurate the colours, but 1.3257 + // this is exponentially (!) expensive 1.3258 + if(depth <= this.options.rayDepth){ 1.3259 + // calculate reflection ray 1.3260 + if(this.options.renderReflections && info.shape.material.reflection > 0) 1.3261 + { 1.3262 + var reflectionRay = this.getReflectionRay(info.position, info.normal, ray.direction); 1.3263 + var refl = this.testIntersection(reflectionRay, scene, info.shape); 1.3264 + 1.3265 + if (refl.isHit && refl.distance > 0){ 1.3266 + refl.color = this.rayTrace(refl, reflectionRay, scene, depth + 1); 1.3267 + } else { 1.3268 + refl.color = scene.background.color; 1.3269 + } 1.3270 + 1.3271 + color = Flog.RayTracer.Color.prototype.blend( 1.3272 + color, 1.3273 + refl.color, 1.3274 + info.shape.material.reflection 1.3275 + ); 1.3276 + } 1.3277 + 1.3278 + // Refraction 1.3279 + /* TODO */ 1.3280 + } 1.3281 + 1.3282 + /* Render shadows and highlights */ 1.3283 + 1.3284 + var shadowInfo = new Flog.RayTracer.IntersectionInfo(); 1.3285 + 1.3286 + if(this.options.renderShadows){ 1.3287 + var shadowRay = new Flog.RayTracer.Ray(info.position, v); 1.3288 + 1.3289 + shadowInfo = this.testIntersection(shadowRay, scene, info.shape); 1.3290 + if(shadowInfo.isHit && shadowInfo.shape != info.shape /*&& shadowInfo.shape.type != 'PLANE'*/){ 1.3291 + var vA = Flog.RayTracer.Color.prototype.multiplyScalar(color, 0.5); 1.3292 + var dB = (0.5 * Math.pow(shadowInfo.shape.material.transparency, 0.5)); 1.3293 + color = Flog.RayTracer.Color.prototype.addScalar(vA,dB); 1.3294 + } 1.3295 + } 1.3296 + 1.3297 + // Phong specular highlights 1.3298 + if(this.options.renderHighlights && !shadowInfo.isHit && info.shape.material.gloss > 0){ 1.3299 + var Lv = Flog.RayTracer.Vector.prototype.subtract( 1.3300 + info.shape.position, 1.3301 + light.position 1.3302 + ).normalize(); 1.3303 + 1.3304 + var E = Flog.RayTracer.Vector.prototype.subtract( 1.3305 + scene.camera.position, 1.3306 + info.shape.position 1.3307 + ).normalize(); 1.3308 + 1.3309 + var H = Flog.RayTracer.Vector.prototype.subtract( 1.3310 + E, 1.3311 + Lv 1.3312 + ).normalize(); 1.3313 + 1.3314 + var glossWeight = Math.pow(Math.max(info.normal.dot(H), 0), shininess); 1.3315 + color = Flog.RayTracer.Color.prototype.add( 1.3316 + Flog.RayTracer.Color.prototype.multiplyScalar(light.color, glossWeight), 1.3317 + color 1.3318 + ); 1.3319 + } 1.3320 + } 1.3321 + color.limit(); 1.3322 + return color; 1.3323 + } 1.3324 +}; 1.3325 + 1.3326 + 1.3327 +function renderScene(){ 1.3328 + var scene = new Flog.RayTracer.Scene(); 1.3329 + 1.3330 + scene.camera = new Flog.RayTracer.Camera( 1.3331 + new Flog.RayTracer.Vector(0, 0, -15), 1.3332 + new Flog.RayTracer.Vector(-0.2, 0, 5), 1.3333 + new Flog.RayTracer.Vector(0, 1, 0) 1.3334 + ); 1.3335 + 1.3336 + scene.background = new Flog.RayTracer.Background( 1.3337 + new Flog.RayTracer.Color(0.5, 0.5, 0.5), 1.3338 + 0.4 1.3339 + ); 1.3340 + 1.3341 + var sphere = new Flog.RayTracer.Shape.Sphere( 1.3342 + new Flog.RayTracer.Vector(-1.5, 1.5, 2), 1.3343 + 1.5, 1.3344 + new Flog.RayTracer.Material.Solid( 1.3345 + new Flog.RayTracer.Color(0,0.5,0.5), 1.3346 + 0.3, 1.3347 + 0.0, 1.3348 + 0.0, 1.3349 + 2.0 1.3350 + ) 1.3351 + ); 1.3352 + 1.3353 + var sphere1 = new Flog.RayTracer.Shape.Sphere( 1.3354 + new Flog.RayTracer.Vector(1, 0.25, 1), 1.3355 + 0.5, 1.3356 + new Flog.RayTracer.Material.Solid( 1.3357 + new Flog.RayTracer.Color(0.9,0.9,0.9), 1.3358 + 0.1, 1.3359 + 0.0, 1.3360 + 0.0, 1.3361 + 1.5 1.3362 + ) 1.3363 + ); 1.3364 + 1.3365 + var plane = new Flog.RayTracer.Shape.Plane( 1.3366 + new Flog.RayTracer.Vector(0.1, 0.9, -0.5).normalize(), 1.3367 + 1.2, 1.3368 + new Flog.RayTracer.Material.Chessboard( 1.3369 + new Flog.RayTracer.Color(1,1,1), 1.3370 + new Flog.RayTracer.Color(0,0,0), 1.3371 + 0.2, 1.3372 + 0.0, 1.3373 + 1.0, 1.3374 + 0.7 1.3375 + ) 1.3376 + ); 1.3377 + 1.3378 + scene.shapes.push(plane); 1.3379 + scene.shapes.push(sphere); 1.3380 + scene.shapes.push(sphere1); 1.3381 + 1.3382 + var light = new Flog.RayTracer.Light( 1.3383 + new Flog.RayTracer.Vector(5, 10, -1), 1.3384 + new Flog.RayTracer.Color(0.8, 0.8, 0.8) 1.3385 + ); 1.3386 + 1.3387 + var light1 = new Flog.RayTracer.Light( 1.3388 + new Flog.RayTracer.Vector(-3, 5, -15), 1.3389 + new Flog.RayTracer.Color(0.8, 0.8, 0.8), 1.3390 + 100 1.3391 + ); 1.3392 + 1.3393 + scene.lights.push(light); 1.3394 + scene.lights.push(light1); 1.3395 + 1.3396 + var imageWidth = 100; // $F('imageWidth'); 1.3397 + var imageHeight = 100; // $F('imageHeight'); 1.3398 + var pixelSize = "5,5".split(','); // $F('pixelSize').split(','); 1.3399 + var renderDiffuse = true; // $F('renderDiffuse'); 1.3400 + var renderShadows = true; // $F('renderShadows'); 1.3401 + var renderHighlights = true; // $F('renderHighlights'); 1.3402 + var renderReflections = true; // $F('renderReflections'); 1.3403 + var rayDepth = 2;//$F('rayDepth'); 1.3404 + 1.3405 + var raytracer = new Flog.RayTracer.Engine( 1.3406 + { 1.3407 + canvasWidth: imageWidth, 1.3408 + canvasHeight: imageHeight, 1.3409 + pixelWidth: pixelSize[0], 1.3410 + pixelHeight: pixelSize[1], 1.3411 + "renderDiffuse": renderDiffuse, 1.3412 + "renderHighlights": renderHighlights, 1.3413 + "renderShadows": renderShadows, 1.3414 + "renderReflections": renderReflections, 1.3415 + "rayDepth": rayDepth 1.3416 + } 1.3417 + ); 1.3418 + 1.3419 + raytracer.renderScene(scene, null, 0); 1.3420 +} 1.3421 +