OLD | NEW |
(Empty) | |
| 1 <html> |
| 2 <head> |
| 3 <script src="../htmlrunner.js"></script> |
| 4 <script> |
| 5 // The ray tracer code in this file is written by Adam Burmister. It |
| 6 // is available in its original form from: |
| 7 // |
| 8 // http://labs.flog.nz.co/raytracer/ |
| 9 // |
| 10 // It has been modified slightly by Google to work as a standalone |
| 11 // benchmark, but the all the computational code remains |
| 12 // untouched. This file also contains a copy of the Prototype |
| 13 // JavaScript framework which is used by the ray tracer. |
| 14 |
| 15 |
| 16 var checkNumber; |
| 17 |
| 18 // Create dummy objects if we're not running in a browser. |
| 19 if (typeof document == 'undefined') { |
| 20 document = { }; |
| 21 window = { opera: null }; |
| 22 navigator = { userAgent: null, appVersion: "" }; |
| 23 } |
| 24 |
| 25 |
| 26 // ------------------------------------------------------------------------ |
| 27 // ------------------------------------------------------------------------ |
| 28 |
| 29 |
| 30 /* Prototype JavaScript framework, version 1.5.0 |
| 31 * (c) 2005-2007 Sam Stephenson |
| 32 * |
| 33 * Prototype is freely distributable under the terms of an MIT-style license. |
| 34 * For details, see the Prototype web site: http://prototype.conio.net/ |
| 35 * |
| 36 /*--------------------------------------------------------------------------*/ |
| 37 |
| 38 //-------------------- |
| 39 var Prototype = { |
| 40 Version: '1.5.0', |
| 41 BrowserFeatures: { |
| 42 XPath: !!document.evaluate |
| 43 }, |
| 44 |
| 45 ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)', |
| 46 emptyFunction: function() {}, |
| 47 K: function(x) { return x } |
| 48 } |
| 49 |
| 50 var Class = { |
| 51 create: function() { |
| 52 return function() { |
| 53 this.initialize.apply(this, arguments); |
| 54 } |
| 55 } |
| 56 } |
| 57 |
| 58 var Abstract = new Object(); |
| 59 |
| 60 Object.extend = function(destination, source) { |
| 61 for (var property in source) { |
| 62 destination[property] = source[property]; |
| 63 } |
| 64 return destination; |
| 65 } |
| 66 |
| 67 Object.extend(Object, { |
| 68 inspect: function(object) { |
| 69 try { |
| 70 if (object === undefined) return 'undefined'; |
| 71 if (object === null) return 'null'; |
| 72 return object.inspect ? object.inspect() : object.toString(); |
| 73 } catch (e) { |
| 74 if (e instanceof RangeError) return '...'; |
| 75 throw e; |
| 76 } |
| 77 }, |
| 78 |
| 79 keys: function(object) { |
| 80 var keys = []; |
| 81 for (var property in object) |
| 82 keys.push(property); |
| 83 return keys; |
| 84 }, |
| 85 |
| 86 values: function(object) { |
| 87 var values = []; |
| 88 for (var property in object) |
| 89 values.push(object[property]); |
| 90 return values; |
| 91 }, |
| 92 |
| 93 clone: function(object) { |
| 94 return Object.extend({}, object); |
| 95 } |
| 96 }); |
| 97 |
| 98 Function.prototype.bind = function() { |
| 99 var __method = this, args = $A(arguments), object = args.shift(); |
| 100 return function() { |
| 101 return __method.apply(object, args.concat($A(arguments))); |
| 102 } |
| 103 } |
| 104 |
| 105 Function.prototype.bindAsEventListener = function(object) { |
| 106 var __method = this, args = $A(arguments), object = args.shift(); |
| 107 return function(event) { |
| 108 return __method.apply(object, [( event || window.event)].concat(args).concat
($A(arguments))); |
| 109 } |
| 110 } |
| 111 |
| 112 Object.extend(Number.prototype, { |
| 113 toColorPart: function() { |
| 114 var digits = this.toString(16); |
| 115 if (this < 16) return '0' + digits; |
| 116 return digits; |
| 117 }, |
| 118 |
| 119 succ: function() { |
| 120 return this + 1; |
| 121 }, |
| 122 |
| 123 times: function(iterator) { |
| 124 $R(0, this, true).each(iterator); |
| 125 return this; |
| 126 } |
| 127 }); |
| 128 |
| 129 var Try = { |
| 130 these: function() { |
| 131 var returnValue; |
| 132 |
| 133 for (var i = 0, length = arguments.length; i < length; i++) { |
| 134 var lambda = arguments[i]; |
| 135 try { |
| 136 returnValue = lambda(); |
| 137 break; |
| 138 } catch (e) {} |
| 139 } |
| 140 |
| 141 return returnValue; |
| 142 } |
| 143 } |
| 144 |
| 145 /*--------------------------------------------------------------------------*/ |
| 146 |
| 147 var PeriodicalExecuter = Class.create(); |
| 148 PeriodicalExecuter.prototype = { |
| 149 initialize: function(callback, frequency) { |
| 150 this.callback = callback; |
| 151 this.frequency = frequency; |
| 152 this.currentlyExecuting = false; |
| 153 |
| 154 this.registerCallback(); |
| 155 }, |
| 156 |
| 157 registerCallback: function() { |
| 158 this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000
); |
| 159 }, |
| 160 |
| 161 stop: function() { |
| 162 if (!this.timer) return; |
| 163 clearInterval(this.timer); |
| 164 this.timer = null; |
| 165 }, |
| 166 |
| 167 onTimerEvent: function() { |
| 168 if (!this.currentlyExecuting) { |
| 169 try { |
| 170 this.currentlyExecuting = true; |
| 171 this.callback(this); |
| 172 } finally { |
| 173 this.currentlyExecuting = false; |
| 174 } |
| 175 } |
| 176 } |
| 177 } |
| 178 String.interpret = function(value){ |
| 179 return value == null ? '' : String(value); |
| 180 } |
| 181 |
| 182 Object.extend(String.prototype, { |
| 183 gsub: function(pattern, replacement) { |
| 184 var result = '', source = this, match; |
| 185 replacement = arguments.callee.prepareReplacement(replacement); |
| 186 |
| 187 while (source.length > 0) { |
| 188 if (match = source.match(pattern)) { |
| 189 result += source.slice(0, match.index); |
| 190 result += String.interpret(replacement(match)); |
| 191 source = source.slice(match.index + match[0].length); |
| 192 } else { |
| 193 result += source, source = ''; |
| 194 } |
| 195 } |
| 196 return result; |
| 197 }, |
| 198 |
| 199 sub: function(pattern, replacement, count) { |
| 200 replacement = this.gsub.prepareReplacement(replacement); |
| 201 count = count === undefined ? 1 : count; |
| 202 |
| 203 return this.gsub(pattern, function(match) { |
| 204 if (--count < 0) return match[0]; |
| 205 return replacement(match); |
| 206 }); |
| 207 }, |
| 208 |
| 209 scan: function(pattern, iterator) { |
| 210 this.gsub(pattern, iterator); |
| 211 return this; |
| 212 }, |
| 213 |
| 214 truncate: function(length, truncation) { |
| 215 length = length || 30; |
| 216 truncation = truncation === undefined ? '...' : truncation; |
| 217 return this.length > length ? |
| 218 this.slice(0, length - truncation.length) + truncation : this; |
| 219 }, |
| 220 |
| 221 strip: function() { |
| 222 return this.replace(/^\s+/, '').replace(/\s+$/, ''); |
| 223 }, |
| 224 |
| 225 stripTags: function() { |
| 226 return this.replace(/<\/?[^>]+>/gi, ''); |
| 227 }, |
| 228 |
| 229 stripScripts: function() { |
| 230 return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); |
| 231 }, |
| 232 |
| 233 extractScripts: function() { |
| 234 var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); |
| 235 var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); |
| 236 return (this.match(matchAll) || []).map(function(scriptTag) { |
| 237 return (scriptTag.match(matchOne) || ['', ''])[1]; |
| 238 }); |
| 239 }, |
| 240 |
| 241 evalScripts: function() { |
| 242 return this.extractScripts().map(function(script) { return eval(script) }); |
| 243 }, |
| 244 |
| 245 escapeHTML: function() { |
| 246 var div = document.createElement('div'); |
| 247 var text = document.createTextNode(this); |
| 248 div.appendChild(text); |
| 249 return div.innerHTML; |
| 250 }, |
| 251 |
| 252 unescapeHTML: function() { |
| 253 var div = document.createElement('div'); |
| 254 div.innerHTML = this.stripTags(); |
| 255 return div.childNodes[0] ? (div.childNodes.length > 1 ? |
| 256 $A(div.childNodes).inject('',function(memo,node){ return memo+node.nodeVal
ue }) : |
| 257 div.childNodes[0].nodeValue) : ''; |
| 258 }, |
| 259 |
| 260 toQueryParams: function(separator) { |
| 261 var match = this.strip().match(/([^?#]*)(#.*)?$/); |
| 262 if (!match) return {}; |
| 263 |
| 264 return match[1].split(separator || '&').inject({}, function(hash, pair) { |
| 265 if ((pair = pair.split('='))[0]) { |
| 266 var name = decodeURIComponent(pair[0]); |
| 267 var value = pair[1] ? decodeURIComponent(pair[1]) : undefined; |
| 268 |
| 269 if (hash[name] !== undefined) { |
| 270 if (hash[name].constructor != Array) |
| 271 hash[name] = [hash[name]]; |
| 272 if (value) hash[name].push(value); |
| 273 } |
| 274 else hash[name] = value; |
| 275 } |
| 276 return hash; |
| 277 }); |
| 278 }, |
| 279 |
| 280 toArray: function() { |
| 281 return this.split(''); |
| 282 }, |
| 283 |
| 284 succ: function() { |
| 285 return this.slice(0, this.length - 1) + |
| 286 String.fromCharCode(this.charCodeAt(this.length - 1) + 1); |
| 287 }, |
| 288 |
| 289 camelize: function() { |
| 290 var parts = this.split('-'), len = parts.length; |
| 291 if (len == 1) return parts[0]; |
| 292 |
| 293 var camelized = this.charAt(0) == '-' |
| 294 ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) |
| 295 : parts[0]; |
| 296 |
| 297 for (var i = 1; i < len; i++) |
| 298 camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); |
| 299 |
| 300 return camelized; |
| 301 }, |
| 302 |
| 303 capitalize: function(){ |
| 304 return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); |
| 305 }, |
| 306 |
| 307 underscore: function() { |
| 308 return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/(
[a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); |
| 309 }, |
| 310 |
| 311 dasherize: function() { |
| 312 return this.gsub(/_/,'-'); |
| 313 }, |
| 314 |
| 315 inspect: function(useDoubleQuotes) { |
| 316 var escapedString = this.replace(/\\/g, '\\\\'); |
| 317 if (useDoubleQuotes) |
| 318 return '"' + escapedString.replace(/"/g, '\\"') + '"'; |
| 319 else |
| 320 return "'" + escapedString.replace(/'/g, '\\\'') + "'"; |
| 321 } |
| 322 }); |
| 323 |
| 324 String.prototype.gsub.prepareReplacement = function(replacement) { |
| 325 if (typeof replacement == 'function') return replacement; |
| 326 var template = new Template(replacement); |
| 327 return function(match) { return template.evaluate(match) }; |
| 328 } |
| 329 |
| 330 String.prototype.parseQuery = String.prototype.toQueryParams; |
| 331 |
| 332 var Template = Class.create(); |
| 333 Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; |
| 334 Template.prototype = { |
| 335 initialize: function(template, pattern) { |
| 336 this.template = template.toString(); |
| 337 this.pattern = pattern || Template.Pattern; |
| 338 }, |
| 339 |
| 340 evaluate: function(object) { |
| 341 return this.template.gsub(this.pattern, function(match) { |
| 342 var before = match[1]; |
| 343 if (before == '\\') return match[2]; |
| 344 return before + String.interpret(object[match[3]]); |
| 345 }); |
| 346 } |
| 347 } |
| 348 |
| 349 var $break = new Object(); |
| 350 var $continue = new Object(); |
| 351 |
| 352 var Enumerable = { |
| 353 each: function(iterator) { |
| 354 var index = 0; |
| 355 try { |
| 356 this._each(function(value) { |
| 357 try { |
| 358 iterator(value, index++); |
| 359 } catch (e) { |
| 360 if (e != $continue) throw e; |
| 361 } |
| 362 }); |
| 363 } catch (e) { |
| 364 if (e != $break) throw e; |
| 365 } |
| 366 return this; |
| 367 }, |
| 368 |
| 369 eachSlice: function(number, iterator) { |
| 370 var index = -number, slices = [], array = this.toArray(); |
| 371 while ((index += number) < array.length) |
| 372 slices.push(array.slice(index, index+number)); |
| 373 return slices.map(iterator); |
| 374 }, |
| 375 |
| 376 all: function(iterator) { |
| 377 var result = true; |
| 378 this.each(function(value, index) { |
| 379 result = result && !!(iterator || Prototype.K)(value, index); |
| 380 if (!result) throw $break; |
| 381 }); |
| 382 return result; |
| 383 }, |
| 384 |
| 385 any: function(iterator) { |
| 386 var result = false; |
| 387 this.each(function(value, index) { |
| 388 if (result = !!(iterator || Prototype.K)(value, index)) |
| 389 throw $break; |
| 390 }); |
| 391 return result; |
| 392 }, |
| 393 |
| 394 collect: function(iterator) { |
| 395 var results = []; |
| 396 this.each(function(value, index) { |
| 397 results.push((iterator || Prototype.K)(value, index)); |
| 398 }); |
| 399 return results; |
| 400 }, |
| 401 |
| 402 detect: function(iterator) { |
| 403 var result; |
| 404 this.each(function(value, index) { |
| 405 if (iterator(value, index)) { |
| 406 result = value; |
| 407 throw $break; |
| 408 } |
| 409 }); |
| 410 return result; |
| 411 }, |
| 412 |
| 413 findAll: function(iterator) { |
| 414 var results = []; |
| 415 this.each(function(value, index) { |
| 416 if (iterator(value, index)) |
| 417 results.push(value); |
| 418 }); |
| 419 return results; |
| 420 }, |
| 421 |
| 422 grep: function(pattern, iterator) { |
| 423 var results = []; |
| 424 this.each(function(value, index) { |
| 425 var stringValue = value.toString(); |
| 426 if (stringValue.match(pattern)) |
| 427 results.push((iterator || Prototype.K)(value, index)); |
| 428 }) |
| 429 return results; |
| 430 }, |
| 431 |
| 432 include: function(object) { |
| 433 var found = false; |
| 434 this.each(function(value) { |
| 435 if (value == object) { |
| 436 found = true; |
| 437 throw $break; |
| 438 } |
| 439 }); |
| 440 return found; |
| 441 }, |
| 442 |
| 443 inGroupsOf: function(number, fillWith) { |
| 444 fillWith = fillWith === undefined ? null : fillWith; |
| 445 return this.eachSlice(number, function(slice) { |
| 446 while(slice.length < number) slice.push(fillWith); |
| 447 return slice; |
| 448 }); |
| 449 }, |
| 450 |
| 451 inject: function(memo, iterator) { |
| 452 this.each(function(value, index) { |
| 453 memo = iterator(memo, value, index); |
| 454 }); |
| 455 return memo; |
| 456 }, |
| 457 |
| 458 invoke: function(method) { |
| 459 var args = $A(arguments).slice(1); |
| 460 return this.map(function(value) { |
| 461 return value[method].apply(value, args); |
| 462 }); |
| 463 }, |
| 464 |
| 465 max: function(iterator) { |
| 466 var result; |
| 467 this.each(function(value, index) { |
| 468 value = (iterator || Prototype.K)(value, index); |
| 469 if (result == undefined || value >= result) |
| 470 result = value; |
| 471 }); |
| 472 return result; |
| 473 }, |
| 474 |
| 475 min: function(iterator) { |
| 476 var result; |
| 477 this.each(function(value, index) { |
| 478 value = (iterator || Prototype.K)(value, index); |
| 479 if (result == undefined || value < result) |
| 480 result = value; |
| 481 }); |
| 482 return result; |
| 483 }, |
| 484 |
| 485 partition: function(iterator) { |
| 486 var trues = [], falses = []; |
| 487 this.each(function(value, index) { |
| 488 ((iterator || Prototype.K)(value, index) ? |
| 489 trues : falses).push(value); |
| 490 }); |
| 491 return [trues, falses]; |
| 492 }, |
| 493 |
| 494 pluck: function(property) { |
| 495 var results = []; |
| 496 this.each(function(value, index) { |
| 497 results.push(value[property]); |
| 498 }); |
| 499 return results; |
| 500 }, |
| 501 |
| 502 reject: function(iterator) { |
| 503 var results = []; |
| 504 this.each(function(value, index) { |
| 505 if (!iterator(value, index)) |
| 506 results.push(value); |
| 507 }); |
| 508 return results; |
| 509 }, |
| 510 |
| 511 sortBy: function(iterator) { |
| 512 return this.map(function(value, index) { |
| 513 return {value: value, criteria: iterator(value, index)}; |
| 514 }).sort(function(left, right) { |
| 515 var a = left.criteria, b = right.criteria; |
| 516 return a < b ? -1 : a > b ? 1 : 0; |
| 517 }).pluck('value'); |
| 518 }, |
| 519 |
| 520 toArray: function() { |
| 521 return this.map(); |
| 522 }, |
| 523 |
| 524 zip: function() { |
| 525 var iterator = Prototype.K, args = $A(arguments); |
| 526 if (typeof args.last() == 'function') |
| 527 iterator = args.pop(); |
| 528 |
| 529 var collections = [this].concat(args).map($A); |
| 530 return this.map(function(value, index) { |
| 531 return iterator(collections.pluck(index)); |
| 532 }); |
| 533 }, |
| 534 |
| 535 size: function() { |
| 536 return this.toArray().length; |
| 537 }, |
| 538 |
| 539 inspect: function() { |
| 540 return '#<Enumerable:' + this.toArray().inspect() + '>'; |
| 541 } |
| 542 } |
| 543 |
| 544 Object.extend(Enumerable, { |
| 545 map: Enumerable.collect, |
| 546 find: Enumerable.detect, |
| 547 select: Enumerable.findAll, |
| 548 member: Enumerable.include, |
| 549 entries: Enumerable.toArray |
| 550 }); |
| 551 var $A = Array.from = function(iterable) { |
| 552 if (!iterable) return []; |
| 553 if (iterable.toArray) { |
| 554 return iterable.toArray(); |
| 555 } else { |
| 556 var results = []; |
| 557 for (var i = 0, length = iterable.length; i < length; i++) |
| 558 results.push(iterable[i]); |
| 559 return results; |
| 560 } |
| 561 } |
| 562 |
| 563 Object.extend(Array.prototype, Enumerable); |
| 564 |
| 565 if (!Array.prototype._reverse) |
| 566 Array.prototype._reverse = Array.prototype.reverse; |
| 567 |
| 568 Object.extend(Array.prototype, { |
| 569 _each: function(iterator) { |
| 570 for (var i = 0, length = this.length; i < length; i++) |
| 571 iterator(this[i]); |
| 572 }, |
| 573 |
| 574 clear: function() { |
| 575 this.length = 0; |
| 576 return this; |
| 577 }, |
| 578 |
| 579 first: function() { |
| 580 return this[0]; |
| 581 }, |
| 582 |
| 583 last: function() { |
| 584 return this[this.length - 1]; |
| 585 }, |
| 586 |
| 587 compact: function() { |
| 588 return this.select(function(value) { |
| 589 return value != null; |
| 590 }); |
| 591 }, |
| 592 |
| 593 flatten: function() { |
| 594 return this.inject([], function(array, value) { |
| 595 return array.concat(value && value.constructor == Array ? |
| 596 value.flatten() : [value]); |
| 597 }); |
| 598 }, |
| 599 |
| 600 without: function() { |
| 601 var values = $A(arguments); |
| 602 return this.select(function(value) { |
| 603 return !values.include(value); |
| 604 }); |
| 605 }, |
| 606 |
| 607 indexOf: function(object) { |
| 608 for (var i = 0, length = this.length; i < length; i++) |
| 609 if (this[i] == object) return i; |
| 610 return -1; |
| 611 }, |
| 612 |
| 613 reverse: function(inline) { |
| 614 return (inline !== false ? this : this.toArray())._reverse(); |
| 615 }, |
| 616 |
| 617 reduce: function() { |
| 618 return this.length > 1 ? this : this[0]; |
| 619 }, |
| 620 |
| 621 uniq: function() { |
| 622 return this.inject([], function(array, value) { |
| 623 return array.include(value) ? array : array.concat([value]); |
| 624 }); |
| 625 }, |
| 626 |
| 627 clone: function() { |
| 628 return [].concat(this); |
| 629 }, |
| 630 |
| 631 size: function() { |
| 632 return this.length; |
| 633 }, |
| 634 |
| 635 inspect: function() { |
| 636 return '[' + this.map(Object.inspect).join(', ') + ']'; |
| 637 } |
| 638 }); |
| 639 |
| 640 Array.prototype.toArray = Array.prototype.clone; |
| 641 |
| 642 function $w(string){ |
| 643 string = string.strip(); |
| 644 return string ? string.split(/\s+/) : []; |
| 645 } |
| 646 |
| 647 if(window.opera){ |
| 648 Array.prototype.concat = function(){ |
| 649 var array = []; |
| 650 for(var i = 0, length = this.length; i < length; i++) array.push(this[i]); |
| 651 for(var i = 0, length = arguments.length; i < length; i++) { |
| 652 if(arguments[i].constructor == Array) { |
| 653 for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) |
| 654 array.push(arguments[i][j]); |
| 655 } else { |
| 656 array.push(arguments[i]); |
| 657 } |
| 658 } |
| 659 return array; |
| 660 } |
| 661 } |
| 662 var Hash = function(obj) { |
| 663 Object.extend(this, obj || {}); |
| 664 }; |
| 665 |
| 666 Object.extend(Hash, { |
| 667 toQueryString: function(obj) { |
| 668 var parts = []; |
| 669 |
| 670 this.prototype._each.call(obj, function(pair) { |
| 671 if (!pair.key) return; |
| 672 |
| 673 if (pair.value && pair.value.constructor == Array) { |
| 674 var values = pair.value.compact(); |
| 675 if (values.length < 2) pair.value = values.reduce(); |
| 676 else { |
| 677 key = encodeURIComponent(pair.key); |
| 678 values.each(function(value) { |
| 679 value = value != undefined ? encodeURIComponent(value) : ''; |
| 680 parts.push(key + '=' + encodeURIComponent(value)); |
| 681 }); |
| 682 return; |
| 683 } |
| 684 } |
| 685 if (pair.value == undefined) pair[1] = ''; |
| 686 parts.push(pair.map(encodeURIComponent).join('=')); |
| 687 }); |
| 688 |
| 689 return parts.join('&'); |
| 690 } |
| 691 }); |
| 692 |
| 693 Object.extend(Hash.prototype, Enumerable); |
| 694 Object.extend(Hash.prototype, { |
| 695 _each: function(iterator) { |
| 696 for (var key in this) { |
| 697 var value = this[key]; |
| 698 if (value && value == Hash.prototype[key]) continue; |
| 699 |
| 700 var pair = [key, value]; |
| 701 pair.key = key; |
| 702 pair.value = value; |
| 703 iterator(pair); |
| 704 } |
| 705 }, |
| 706 |
| 707 keys: function() { |
| 708 return this.pluck('key'); |
| 709 }, |
| 710 |
| 711 values: function() { |
| 712 return this.pluck('value'); |
| 713 }, |
| 714 |
| 715 merge: function(hash) { |
| 716 return $H(hash).inject(this, function(mergedHash, pair) { |
| 717 mergedHash[pair.key] = pair.value; |
| 718 return mergedHash; |
| 719 }); |
| 720 }, |
| 721 |
| 722 remove: function() { |
| 723 var result; |
| 724 for(var i = 0, length = arguments.length; i < length; i++) { |
| 725 var value = this[arguments[i]]; |
| 726 if (value !== undefined){ |
| 727 if (result === undefined) result = value; |
| 728 else { |
| 729 if (result.constructor != Array) result = [result]; |
| 730 result.push(value) |
| 731 } |
| 732 } |
| 733 delete this[arguments[i]]; |
| 734 } |
| 735 return result; |
| 736 }, |
| 737 |
| 738 toQueryString: function() { |
| 739 return Hash.toQueryString(this); |
| 740 }, |
| 741 |
| 742 inspect: function() { |
| 743 return '#<Hash:{' + this.map(function(pair) { |
| 744 return pair.map(Object.inspect).join(': '); |
| 745 }).join(', ') + '}>'; |
| 746 } |
| 747 }); |
| 748 |
| 749 function $H(object) { |
| 750 if (object && object.constructor == Hash) return object; |
| 751 return new Hash(object); |
| 752 }; |
| 753 ObjectRange = Class.create(); |
| 754 Object.extend(ObjectRange.prototype, Enumerable); |
| 755 Object.extend(ObjectRange.prototype, { |
| 756 initialize: function(start, end, exclusive) { |
| 757 this.start = start; |
| 758 this.end = end; |
| 759 this.exclusive = exclusive; |
| 760 }, |
| 761 |
| 762 _each: function(iterator) { |
| 763 var value = this.start; |
| 764 while (this.include(value)) { |
| 765 iterator(value); |
| 766 value = value.succ(); |
| 767 } |
| 768 }, |
| 769 |
| 770 include: function(value) { |
| 771 if (value < this.start) |
| 772 return false; |
| 773 if (this.exclusive) |
| 774 return value < this.end; |
| 775 return value <= this.end; |
| 776 } |
| 777 }); |
| 778 |
| 779 var $R = function(start, end, exclusive) { |
| 780 return new ObjectRange(start, end, exclusive); |
| 781 } |
| 782 |
| 783 var Ajax = { |
| 784 getTransport: function() { |
| 785 return Try.these( |
| 786 function() {return new XMLHttpRequest()}, |
| 787 function() {return new ActiveXObject('Msxml2.XMLHTTP')}, |
| 788 function() {return new ActiveXObject('Microsoft.XMLHTTP')} |
| 789 ) || false; |
| 790 }, |
| 791 |
| 792 activeRequestCount: 0 |
| 793 } |
| 794 |
| 795 Ajax.Responders = { |
| 796 responders: [], |
| 797 |
| 798 _each: function(iterator) { |
| 799 this.responders._each(iterator); |
| 800 }, |
| 801 |
| 802 register: function(responder) { |
| 803 if (!this.include(responder)) |
| 804 this.responders.push(responder); |
| 805 }, |
| 806 |
| 807 unregister: function(responder) { |
| 808 this.responders = this.responders.without(responder); |
| 809 }, |
| 810 |
| 811 dispatch: function(callback, request, transport, json) { |
| 812 this.each(function(responder) { |
| 813 if (typeof responder[callback] == 'function') { |
| 814 try { |
| 815 responder[callback].apply(responder, [request, transport, json]); |
| 816 } catch (e) {} |
| 817 } |
| 818 }); |
| 819 } |
| 820 }; |
| 821 |
| 822 Object.extend(Ajax.Responders, Enumerable); |
| 823 |
| 824 Ajax.Responders.register({ |
| 825 onCreate: function() { |
| 826 Ajax.activeRequestCount++; |
| 827 }, |
| 828 onComplete: function() { |
| 829 Ajax.activeRequestCount--; |
| 830 } |
| 831 }); |
| 832 |
| 833 Ajax.Base = function() {}; |
| 834 Ajax.Base.prototype = { |
| 835 setOptions: function(options) { |
| 836 this.options = { |
| 837 method: 'post', |
| 838 asynchronous: true, |
| 839 contentType: 'application/x-www-form-urlencoded', |
| 840 encoding: 'UTF-8', |
| 841 parameters: '' |
| 842 } |
| 843 Object.extend(this.options, options || {}); |
| 844 |
| 845 this.options.method = this.options.method.toLowerCase(); |
| 846 if (typeof this.options.parameters == 'string') |
| 847 this.options.parameters = this.options.parameters.toQueryParams(); |
| 848 } |
| 849 } |
| 850 |
| 851 Ajax.Request = Class.create(); |
| 852 Ajax.Request.Events = |
| 853 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; |
| 854 |
| 855 Ajax.Request.prototype = Object.extend(new Ajax.Base(), { |
| 856 _complete: false, |
| 857 |
| 858 initialize: function(url, options) { |
| 859 this.transport = Ajax.getTransport(); |
| 860 this.setOptions(options); |
| 861 this.request(url); |
| 862 }, |
| 863 |
| 864 request: function(url) { |
| 865 this.url = url; |
| 866 this.method = this.options.method; |
| 867 var params = this.options.parameters; |
| 868 |
| 869 if (!['get', 'post'].include(this.method)) { |
| 870 // simulate other verbs over post |
| 871 params['_method'] = this.method; |
| 872 this.method = 'post'; |
| 873 } |
| 874 |
| 875 params = Hash.toQueryString(params); |
| 876 if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params +=
'&_=' |
| 877 |
| 878 // when GET, append parameters to URL |
| 879 if (this.method == 'get' && params) |
| 880 this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params; |
| 881 |
| 882 try { |
| 883 Ajax.Responders.dispatch('onCreate', this, this.transport); |
| 884 |
| 885 this.transport.open(this.method.toUpperCase(), this.url, |
| 886 this.options.asynchronous); |
| 887 |
| 888 if (this.options.asynchronous) |
| 889 setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); |
| 890 |
| 891 this.transport.onreadystatechange = this.onStateChange.bind(this); |
| 892 this.setRequestHeaders(); |
| 893 |
| 894 var body = this.method == 'post' ? (this.options.postBody || params) : nul
l; |
| 895 |
| 896 this.transport.send(body); |
| 897 |
| 898 /* Force Firefox to handle ready state 4 for synchronous requests */ |
| 899 if (!this.options.asynchronous && this.transport.overrideMimeType) |
| 900 this.onStateChange(); |
| 901 |
| 902 } |
| 903 catch (e) { |
| 904 this.dispatchException(e); |
| 905 } |
| 906 }, |
| 907 |
| 908 onStateChange: function() { |
| 909 var readyState = this.transport.readyState; |
| 910 if (readyState > 1 && !((readyState == 4) && this._complete)) |
| 911 this.respondToReadyState(this.transport.readyState); |
| 912 }, |
| 913 |
| 914 setRequestHeaders: function() { |
| 915 var headers = { |
| 916 'X-Requested-With': 'XMLHttpRequest', |
| 917 'X-Prototype-Version': Prototype.Version, |
| 918 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' |
| 919 }; |
| 920 |
| 921 if (this.method == 'post') { |
| 922 headers['Content-type'] = this.options.contentType + |
| 923 (this.options.encoding ? '; charset=' + this.options.encoding : ''); |
| 924 |
| 925 /* Force "Connection: close" for older Mozilla browsers to work |
| 926 * around a bug where XMLHttpRequest sends an incorrect |
| 927 * Content-length header. See Mozilla Bugzilla #246651. |
| 928 */ |
| 929 if (this.transport.overrideMimeType && |
| 930 (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) |
| 931 headers['Connection'] = 'close'; |
| 932 } |
| 933 |
| 934 // user-defined headers |
| 935 if (typeof this.options.requestHeaders == 'object') { |
| 936 var extras = this.options.requestHeaders; |
| 937 |
| 938 if (typeof extras.push == 'function') |
| 939 for (var i = 0, length = extras.length; i < length; i += 2) |
| 940 headers[extras[i]] = extras[i+1]; |
| 941 else |
| 942 $H(extras).each(function(pair) { headers[pair.key] = pair.value }); |
| 943 } |
| 944 |
| 945 for (var name in headers) |
| 946 this.transport.setRequestHeader(name, headers[name]); |
| 947 }, |
| 948 |
| 949 success: function() { |
| 950 return !this.transport.status |
| 951 || (this.transport.status >= 200 && this.transport.status < 300); |
| 952 }, |
| 953 |
| 954 respondToReadyState: function(readyState) { |
| 955 var state = Ajax.Request.Events[readyState]; |
| 956 var transport = this.transport, json = this.evalJSON(); |
| 957 |
| 958 if (state == 'Complete') { |
| 959 try { |
| 960 this._complete = true; |
| 961 (this.options['on' + this.transport.status] |
| 962 || this.options['on' + (this.success() ? 'Success' : 'Failure')] |
| 963 || Prototype.emptyFunction)(transport, json); |
| 964 } catch (e) { |
| 965 this.dispatchException(e); |
| 966 } |
| 967 |
| 968 if ((this.getHeader('Content-type') || 'text/javascript').strip(). |
| 969 match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) |
| 970 this.evalResponse(); |
| 971 } |
| 972 |
| 973 try { |
| 974 (this.options['on' + state] || Prototype.emptyFunction)(transport, json); |
| 975 Ajax.Responders.dispatch('on' + state, this, transport, json); |
| 976 } catch (e) { |
| 977 this.dispatchException(e); |
| 978 } |
| 979 |
| 980 if (state == 'Complete') { |
| 981 // avoid memory leak in MSIE: clean up |
| 982 this.transport.onreadystatechange = Prototype.emptyFunction; |
| 983 } |
| 984 }, |
| 985 |
| 986 getHeader: function(name) { |
| 987 try { |
| 988 return this.transport.getResponseHeader(name); |
| 989 } catch (e) { return null } |
| 990 }, |
| 991 |
| 992 evalJSON: function() { |
| 993 try { |
| 994 var json = this.getHeader('X-JSON'); |
| 995 return json ? eval('(' + json + ')') : null; |
| 996 } catch (e) { return null } |
| 997 }, |
| 998 |
| 999 evalResponse: function() { |
| 1000 try { |
| 1001 return eval(this.transport.responseText); |
| 1002 } catch (e) { |
| 1003 this.dispatchException(e); |
| 1004 } |
| 1005 }, |
| 1006 |
| 1007 dispatchException: function(exception) { |
| 1008 (this.options.onException || Prototype.emptyFunction)(this, exception); |
| 1009 Ajax.Responders.dispatch('onException', this, exception); |
| 1010 } |
| 1011 }); |
| 1012 |
| 1013 Ajax.Updater = Class.create(); |
| 1014 |
| 1015 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { |
| 1016 initialize: function(container, url, options) { |
| 1017 this.container = { |
| 1018 success: (container.success || container), |
| 1019 failure: (container.failure || (container.success ? null : container)) |
| 1020 } |
| 1021 |
| 1022 this.transport = Ajax.getTransport(); |
| 1023 this.setOptions(options); |
| 1024 |
| 1025 var onComplete = this.options.onComplete || Prototype.emptyFunction; |
| 1026 this.options.onComplete = (function(transport, param) { |
| 1027 this.updateContent(); |
| 1028 onComplete(transport, param); |
| 1029 }).bind(this); |
| 1030 |
| 1031 this.request(url); |
| 1032 }, |
| 1033 |
| 1034 updateContent: function() { |
| 1035 var receiver = this.container[this.success() ? 'success' : 'failure']; |
| 1036 var response = this.transport.responseText; |
| 1037 |
| 1038 if (!this.options.evalScripts) response = response.stripScripts(); |
| 1039 |
| 1040 if (receiver = $(receiver)) { |
| 1041 if (this.options.insertion) |
| 1042 new this.options.insertion(receiver, response); |
| 1043 else |
| 1044 receiver.update(response); |
| 1045 } |
| 1046 |
| 1047 if (this.success()) { |
| 1048 if (this.onComplete) |
| 1049 setTimeout(this.onComplete.bind(this), 10); |
| 1050 } |
| 1051 } |
| 1052 }); |
| 1053 |
| 1054 Ajax.PeriodicalUpdater = Class.create(); |
| 1055 Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { |
| 1056 initialize: function(container, url, options) { |
| 1057 this.setOptions(options); |
| 1058 this.onComplete = this.options.onComplete; |
| 1059 |
| 1060 this.frequency = (this.options.frequency || 2); |
| 1061 this.decay = (this.options.decay || 1); |
| 1062 |
| 1063 this.updater = {}; |
| 1064 this.container = container; |
| 1065 this.url = url; |
| 1066 |
| 1067 this.start(); |
| 1068 }, |
| 1069 |
| 1070 start: function() { |
| 1071 this.options.onComplete = this.updateComplete.bind(this); |
| 1072 this.onTimerEvent(); |
| 1073 }, |
| 1074 |
| 1075 stop: function() { |
| 1076 this.updater.options.onComplete = undefined; |
| 1077 clearTimeout(this.timer); |
| 1078 (this.onComplete || Prototype.emptyFunction).apply(this, arguments); |
| 1079 }, |
| 1080 |
| 1081 updateComplete: function(request) { |
| 1082 if (this.options.decay) { |
| 1083 this.decay = (request.responseText == this.lastText ? |
| 1084 this.decay * this.options.decay : 1); |
| 1085 |
| 1086 this.lastText = request.responseText; |
| 1087 } |
| 1088 this.timer = setTimeout(this.onTimerEvent.bind(this), |
| 1089 this.decay * this.frequency * 1000); |
| 1090 }, |
| 1091 |
| 1092 onTimerEvent: function() { |
| 1093 this.updater = new Ajax.Updater(this.container, this.url, this.options); |
| 1094 } |
| 1095 }); |
| 1096 function $(element) { |
| 1097 if (arguments.length > 1) { |
| 1098 for (var i = 0, elements = [], length = arguments.length; i < length; i++) |
| 1099 elements.push($(arguments[i])); |
| 1100 return elements; |
| 1101 } |
| 1102 if (typeof element == 'string') |
| 1103 element = document.getElementById(element); |
| 1104 return Element.extend(element); |
| 1105 } |
| 1106 |
| 1107 if (Prototype.BrowserFeatures.XPath) { |
| 1108 document._getElementsByXPath = function(expression, parentElement) { |
| 1109 var results = []; |
| 1110 var query = document.evaluate(expression, $(parentElement) || document, |
| 1111 null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); |
| 1112 for (var i = 0, length = query.snapshotLength; i < length; i++) |
| 1113 results.push(query.snapshotItem(i)); |
| 1114 return results; |
| 1115 }; |
| 1116 } |
| 1117 |
| 1118 document.getElementsByClassName = function(className, parentElement) { |
| 1119 if (Prototype.BrowserFeatures.XPath) { |
| 1120 var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; |
| 1121 return document._getElementsByXPath(q, parentElement); |
| 1122 } else { |
| 1123 var children = ($(parentElement) || document.body).getElementsByTagName('*')
; |
| 1124 var elements = [], child; |
| 1125 for (var i = 0, length = children.length; i < length; i++) { |
| 1126 child = children[i]; |
| 1127 if (Element.hasClassName(child, className)) |
| 1128 elements.push(Element.extend(child)); |
| 1129 } |
| 1130 return elements; |
| 1131 } |
| 1132 }; |
| 1133 |
| 1134 /*--------------------------------------------------------------------------*/ |
| 1135 |
| 1136 if (!window.Element) |
| 1137 var Element = new Object(); |
| 1138 |
| 1139 Element.extend = function(element) { |
| 1140 if (!element || _nativeExtensions || element.nodeType == 3) return element; |
| 1141 |
| 1142 if (!element._extended && element.tagName && element != window) { |
| 1143 var methods = Object.clone(Element.Methods), cache = Element.extend.cache; |
| 1144 |
| 1145 if (element.tagName == 'FORM') |
| 1146 Object.extend(methods, Form.Methods); |
| 1147 if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName)) |
| 1148 Object.extend(methods, Form.Element.Methods); |
| 1149 |
| 1150 Object.extend(methods, Element.Methods.Simulated); |
| 1151 |
| 1152 for (var property in methods) { |
| 1153 var value = methods[property]; |
| 1154 if (typeof value == 'function' && !(property in element)) |
| 1155 element[property] = cache.findOrStore(value); |
| 1156 } |
| 1157 } |
| 1158 |
| 1159 element._extended = true; |
| 1160 return element; |
| 1161 }; |
| 1162 |
| 1163 Element.extend.cache = { |
| 1164 findOrStore: function(value) { |
| 1165 return this[value] = this[value] || function() { |
| 1166 return value.apply(null, [this].concat($A(arguments))); |
| 1167 } |
| 1168 } |
| 1169 }; |
| 1170 |
| 1171 Element.Methods = { |
| 1172 visible: function(element) { |
| 1173 return $(element).style.display != 'none'; |
| 1174 }, |
| 1175 |
| 1176 toggle: function(element) { |
| 1177 element = $(element); |
| 1178 Element[Element.visible(element) ? 'hide' : 'show'](element); |
| 1179 return element; |
| 1180 }, |
| 1181 |
| 1182 hide: function(element) { |
| 1183 $(element).style.display = 'none'; |
| 1184 return element; |
| 1185 }, |
| 1186 |
| 1187 show: function(element) { |
| 1188 $(element).style.display = ''; |
| 1189 return element; |
| 1190 }, |
| 1191 |
| 1192 remove: function(element) { |
| 1193 element = $(element); |
| 1194 element.parentNode.removeChild(element); |
| 1195 return element; |
| 1196 }, |
| 1197 |
| 1198 update: function(element, html) { |
| 1199 html = typeof html == 'undefined' ? '' : html.toString(); |
| 1200 $(element).innerHTML = html.stripScripts(); |
| 1201 setTimeout(function() {html.evalScripts()}, 10); |
| 1202 return element; |
| 1203 }, |
| 1204 |
| 1205 replace: function(element, html) { |
| 1206 element = $(element); |
| 1207 html = typeof html == 'undefined' ? '' : html.toString(); |
| 1208 if (element.outerHTML) { |
| 1209 element.outerHTML = html.stripScripts(); |
| 1210 } else { |
| 1211 var range = element.ownerDocument.createRange(); |
| 1212 range.selectNodeContents(element); |
| 1213 element.parentNode.replaceChild( |
| 1214 range.createContextualFragment(html.stripScripts()), element); |
| 1215 } |
| 1216 setTimeout(function() {html.evalScripts()}, 10); |
| 1217 return element; |
| 1218 }, |
| 1219 |
| 1220 inspect: function(element) { |
| 1221 element = $(element); |
| 1222 var result = '<' + element.tagName.toLowerCase(); |
| 1223 $H({'id': 'id', 'className': 'class'}).each(function(pair) { |
| 1224 var property = pair.first(), attribute = pair.last(); |
| 1225 var value = (element[property] || '').toString(); |
| 1226 if (value) result += ' ' + attribute + '=' + value.inspect(true); |
| 1227 }); |
| 1228 return result + '>'; |
| 1229 }, |
| 1230 |
| 1231 recursivelyCollect: function(element, property) { |
| 1232 element = $(element); |
| 1233 var elements = []; |
| 1234 while (element = element[property]) |
| 1235 if (element.nodeType == 1) |
| 1236 elements.push(Element.extend(element)); |
| 1237 return elements; |
| 1238 }, |
| 1239 |
| 1240 ancestors: function(element) { |
| 1241 return $(element).recursivelyCollect('parentNode'); |
| 1242 }, |
| 1243 |
| 1244 descendants: function(element) { |
| 1245 return $A($(element).getElementsByTagName('*')); |
| 1246 }, |
| 1247 |
| 1248 immediateDescendants: function(element) { |
| 1249 if (!(element = $(element).firstChild)) return []; |
| 1250 while (element && element.nodeType != 1) element = element.nextSibling; |
| 1251 if (element) return [element].concat($(element).nextSiblings()); |
| 1252 return []; |
| 1253 }, |
| 1254 |
| 1255 previousSiblings: function(element) { |
| 1256 return $(element).recursivelyCollect('previousSibling'); |
| 1257 }, |
| 1258 |
| 1259 nextSiblings: function(element) { |
| 1260 return $(element).recursivelyCollect('nextSibling'); |
| 1261 }, |
| 1262 |
| 1263 siblings: function(element) { |
| 1264 element = $(element); |
| 1265 return element.previousSiblings().reverse().concat(element.nextSiblings()); |
| 1266 }, |
| 1267 |
| 1268 match: function(element, selector) { |
| 1269 if (typeof selector == 'string') |
| 1270 selector = new Selector(selector); |
| 1271 return selector.match($(element)); |
| 1272 }, |
| 1273 |
| 1274 up: function(element, expression, index) { |
| 1275 return Selector.findElement($(element).ancestors(), expression, index); |
| 1276 }, |
| 1277 |
| 1278 down: function(element, expression, index) { |
| 1279 return Selector.findElement($(element).descendants(), expression, index); |
| 1280 }, |
| 1281 |
| 1282 previous: function(element, expression, index) { |
| 1283 return Selector.findElement($(element).previousSiblings(), expression, index
); |
| 1284 }, |
| 1285 |
| 1286 next: function(element, expression, index) { |
| 1287 return Selector.findElement($(element).nextSiblings(), expression, index); |
| 1288 }, |
| 1289 |
| 1290 getElementsBySelector: function() { |
| 1291 var args = $A(arguments), element = $(args.shift()); |
| 1292 return Selector.findChildElements(element, args); |
| 1293 }, |
| 1294 |
| 1295 getElementsByClassName: function(element, className) { |
| 1296 return document.getElementsByClassName(className, element); |
| 1297 }, |
| 1298 |
| 1299 readAttribute: function(element, name) { |
| 1300 element = $(element); |
| 1301 if (document.all && !window.opera) { |
| 1302 var t = Element._attributeTranslations; |
| 1303 if (t.values[name]) return t.values[name](element, name); |
| 1304 if (t.names[name]) name = t.names[name]; |
| 1305 var attribute = element.attributes[name]; |
| 1306 if(attribute) return attribute.nodeValue; |
| 1307 } |
| 1308 return element.getAttribute(name); |
| 1309 }, |
| 1310 |
| 1311 getHeight: function(element) { |
| 1312 return $(element).getDimensions().height; |
| 1313 }, |
| 1314 |
| 1315 getWidth: function(element) { |
| 1316 return $(element).getDimensions().width; |
| 1317 }, |
| 1318 |
| 1319 classNames: function(element) { |
| 1320 return new Element.ClassNames(element); |
| 1321 }, |
| 1322 |
| 1323 hasClassName: function(element, className) { |
| 1324 if (!(element = $(element))) return; |
| 1325 var elementClassName = element.className; |
| 1326 if (elementClassName.length == 0) return false; |
| 1327 if (elementClassName == className || |
| 1328 elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) |
| 1329 return true; |
| 1330 return false; |
| 1331 }, |
| 1332 |
| 1333 addClassName: function(element, className) { |
| 1334 if (!(element = $(element))) return; |
| 1335 Element.classNames(element).add(className); |
| 1336 return element; |
| 1337 }, |
| 1338 |
| 1339 removeClassName: function(element, className) { |
| 1340 if (!(element = $(element))) return; |
| 1341 Element.classNames(element).remove(className); |
| 1342 return element; |
| 1343 }, |
| 1344 |
| 1345 toggleClassName: function(element, className) { |
| 1346 if (!(element = $(element))) return; |
| 1347 Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'ad
d'](className); |
| 1348 return element; |
| 1349 }, |
| 1350 |
| 1351 observe: function() { |
| 1352 Event.observe.apply(Event, arguments); |
| 1353 return $A(arguments).first(); |
| 1354 }, |
| 1355 |
| 1356 stopObserving: function() { |
| 1357 Event.stopObserving.apply(Event, arguments); |
| 1358 return $A(arguments).first(); |
| 1359 }, |
| 1360 |
| 1361 // removes whitespace-only text node children |
| 1362 cleanWhitespace: function(element) { |
| 1363 element = $(element); |
| 1364 var node = element.firstChild; |
| 1365 while (node) { |
| 1366 var nextNode = node.nextSibling; |
| 1367 if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) |
| 1368 element.removeChild(node); |
| 1369 node = nextNode; |
| 1370 } |
| 1371 return element; |
| 1372 }, |
| 1373 |
| 1374 empty: function(element) { |
| 1375 return $(element).innerHTML.match(/^\s*$/); |
| 1376 }, |
| 1377 |
| 1378 descendantOf: function(element, ancestor) { |
| 1379 element = $(element), ancestor = $(ancestor); |
| 1380 while (element = element.parentNode) |
| 1381 if (element == ancestor) return true; |
| 1382 return false; |
| 1383 }, |
| 1384 |
| 1385 scrollTo: function(element) { |
| 1386 element = $(element); |
| 1387 var pos = Position.cumulativeOffset(element); |
| 1388 window.scrollTo(pos[0], pos[1]); |
| 1389 return element; |
| 1390 }, |
| 1391 |
| 1392 getStyle: function(element, style) { |
| 1393 element = $(element); |
| 1394 if (['float','cssFloat'].include(style)) |
| 1395 style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : '
cssFloat'); |
| 1396 style = style.camelize(); |
| 1397 var value = element.style[style]; |
| 1398 if (!value) { |
| 1399 if (document.defaultView && document.defaultView.getComputedStyle) { |
| 1400 var css = document.defaultView.getComputedStyle(element, null); |
| 1401 value = css ? css[style] : null; |
| 1402 } else if (element.currentStyle) { |
| 1403 value = element.currentStyle[style]; |
| 1404 } |
| 1405 } |
| 1406 |
| 1407 if((value == 'auto') && ['width','height'].include(style) && (element.getSty
le('display') != 'none')) |
| 1408 value = element['offset'+style.capitalize()] + 'px'; |
| 1409 |
| 1410 if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) |
| 1411 if (Element.getStyle(element, 'position') == 'static') value = 'auto'; |
| 1412 if(style == 'opacity') { |
| 1413 if(value) return parseFloat(value); |
| 1414 if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)
/)) |
| 1415 if(value[1]) return parseFloat(value[1]) / 100; |
| 1416 return 1.0; |
| 1417 } |
| 1418 return value == 'auto' ? null : value; |
| 1419 }, |
| 1420 |
| 1421 setStyle: function(element, style) { |
| 1422 element = $(element); |
| 1423 for (var name in style) { |
| 1424 var value = style[name]; |
| 1425 if(name == 'opacity') { |
| 1426 if (value == 1) { |
| 1427 value = (/Gecko/.test(navigator.userAgent) && |
| 1428 !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.
0; |
| 1429 if(/MSIE/.test(navigator.userAgent) && !window.opera) |
| 1430 element.style.filter = element.getStyle('filter').replace(/alpha\([^
\)]*\)/gi,''); |
| 1431 } else if(value == '') { |
| 1432 if(/MSIE/.test(navigator.userAgent) && !window.opera) |
| 1433 element.style.filter = element.getStyle('filter').replace(/alpha\([^
\)]*\)/gi,''); |
| 1434 } else { |
| 1435 if(value < 0.00001) value = 0; |
| 1436 if(/MSIE/.test(navigator.userAgent) && !window.opera) |
| 1437 element.style.filter = element.getStyle('filter').replace(/alpha\([^
\)]*\)/gi,'') + |
| 1438 'alpha(opacity='+value*100+')'; |
| 1439 } |
| 1440 } else if(['float','cssFloat'].include(name)) name = (typeof element.style
.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat'; |
| 1441 element.style[name.camelize()] = value; |
| 1442 } |
| 1443 return element; |
| 1444 }, |
| 1445 |
| 1446 getDimensions: function(element) { |
| 1447 element = $(element); |
| 1448 var display = $(element).getStyle('display'); |
| 1449 if (display != 'none' && display != null) // Safari bug |
| 1450 return {width: element.offsetWidth, height: element.offsetHeight}; |
| 1451 |
| 1452 // All *Width and *Height properties give 0 on elements with display none, |
| 1453 // so enable the element temporarily |
| 1454 var els = element.style; |
| 1455 var originalVisibility = els.visibility; |
| 1456 var originalPosition = els.position; |
| 1457 var originalDisplay = els.display; |
| 1458 els.visibility = 'hidden'; |
| 1459 els.position = 'absolute'; |
| 1460 els.display = 'block'; |
| 1461 var originalWidth = element.clientWidth; |
| 1462 var originalHeight = element.clientHeight; |
| 1463 els.display = originalDisplay; |
| 1464 els.position = originalPosition; |
| 1465 els.visibility = originalVisibility; |
| 1466 return {width: originalWidth, height: originalHeight}; |
| 1467 }, |
| 1468 |
| 1469 makePositioned: function(element) { |
| 1470 element = $(element); |
| 1471 var pos = Element.getStyle(element, 'position'); |
| 1472 if (pos == 'static' || !pos) { |
| 1473 element._madePositioned = true; |
| 1474 element.style.position = 'relative'; |
| 1475 // Opera returns the offset relative to the positioning context, when an |
| 1476 // element is position relative but top and left have not been defined |
| 1477 if (window.opera) { |
| 1478 element.style.top = 0; |
| 1479 element.style.left = 0; |
| 1480 } |
| 1481 } |
| 1482 return element; |
| 1483 }, |
| 1484 |
| 1485 undoPositioned: function(element) { |
| 1486 element = $(element); |
| 1487 if (element._madePositioned) { |
| 1488 element._madePositioned = undefined; |
| 1489 element.style.position = |
| 1490 element.style.top = |
| 1491 element.style.left = |
| 1492 element.style.bottom = |
| 1493 element.style.right = ''; |
| 1494 } |
| 1495 return element; |
| 1496 }, |
| 1497 |
| 1498 makeClipping: function(element) { |
| 1499 element = $(element); |
| 1500 if (element._overflow) return element; |
| 1501 element._overflow = element.style.overflow || 'auto'; |
| 1502 if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') |
| 1503 element.style.overflow = 'hidden'; |
| 1504 return element; |
| 1505 }, |
| 1506 |
| 1507 undoClipping: function(element) { |
| 1508 element = $(element); |
| 1509 if (!element._overflow) return element; |
| 1510 element.style.overflow = element._overflow == 'auto' ? '' : element._overflo
w; |
| 1511 element._overflow = null; |
| 1512 return element; |
| 1513 } |
| 1514 }; |
| 1515 |
| 1516 Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf}); |
| 1517 |
| 1518 Element._attributeTranslations = {}; |
| 1519 |
| 1520 Element._attributeTranslations.names = { |
| 1521 colspan: "colSpan", |
| 1522 rowspan: "rowSpan", |
| 1523 valign: "vAlign", |
| 1524 datetime: "dateTime", |
| 1525 accesskey: "accessKey", |
| 1526 tabindex: "tabIndex", |
| 1527 enctype: "encType", |
| 1528 maxlength: "maxLength", |
| 1529 readonly: "readOnly", |
| 1530 longdesc: "longDesc" |
| 1531 }; |
| 1532 |
| 1533 Element._attributeTranslations.values = { |
| 1534 _getAttr: function(element, attribute) { |
| 1535 return element.getAttribute(attribute, 2); |
| 1536 }, |
| 1537 |
| 1538 _flag: function(element, attribute) { |
| 1539 return $(element).hasAttribute(attribute) ? attribute : null; |
| 1540 }, |
| 1541 |
| 1542 style: function(element) { |
| 1543 return element.style.cssText.toLowerCase(); |
| 1544 }, |
| 1545 |
| 1546 title: function(element) { |
| 1547 var node = element.getAttributeNode('title'); |
| 1548 return node.specified ? node.nodeValue : null; |
| 1549 } |
| 1550 }; |
| 1551 |
| 1552 Object.extend(Element._attributeTranslations.values, { |
| 1553 href: Element._attributeTranslations.values._getAttr, |
| 1554 src: Element._attributeTranslations.values._getAttr, |
| 1555 disabled: Element._attributeTranslations.values._flag, |
| 1556 checked: Element._attributeTranslations.values._flag, |
| 1557 readonly: Element._attributeTranslations.values._flag, |
| 1558 multiple: Element._attributeTranslations.values._flag |
| 1559 }); |
| 1560 |
| 1561 Element.Methods.Simulated = { |
| 1562 hasAttribute: function(element, attribute) { |
| 1563 var t = Element._attributeTranslations; |
| 1564 attribute = t.names[attribute] || attribute; |
| 1565 return $(element).getAttributeNode(attribute).specified; |
| 1566 } |
| 1567 }; |
| 1568 |
| 1569 // IE is missing .innerHTML support for TABLE-related elements |
| 1570 if (document.all && !window.opera){ |
| 1571 Element.Methods.update = function(element, html) { |
| 1572 element = $(element); |
| 1573 html = typeof html == 'undefined' ? '' : html.toString(); |
| 1574 var tagName = element.tagName.toUpperCase(); |
| 1575 if (['THEAD','TBODY','TR','TD'].include(tagName)) { |
| 1576 var div = document.createElement('div'); |
| 1577 switch (tagName) { |
| 1578 case 'THEAD': |
| 1579 case 'TBODY': |
| 1580 div.innerHTML = '<table><tbody>' + html.stripScripts() + '</tbody></t
able>'; |
| 1581 depth = 2; |
| 1582 break; |
| 1583 case 'TR': |
| 1584 div.innerHTML = '<table><tbody><tr>' + html.stripScripts() + '</tr></
tbody></table>'; |
| 1585 depth = 3; |
| 1586 break; |
| 1587 case 'TD': |
| 1588 div.innerHTML = '<table><tbody><tr><td>' + html.stripScripts() + '</t
d></tr></tbody></table>'; |
| 1589 depth = 4; |
| 1590 } |
| 1591 $A(element.childNodes).each(function(node){ |
| 1592 element.removeChild(node) |
| 1593 }); |
| 1594 depth.times(function(){ div = div.firstChild }); |
| 1595 |
| 1596 $A(div.childNodes).each( |
| 1597 function(node){ element.appendChild(node) }); |
| 1598 } else { |
| 1599 element.innerHTML = html.stripScripts(); |
| 1600 } |
| 1601 setTimeout(function() {html.evalScripts()}, 10); |
| 1602 return element; |
| 1603 } |
| 1604 }; |
| 1605 |
| 1606 Object.extend(Element, Element.Methods); |
| 1607 |
| 1608 var _nativeExtensions = false; |
| 1609 |
| 1610 if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) |
| 1611 ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) { |
| 1612 var className = 'HTML' + tag + 'Element'; |
| 1613 if(window[className]) return; |
| 1614 var klass = window[className] = {}; |
| 1615 klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__
proto__; |
| 1616 }); |
| 1617 |
| 1618 Element.addMethods = function(methods) { |
| 1619 Object.extend(Element.Methods, methods || {}); |
| 1620 |
| 1621 function copy(methods, destination, onlyIfAbsent) { |
| 1622 onlyIfAbsent = onlyIfAbsent || false; |
| 1623 var cache = Element.extend.cache; |
| 1624 for (var property in methods) { |
| 1625 var value = methods[property]; |
| 1626 if (!onlyIfAbsent || !(property in destination)) |
| 1627 destination[property] = cache.findOrStore(value); |
| 1628 } |
| 1629 } |
| 1630 |
| 1631 if (typeof HTMLElement != 'undefined') { |
| 1632 copy(Element.Methods, HTMLElement.prototype); |
| 1633 copy(Element.Methods.Simulated, HTMLElement.prototype, true); |
| 1634 copy(Form.Methods, HTMLFormElement.prototype); |
| 1635 [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(kla
ss) { |
| 1636 copy(Form.Element.Methods, klass.prototype); |
| 1637 }); |
| 1638 _nativeExtensions = true; |
| 1639 } |
| 1640 } |
| 1641 |
| 1642 var Toggle = new Object(); |
| 1643 Toggle.display = Element.toggle; |
| 1644 |
| 1645 /*--------------------------------------------------------------------------*/ |
| 1646 |
| 1647 Abstract.Insertion = function(adjacency) { |
| 1648 this.adjacency = adjacency; |
| 1649 } |
| 1650 |
| 1651 Abstract.Insertion.prototype = { |
| 1652 initialize: function(element, content) { |
| 1653 this.element = $(element); |
| 1654 this.content = content.stripScripts(); |
| 1655 |
| 1656 if (this.adjacency && this.element.insertAdjacentHTML) { |
| 1657 try { |
| 1658 this.element.insertAdjacentHTML(this.adjacency, this.content); |
| 1659 } catch (e) { |
| 1660 var tagName = this.element.tagName.toUpperCase(); |
| 1661 if (['TBODY', 'TR'].include(tagName)) { |
| 1662 this.insertContent(this.contentFromAnonymousTable()); |
| 1663 } else { |
| 1664 throw e; |
| 1665 } |
| 1666 } |
| 1667 } else { |
| 1668 this.range = this.element.ownerDocument.createRange(); |
| 1669 if (this.initializeRange) this.initializeRange(); |
| 1670 this.insertContent([this.range.createContextualFragment(this.content)]); |
| 1671 } |
| 1672 |
| 1673 setTimeout(function() {content.evalScripts()}, 10); |
| 1674 }, |
| 1675 |
| 1676 contentFromAnonymousTable: function() { |
| 1677 var div = document.createElement('div'); |
| 1678 div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>'; |
| 1679 return $A(div.childNodes[0].childNodes[0].childNodes); |
| 1680 } |
| 1681 } |
| 1682 |
| 1683 var Insertion = new Object(); |
| 1684 |
| 1685 Insertion.Before = Class.create(); |
| 1686 Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin')
, { |
| 1687 initializeRange: function() { |
| 1688 this.range.setStartBefore(this.element); |
| 1689 }, |
| 1690 |
| 1691 insertContent: function(fragments) { |
| 1692 fragments.each((function(fragment) { |
| 1693 this.element.parentNode.insertBefore(fragment, this.element); |
| 1694 }).bind(this)); |
| 1695 } |
| 1696 }); |
| 1697 |
| 1698 Insertion.Top = Class.create(); |
| 1699 Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { |
| 1700 initializeRange: function() { |
| 1701 this.range.selectNodeContents(this.element); |
| 1702 this.range.collapse(true); |
| 1703 }, |
| 1704 |
| 1705 insertContent: function(fragments) { |
| 1706 fragments.reverse(false).each((function(fragment) { |
| 1707 this.element.insertBefore(fragment, this.element.firstChild); |
| 1708 }).bind(this)); |
| 1709 } |
| 1710 }); |
| 1711 |
| 1712 Insertion.Bottom = Class.create(); |
| 1713 Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'),
{ |
| 1714 initializeRange: function() { |
| 1715 this.range.selectNodeContents(this.element); |
| 1716 this.range.collapse(this.element); |
| 1717 }, |
| 1718 |
| 1719 insertContent: function(fragments) { |
| 1720 fragments.each((function(fragment) { |
| 1721 this.element.appendChild(fragment); |
| 1722 }).bind(this)); |
| 1723 } |
| 1724 }); |
| 1725 |
| 1726 Insertion.After = Class.create(); |
| 1727 Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { |
| 1728 initializeRange: function() { |
| 1729 this.range.setStartAfter(this.element); |
| 1730 }, |
| 1731 |
| 1732 insertContent: function(fragments) { |
| 1733 fragments.each((function(fragment) { |
| 1734 this.element.parentNode.insertBefore(fragment, |
| 1735 this.element.nextSibling); |
| 1736 }).bind(this)); |
| 1737 } |
| 1738 }); |
| 1739 |
| 1740 /*--------------------------------------------------------------------------*/ |
| 1741 |
| 1742 Element.ClassNames = Class.create(); |
| 1743 Element.ClassNames.prototype = { |
| 1744 initialize: function(element) { |
| 1745 this.element = $(element); |
| 1746 }, |
| 1747 |
| 1748 _each: function(iterator) { |
| 1749 this.element.className.split(/\s+/).select(function(name) { |
| 1750 return name.length > 0; |
| 1751 })._each(iterator); |
| 1752 }, |
| 1753 |
| 1754 set: function(className) { |
| 1755 this.element.className = className; |
| 1756 }, |
| 1757 |
| 1758 add: function(classNameToAdd) { |
| 1759 if (this.include(classNameToAdd)) return; |
| 1760 this.set($A(this).concat(classNameToAdd).join(' ')); |
| 1761 }, |
| 1762 |
| 1763 remove: function(classNameToRemove) { |
| 1764 if (!this.include(classNameToRemove)) return; |
| 1765 this.set($A(this).without(classNameToRemove).join(' ')); |
| 1766 }, |
| 1767 |
| 1768 toString: function() { |
| 1769 return $A(this).join(' '); |
| 1770 } |
| 1771 }; |
| 1772 |
| 1773 Object.extend(Element.ClassNames.prototype, Enumerable); |
| 1774 var Selector = Class.create(); |
| 1775 Selector.prototype = { |
| 1776 initialize: function(expression) { |
| 1777 this.params = {classNames: []}; |
| 1778 this.expression = expression.toString().strip(); |
| 1779 this.parseExpression(); |
| 1780 this.compileMatcher(); |
| 1781 }, |
| 1782 |
| 1783 parseExpression: function() { |
| 1784 function abort(message) { throw 'Parse error in selector: ' + message; } |
| 1785 |
| 1786 if (this.expression == '') abort('empty expression'); |
| 1787 |
| 1788 var params = this.params, expr = this.expression, match, modifier, clause, r
est; |
| 1789 while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|(
[^\]\s]*)))?\]$/i)) { |
| 1790 params.attributes = params.attributes || []; |
| 1791 params.attributes.push({name: match[2], operator: match[3], value: match[4
] || match[5] || ''}); |
| 1792 expr = match[1]; |
| 1793 } |
| 1794 |
| 1795 if (expr == '*') return this.params.wildcard = true; |
| 1796 |
| 1797 while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { |
| 1798 modifier = match[1], clause = match[2], rest = match[3]; |
| 1799 switch (modifier) { |
| 1800 case '#': params.id = clause; break; |
| 1801 case '.': params.classNames.push(clause); break; |
| 1802 case '': |
| 1803 case undefined: params.tagName = clause.toUpperCase(); break; |
| 1804 default: abort(expr.inspect()); |
| 1805 } |
| 1806 expr = rest; |
| 1807 } |
| 1808 |
| 1809 if (expr.length > 0) abort(expr.inspect()); |
| 1810 }, |
| 1811 |
| 1812 buildMatchExpression: function() { |
| 1813 var params = this.params, conditions = [], clause; |
| 1814 |
| 1815 if (params.wildcard) |
| 1816 conditions.push('true'); |
| 1817 if (clause = params.id) |
| 1818 conditions.push('element.readAttribute("id") == ' + clause.inspect()); |
| 1819 if (clause = params.tagName) |
| 1820 conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); |
| 1821 if ((clause = params.classNames).length > 0) |
| 1822 for (var i = 0, length = clause.length; i < length; i++) |
| 1823 conditions.push('element.hasClassName(' + clause[i].inspect() + ')'); |
| 1824 if (clause = params.attributes) { |
| 1825 clause.each(function(attribute) { |
| 1826 var value = 'element.readAttribute(' + attribute.name.inspect() + ')'; |
| 1827 var splitValueBy = function(delimiter) { |
| 1828 return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; |
| 1829 } |
| 1830 |
| 1831 switch (attribute.operator) { |
| 1832 case '=': conditions.push(value + ' == ' + attribute.value.inspe
ct()); break; |
| 1833 case '~=': conditions.push(splitValueBy(' ') + '.include(' + attr
ibute.value.inspect() + ')'); break; |
| 1834 case '|=': conditions.push( |
| 1835 splitValueBy('-') + '.first().toUpperCase() == ' + a
ttribute.value.toUpperCase().inspect() |
| 1836 ); break; |
| 1837 case '!=': conditions.push(value + ' != ' + attribute.value.inspe
ct()); break; |
| 1838 case '': |
| 1839 case undefined: conditions.push('element.hasAttribute(' + attribute.na
me.inspect() + ')'); break; |
| 1840 default: throw 'Unknown operator ' + attribute.operator + ' in
selector'; |
| 1841 } |
| 1842 }); |
| 1843 } |
| 1844 |
| 1845 return conditions.join(' && '); |
| 1846 }, |
| 1847 |
| 1848 compileMatcher: function() { |
| 1849 this.match = new Function('element', 'if (!element.tagName) return false; \ |
| 1850 element = $(element); \ |
| 1851 return ' + this.buildMatchExpression()); |
| 1852 }, |
| 1853 |
| 1854 findElements: function(scope) { |
| 1855 var element; |
| 1856 |
| 1857 if (element = $(this.params.id)) |
| 1858 if (this.match(element)) |
| 1859 if (!scope || Element.childOf(element, scope)) |
| 1860 return [element]; |
| 1861 |
| 1862 scope = (scope || document).getElementsByTagName(this.params.tagName || '*')
; |
| 1863 |
| 1864 var results = []; |
| 1865 for (var i = 0, length = scope.length; i < length; i++) |
| 1866 if (this.match(element = scope[i])) |
| 1867 results.push(Element.extend(element)); |
| 1868 |
| 1869 return results; |
| 1870 }, |
| 1871 |
| 1872 toString: function() { |
| 1873 return this.expression; |
| 1874 } |
| 1875 } |
| 1876 |
| 1877 Object.extend(Selector, { |
| 1878 matchElements: function(elements, expression) { |
| 1879 var selector = new Selector(expression); |
| 1880 return elements.select(selector.match.bind(selector)).map(Element.extend); |
| 1881 }, |
| 1882 |
| 1883 findElement: function(elements, expression, index) { |
| 1884 if (typeof expression == 'number') index = expression, expression = false; |
| 1885 return Selector.matchElements(elements, expression || '*')[index || 0]; |
| 1886 }, |
| 1887 |
| 1888 findChildElements: function(element, expressions) { |
| 1889 return expressions.map(function(expression) { |
| 1890 return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], func
tion(results, expr) { |
| 1891 var selector = new Selector(expr); |
| 1892 return results.inject([], function(elements, result) { |
| 1893 return elements.concat(selector.findElements(result || element)); |
| 1894 }); |
| 1895 }); |
| 1896 }).flatten(); |
| 1897 } |
| 1898 }); |
| 1899 |
| 1900 function $$() { |
| 1901 return Selector.findChildElements(document, $A(arguments)); |
| 1902 } |
| 1903 var Form = { |
| 1904 reset: function(form) { |
| 1905 $(form).reset(); |
| 1906 return form; |
| 1907 }, |
| 1908 |
| 1909 serializeElements: function(elements, getHash) { |
| 1910 var data = elements.inject({}, function(result, element) { |
| 1911 if (!element.disabled && element.name) { |
| 1912 var key = element.name, value = $(element).getValue(); |
| 1913 if (value != undefined) { |
| 1914 if (result[key]) { |
| 1915 if (result[key].constructor != Array) result[key] = [result[key]]; |
| 1916 result[key].push(value); |
| 1917 } |
| 1918 else result[key] = value; |
| 1919 } |
| 1920 } |
| 1921 return result; |
| 1922 }); |
| 1923 |
| 1924 return getHash ? data : Hash.toQueryString(data); |
| 1925 } |
| 1926 }; |
| 1927 |
| 1928 Form.Methods = { |
| 1929 serialize: function(form, getHash) { |
| 1930 return Form.serializeElements(Form.getElements(form), getHash); |
| 1931 }, |
| 1932 |
| 1933 getElements: function(form) { |
| 1934 return $A($(form).getElementsByTagName('*')).inject([], |
| 1935 function(elements, child) { |
| 1936 if (Form.Element.Serializers[child.tagName.toLowerCase()]) |
| 1937 elements.push(Element.extend(child)); |
| 1938 return elements; |
| 1939 } |
| 1940 ); |
| 1941 }, |
| 1942 |
| 1943 getInputs: function(form, typeName, name) { |
| 1944 form = $(form); |
| 1945 var inputs = form.getElementsByTagName('input'); |
| 1946 |
| 1947 if (!typeName && !name) return $A(inputs).map(Element.extend); |
| 1948 |
| 1949 for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++
) { |
| 1950 var input = inputs[i]; |
| 1951 if ((typeName && input.type != typeName) || (name && input.name != name)) |
| 1952 continue; |
| 1953 matchingInputs.push(Element.extend(input)); |
| 1954 } |
| 1955 |
| 1956 return matchingInputs; |
| 1957 }, |
| 1958 |
| 1959 disable: function(form) { |
| 1960 form = $(form); |
| 1961 form.getElements().each(function(element) { |
| 1962 element.blur(); |
| 1963 element.disabled = 'true'; |
| 1964 }); |
| 1965 return form; |
| 1966 }, |
| 1967 |
| 1968 enable: function(form) { |
| 1969 form = $(form); |
| 1970 form.getElements().each(function(element) { |
| 1971 element.disabled = ''; |
| 1972 }); |
| 1973 return form; |
| 1974 }, |
| 1975 |
| 1976 findFirstElement: function(form) { |
| 1977 return $(form).getElements().find(function(element) { |
| 1978 return element.type != 'hidden' && !element.disabled && |
| 1979 ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); |
| 1980 }); |
| 1981 }, |
| 1982 |
| 1983 focusFirstElement: function(form) { |
| 1984 form = $(form); |
| 1985 form.findFirstElement().activate(); |
| 1986 return form; |
| 1987 } |
| 1988 } |
| 1989 |
| 1990 Object.extend(Form, Form.Methods); |
| 1991 |
| 1992 /*--------------------------------------------------------------------------*/ |
| 1993 |
| 1994 Form.Element = { |
| 1995 focus: function(element) { |
| 1996 $(element).focus(); |
| 1997 return element; |
| 1998 }, |
| 1999 |
| 2000 select: function(element) { |
| 2001 $(element).select(); |
| 2002 return element; |
| 2003 } |
| 2004 } |
| 2005 |
| 2006 Form.Element.Methods = { |
| 2007 serialize: function(element) { |
| 2008 element = $(element); |
| 2009 if (!element.disabled && element.name) { |
| 2010 var value = element.getValue(); |
| 2011 if (value != undefined) { |
| 2012 var pair = {}; |
| 2013 pair[element.name] = value; |
| 2014 return Hash.toQueryString(pair); |
| 2015 } |
| 2016 } |
| 2017 return ''; |
| 2018 }, |
| 2019 |
| 2020 getValue: function(element) { |
| 2021 element = $(element); |
| 2022 var method = element.tagName.toLowerCase(); |
| 2023 return Form.Element.Serializers[method](element); |
| 2024 }, |
| 2025 |
| 2026 clear: function(element) { |
| 2027 $(element).value = ''; |
| 2028 return element; |
| 2029 }, |
| 2030 |
| 2031 present: function(element) { |
| 2032 return $(element).value != ''; |
| 2033 }, |
| 2034 |
| 2035 activate: function(element) { |
| 2036 element = $(element); |
| 2037 element.focus(); |
| 2038 if (element.select && ( element.tagName.toLowerCase() != 'input' || |
| 2039 !['button', 'reset', 'submit'].include(element.type) ) ) |
| 2040 element.select(); |
| 2041 return element; |
| 2042 }, |
| 2043 |
| 2044 disable: function(element) { |
| 2045 element = $(element); |
| 2046 element.disabled = true; |
| 2047 return element; |
| 2048 }, |
| 2049 |
| 2050 enable: function(element) { |
| 2051 element = $(element); |
| 2052 element.blur(); |
| 2053 element.disabled = false; |
| 2054 return element; |
| 2055 } |
| 2056 } |
| 2057 |
| 2058 Object.extend(Form.Element, Form.Element.Methods); |
| 2059 var Field = Form.Element; |
| 2060 var $F = Form.Element.getValue; |
| 2061 |
| 2062 /*--------------------------------------------------------------------------*/ |
| 2063 |
| 2064 Form.Element.Serializers = { |
| 2065 input: function(element) { |
| 2066 switch (element.type.toLowerCase()) { |
| 2067 case 'checkbox': |
| 2068 case 'radio': |
| 2069 return Form.Element.Serializers.inputSelector(element); |
| 2070 default: |
| 2071 return Form.Element.Serializers.textarea(element); |
| 2072 } |
| 2073 }, |
| 2074 |
| 2075 inputSelector: function(element) { |
| 2076 return element.checked ? element.value : null; |
| 2077 }, |
| 2078 |
| 2079 textarea: function(element) { |
| 2080 return element.value; |
| 2081 }, |
| 2082 |
| 2083 select: function(element) { |
| 2084 return this[element.type == 'select-one' ? |
| 2085 'selectOne' : 'selectMany'](element); |
| 2086 }, |
| 2087 |
| 2088 selectOne: function(element) { |
| 2089 var index = element.selectedIndex; |
| 2090 return index >= 0 ? this.optionValue(element.options[index]) : null; |
| 2091 }, |
| 2092 |
| 2093 selectMany: function(element) { |
| 2094 var values, length = element.length; |
| 2095 if (!length) return null; |
| 2096 |
| 2097 for (var i = 0, values = []; i < length; i++) { |
| 2098 var opt = element.options[i]; |
| 2099 if (opt.selected) values.push(this.optionValue(opt)); |
| 2100 } |
| 2101 return values; |
| 2102 }, |
| 2103 |
| 2104 optionValue: function(opt) { |
| 2105 // extend element because hasAttribute may not be native |
| 2106 return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; |
| 2107 } |
| 2108 } |
| 2109 |
| 2110 /*--------------------------------------------------------------------------*/ |
| 2111 |
| 2112 Abstract.TimedObserver = function() {} |
| 2113 Abstract.TimedObserver.prototype = { |
| 2114 initialize: function(element, frequency, callback) { |
| 2115 this.frequency = frequency; |
| 2116 this.element = $(element); |
| 2117 this.callback = callback; |
| 2118 |
| 2119 this.lastValue = this.getValue(); |
| 2120 this.registerCallback(); |
| 2121 }, |
| 2122 |
| 2123 registerCallback: function() { |
| 2124 setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); |
| 2125 }, |
| 2126 |
| 2127 onTimerEvent: function() { |
| 2128 var value = this.getValue(); |
| 2129 var changed = ('string' == typeof this.lastValue && 'string' == typeof value |
| 2130 ? this.lastValue != value : String(this.lastValue) != String(value)); |
| 2131 if (changed) { |
| 2132 this.callback(this.element, value); |
| 2133 this.lastValue = value; |
| 2134 } |
| 2135 } |
| 2136 } |
| 2137 |
| 2138 Form.Element.Observer = Class.create(); |
| 2139 Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { |
| 2140 getValue: function() { |
| 2141 return Form.Element.getValue(this.element); |
| 2142 } |
| 2143 }); |
| 2144 |
| 2145 Form.Observer = Class.create(); |
| 2146 Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { |
| 2147 getValue: function() { |
| 2148 return Form.serialize(this.element); |
| 2149 } |
| 2150 }); |
| 2151 |
| 2152 /*--------------------------------------------------------------------------*/ |
| 2153 |
| 2154 Abstract.EventObserver = function() {} |
| 2155 Abstract.EventObserver.prototype = { |
| 2156 initialize: function(element, callback) { |
| 2157 this.element = $(element); |
| 2158 this.callback = callback; |
| 2159 |
| 2160 this.lastValue = this.getValue(); |
| 2161 if (this.element.tagName.toLowerCase() == 'form') |
| 2162 this.registerFormCallbacks(); |
| 2163 else |
| 2164 this.registerCallback(this.element); |
| 2165 }, |
| 2166 |
| 2167 onElementEvent: function() { |
| 2168 var value = this.getValue(); |
| 2169 if (this.lastValue != value) { |
| 2170 this.callback(this.element, value); |
| 2171 this.lastValue = value; |
| 2172 } |
| 2173 }, |
| 2174 |
| 2175 registerFormCallbacks: function() { |
| 2176 Form.getElements(this.element).each(this.registerCallback.bind(this)); |
| 2177 }, |
| 2178 |
| 2179 registerCallback: function(element) { |
| 2180 if (element.type) { |
| 2181 switch (element.type.toLowerCase()) { |
| 2182 case 'checkbox': |
| 2183 case 'radio': |
| 2184 Event.observe(element, 'click', this.onElementEvent.bind(this)); |
| 2185 break; |
| 2186 default: |
| 2187 Event.observe(element, 'change', this.onElementEvent.bind(this)); |
| 2188 break; |
| 2189 } |
| 2190 } |
| 2191 } |
| 2192 } |
| 2193 |
| 2194 Form.Element.EventObserver = Class.create(); |
| 2195 Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(
), { |
| 2196 getValue: function() { |
| 2197 return Form.Element.getValue(this.element); |
| 2198 } |
| 2199 }); |
| 2200 |
| 2201 Form.EventObserver = Class.create(); |
| 2202 Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { |
| 2203 getValue: function() { |
| 2204 return Form.serialize(this.element); |
| 2205 } |
| 2206 }); |
| 2207 if (!window.Event) { |
| 2208 var Event = new Object(); |
| 2209 } |
| 2210 |
| 2211 Object.extend(Event, { |
| 2212 KEY_BACKSPACE: 8, |
| 2213 KEY_TAB: 9, |
| 2214 KEY_RETURN: 13, |
| 2215 KEY_ESC: 27, |
| 2216 KEY_LEFT: 37, |
| 2217 KEY_UP: 38, |
| 2218 KEY_RIGHT: 39, |
| 2219 KEY_DOWN: 40, |
| 2220 KEY_DELETE: 46, |
| 2221 KEY_HOME: 36, |
| 2222 KEY_END: 35, |
| 2223 KEY_PAGEUP: 33, |
| 2224 KEY_PAGEDOWN: 34, |
| 2225 |
| 2226 element: function(event) { |
| 2227 return event.target || event.srcElement; |
| 2228 }, |
| 2229 |
| 2230 isLeftClick: function(event) { |
| 2231 return (((event.which) && (event.which == 1)) || |
| 2232 ((event.button) && (event.button == 1))); |
| 2233 }, |
| 2234 |
| 2235 pointerX: function(event) { |
| 2236 return event.pageX || (event.clientX + |
| 2237 (document.documentElement.scrollLeft || document.body.scrollLeft)); |
| 2238 }, |
| 2239 |
| 2240 pointerY: function(event) { |
| 2241 return event.pageY || (event.clientY + |
| 2242 (document.documentElement.scrollTop || document.body.scrollTop)); |
| 2243 }, |
| 2244 |
| 2245 stop: function(event) { |
| 2246 if (event.preventDefault) { |
| 2247 event.preventDefault(); |
| 2248 event.stopPropagation(); |
| 2249 } else { |
| 2250 event.returnValue = false; |
| 2251 event.cancelBubble = true; |
| 2252 } |
| 2253 }, |
| 2254 |
| 2255 // find the first node with the given tagName, starting from the |
| 2256 // node the event was triggered on; traverses the DOM upwards |
| 2257 findElement: function(event, tagName) { |
| 2258 var element = Event.element(event); |
| 2259 while (element.parentNode && (!element.tagName || |
| 2260 (element.tagName.toUpperCase() != tagName.toUpperCase()))) |
| 2261 element = element.parentNode; |
| 2262 return element; |
| 2263 }, |
| 2264 |
| 2265 observers: false, |
| 2266 |
| 2267 _observeAndCache: function(element, name, observer, useCapture) { |
| 2268 if (!this.observers) this.observers = []; |
| 2269 if (element.addEventListener) { |
| 2270 this.observers.push([element, name, observer, useCapture]); |
| 2271 element.addEventListener(name, observer, useCapture); |
| 2272 } else if (element.attachEvent) { |
| 2273 this.observers.push([element, name, observer, useCapture]); |
| 2274 element.attachEvent('on' + name, observer); |
| 2275 } |
| 2276 }, |
| 2277 |
| 2278 unloadCache: function() { |
| 2279 if (!Event.observers) return; |
| 2280 for (var i = 0, length = Event.observers.length; i < length; i++) { |
| 2281 Event.stopObserving.apply(this, Event.observers[i]); |
| 2282 Event.observers[i][0] = null; |
| 2283 } |
| 2284 Event.observers = false; |
| 2285 }, |
| 2286 |
| 2287 observe: function(element, name, observer, useCapture) { |
| 2288 element = $(element); |
| 2289 useCapture = useCapture || false; |
| 2290 |
| 2291 if (name == 'keypress' && |
| 2292 (navigator.appVersion.match(/Konqueror|Safari|KHTML/) |
| 2293 || element.attachEvent)) |
| 2294 name = 'keydown'; |
| 2295 |
| 2296 Event._observeAndCache(element, name, observer, useCapture); |
| 2297 }, |
| 2298 |
| 2299 stopObserving: function(element, name, observer, useCapture) { |
| 2300 element = $(element); |
| 2301 useCapture = useCapture || false; |
| 2302 |
| 2303 if (name == 'keypress' && |
| 2304 (navigator.appVersion.match(/Konqueror|Safari|KHTML/) |
| 2305 || element.detachEvent)) |
| 2306 name = 'keydown'; |
| 2307 |
| 2308 if (element.removeEventListener) { |
| 2309 element.removeEventListener(name, observer, useCapture); |
| 2310 } else if (element.detachEvent) { |
| 2311 try { |
| 2312 element.detachEvent('on' + name, observer); |
| 2313 } catch (e) {} |
| 2314 } |
| 2315 } |
| 2316 }); |
| 2317 |
| 2318 /* prevent memory leaks in IE */ |
| 2319 if (navigator.appVersion.match(/\bMSIE\b/)) |
| 2320 Event.observe(window, 'unload', Event.unloadCache, false); |
| 2321 var Position = { |
| 2322 // set to true if needed, warning: firefox performance problems |
| 2323 // NOT neeeded for page scrolling, only if draggable contained in |
| 2324 // scrollable elements |
| 2325 includeScrollOffsets: false, |
| 2326 |
| 2327 // must be called before calling withinIncludingScrolloffset, every time the |
| 2328 // page is scrolled |
| 2329 prepare: function() { |
| 2330 this.deltaX = window.pageXOffset |
| 2331 || document.documentElement.scrollLeft |
| 2332 || document.body.scrollLeft |
| 2333 || 0; |
| 2334 this.deltaY = window.pageYOffset |
| 2335 || document.documentElement.scrollTop |
| 2336 || document.body.scrollTop |
| 2337 || 0; |
| 2338 }, |
| 2339 |
| 2340 realOffset: function(element) { |
| 2341 var valueT = 0, valueL = 0; |
| 2342 do { |
| 2343 valueT += element.scrollTop || 0; |
| 2344 valueL += element.scrollLeft || 0; |
| 2345 element = element.parentNode; |
| 2346 } while (element); |
| 2347 return [valueL, valueT]; |
| 2348 }, |
| 2349 |
| 2350 cumulativeOffset: function(element) { |
| 2351 var valueT = 0, valueL = 0; |
| 2352 do { |
| 2353 valueT += element.offsetTop || 0; |
| 2354 valueL += element.offsetLeft || 0; |
| 2355 element = element.offsetParent; |
| 2356 } while (element); |
| 2357 return [valueL, valueT]; |
| 2358 }, |
| 2359 |
| 2360 positionedOffset: function(element) { |
| 2361 var valueT = 0, valueL = 0; |
| 2362 do { |
| 2363 valueT += element.offsetTop || 0; |
| 2364 valueL += element.offsetLeft || 0; |
| 2365 element = element.offsetParent; |
| 2366 if (element) { |
| 2367 if(element.tagName=='BODY') break; |
| 2368 var p = Element.getStyle(element, 'position'); |
| 2369 if (p == 'relative' || p == 'absolute') break; |
| 2370 } |
| 2371 } while (element); |
| 2372 return [valueL, valueT]; |
| 2373 }, |
| 2374 |
| 2375 offsetParent: function(element) { |
| 2376 if (element.offsetParent) return element.offsetParent; |
| 2377 if (element == document.body) return element; |
| 2378 |
| 2379 while ((element = element.parentNode) && element != document.body) |
| 2380 if (Element.getStyle(element, 'position') != 'static') |
| 2381 return element; |
| 2382 |
| 2383 return document.body; |
| 2384 }, |
| 2385 |
| 2386 // caches x/y coordinate pair to use with overlap |
| 2387 within: function(element, x, y) { |
| 2388 if (this.includeScrollOffsets) |
| 2389 return this.withinIncludingScrolloffsets(element, x, y); |
| 2390 this.xcomp = x; |
| 2391 this.ycomp = y; |
| 2392 this.offset = this.cumulativeOffset(element); |
| 2393 |
| 2394 return (y >= this.offset[1] && |
| 2395 y < this.offset[1] + element.offsetHeight && |
| 2396 x >= this.offset[0] && |
| 2397 x < this.offset[0] + element.offsetWidth); |
| 2398 }, |
| 2399 |
| 2400 withinIncludingScrolloffsets: function(element, x, y) { |
| 2401 var offsetcache = this.realOffset(element); |
| 2402 |
| 2403 this.xcomp = x + offsetcache[0] - this.deltaX; |
| 2404 this.ycomp = y + offsetcache[1] - this.deltaY; |
| 2405 this.offset = this.cumulativeOffset(element); |
| 2406 |
| 2407 return (this.ycomp >= this.offset[1] && |
| 2408 this.ycomp < this.offset[1] + element.offsetHeight && |
| 2409 this.xcomp >= this.offset[0] && |
| 2410 this.xcomp < this.offset[0] + element.offsetWidth); |
| 2411 }, |
| 2412 |
| 2413 // within must be called directly before |
| 2414 overlap: function(mode, element) { |
| 2415 if (!mode) return 0; |
| 2416 if (mode == 'vertical') |
| 2417 return ((this.offset[1] + element.offsetHeight) - this.ycomp) / |
| 2418 element.offsetHeight; |
| 2419 if (mode == 'horizontal') |
| 2420 return ((this.offset[0] + element.offsetWidth) - this.xcomp) / |
| 2421 element.offsetWidth; |
| 2422 }, |
| 2423 |
| 2424 page: function(forElement) { |
| 2425 var valueT = 0, valueL = 0; |
| 2426 |
| 2427 var element = forElement; |
| 2428 do { |
| 2429 valueT += element.offsetTop || 0; |
| 2430 valueL += element.offsetLeft || 0; |
| 2431 |
| 2432 // Safari fix |
| 2433 if (element.offsetParent==document.body) |
| 2434 if (Element.getStyle(element,'position')=='absolute') break; |
| 2435 |
| 2436 } while (element = element.offsetParent); |
| 2437 |
| 2438 element = forElement; |
| 2439 do { |
| 2440 if (!window.opera || element.tagName=='BODY') { |
| 2441 valueT -= element.scrollTop || 0; |
| 2442 valueL -= element.scrollLeft || 0; |
| 2443 } |
| 2444 } while (element = element.parentNode); |
| 2445 |
| 2446 return [valueL, valueT]; |
| 2447 }, |
| 2448 |
| 2449 clone: function(source, target) { |
| 2450 var options = Object.extend({ |
| 2451 setLeft: true, |
| 2452 setTop: true, |
| 2453 setWidth: true, |
| 2454 setHeight: true, |
| 2455 offsetTop: 0, |
| 2456 offsetLeft: 0 |
| 2457 }, arguments[2] || {}) |
| 2458 |
| 2459 // find page position of source |
| 2460 source = $(source); |
| 2461 var p = Position.page(source); |
| 2462 |
| 2463 // find coordinate system to use |
| 2464 target = $(target); |
| 2465 var delta = [0, 0]; |
| 2466 var parent = null; |
| 2467 // delta [0,0] will do fine with position: fixed elements, |
| 2468 // position:absolute needs offsetParent deltas |
| 2469 if (Element.getStyle(target,'position') == 'absolute') { |
| 2470 parent = Position.offsetParent(target); |
| 2471 delta = Position.page(parent); |
| 2472 } |
| 2473 |
| 2474 // correct by body offsets (fixes Safari) |
| 2475 if (parent == document.body) { |
| 2476 delta[0] -= document.body.offsetLeft; |
| 2477 delta[1] -= document.body.offsetTop; |
| 2478 } |
| 2479 |
| 2480 // set position |
| 2481 if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offset
Left) + 'px'; |
| 2482 if(options.setTop) target.style.top = (p[1] - delta[1] + options.offset
Top) + 'px'; |
| 2483 if(options.setWidth) target.style.width = source.offsetWidth + 'px'; |
| 2484 if(options.setHeight) target.style.height = source.offsetHeight + 'px'; |
| 2485 }, |
| 2486 |
| 2487 absolutize: function(element) { |
| 2488 element = $(element); |
| 2489 if (element.style.position == 'absolute') return; |
| 2490 Position.prepare(); |
| 2491 |
| 2492 var offsets = Position.positionedOffset(element); |
| 2493 var top = offsets[1]; |
| 2494 var left = offsets[0]; |
| 2495 var width = element.clientWidth; |
| 2496 var height = element.clientHeight; |
| 2497 |
| 2498 element._originalLeft = left - parseFloat(element.style.left || 0); |
| 2499 element._originalTop = top - parseFloat(element.style.top || 0); |
| 2500 element._originalWidth = element.style.width; |
| 2501 element._originalHeight = element.style.height; |
| 2502 |
| 2503 element.style.position = 'absolute'; |
| 2504 element.style.top = top + 'px'; |
| 2505 element.style.left = left + 'px'; |
| 2506 element.style.width = width + 'px'; |
| 2507 element.style.height = height + 'px'; |
| 2508 }, |
| 2509 |
| 2510 relativize: function(element) { |
| 2511 element = $(element); |
| 2512 if (element.style.position == 'relative') return; |
| 2513 Position.prepare(); |
| 2514 |
| 2515 element.style.position = 'relative'; |
| 2516 var top = parseFloat(element.style.top || 0) - (element._originalTop || 0)
; |
| 2517 var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0
); |
| 2518 |
| 2519 element.style.top = top + 'px'; |
| 2520 element.style.left = left + 'px'; |
| 2521 element.style.height = element._originalHeight; |
| 2522 element.style.width = element._originalWidth; |
| 2523 } |
| 2524 } |
| 2525 |
| 2526 // Safari returns margins on body which is incorrect if the child is absolutely |
| 2527 // positioned. For performance reasons, redefine Position.cumulativeOffset for |
| 2528 // KHTML/WebKit only. |
| 2529 if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { |
| 2530 Position.cumulativeOffset = function(element) { |
| 2531 var valueT = 0, valueL = 0; |
| 2532 do { |
| 2533 valueT += element.offsetTop || 0; |
| 2534 valueL += element.offsetLeft || 0; |
| 2535 if (element.offsetParent == document.body) |
| 2536 if (Element.getStyle(element, 'position') == 'absolute') break; |
| 2537 |
| 2538 element = element.offsetParent; |
| 2539 } while (element); |
| 2540 |
| 2541 return [valueL, valueT]; |
| 2542 } |
| 2543 } |
| 2544 |
| 2545 Element.addMethods(); |
| 2546 |
| 2547 |
| 2548 // ------------------------------------------------------------------------ |
| 2549 // ------------------------------------------------------------------------ |
| 2550 |
| 2551 // The rest of this file is the actual ray tracer written by Adam |
| 2552 // Burmister. It's a concatenation of the following files: |
| 2553 // |
| 2554 // flog/color.js |
| 2555 // flog/light.js |
| 2556 // flog/vector.js |
| 2557 // flog/ray.js |
| 2558 // flog/scene.js |
| 2559 // flog/material/basematerial.js |
| 2560 // flog/material/solid.js |
| 2561 // flog/material/chessboard.js |
| 2562 // flog/shape/baseshape.js |
| 2563 // flog/shape/sphere.js |
| 2564 // flog/shape/plane.js |
| 2565 // flog/intersectioninfo.js |
| 2566 // flog/camera.js |
| 2567 // flog/background.js |
| 2568 // flog/engine.js |
| 2569 |
| 2570 |
| 2571 /* Fake a Flog.* namespace */ |
| 2572 if(typeof(Flog) == 'undefined') var Flog = {}; |
| 2573 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; |
| 2574 |
| 2575 Flog.RayTracer.Color = Class.create(); |
| 2576 |
| 2577 Flog.RayTracer.Color.prototype = { |
| 2578 red : 0.0, |
| 2579 green : 0.0, |
| 2580 blue : 0.0, |
| 2581 |
| 2582 initialize : function(r, g, b) { |
| 2583 if(!r) r = 0.0; |
| 2584 if(!g) g = 0.0; |
| 2585 if(!b) b = 0.0; |
| 2586 |
| 2587 this.red = r; |
| 2588 this.green = g; |
| 2589 this.blue = b; |
| 2590 }, |
| 2591 |
| 2592 add : function(c1, c2){ |
| 2593 var result = new Flog.RayTracer.Color(0,0,0); |
| 2594 |
| 2595 result.red = c1.red + c2.red; |
| 2596 result.green = c1.green + c2.green; |
| 2597 result.blue = c1.blue + c2.blue; |
| 2598 |
| 2599 return result; |
| 2600 }, |
| 2601 |
| 2602 addScalar: function(c1, s){ |
| 2603 var result = new Flog.RayTracer.Color(0,0,0); |
| 2604 |
| 2605 result.red = c1.red + s; |
| 2606 result.green = c1.green + s; |
| 2607 result.blue = c1.blue + s; |
| 2608 |
| 2609 result.limit(); |
| 2610 |
| 2611 return result; |
| 2612 }, |
| 2613 |
| 2614 subtract: function(c1, c2){ |
| 2615 var result = new Flog.RayTracer.Color(0,0,0); |
| 2616 |
| 2617 result.red = c1.red - c2.red; |
| 2618 result.green = c1.green - c2.green; |
| 2619 result.blue = c1.blue - c2.blue; |
| 2620 |
| 2621 return result; |
| 2622 }, |
| 2623 |
| 2624 multiply : function(c1, c2) { |
| 2625 var result = new Flog.RayTracer.Color(0,0,0); |
| 2626 |
| 2627 result.red = c1.red * c2.red; |
| 2628 result.green = c1.green * c2.green; |
| 2629 result.blue = c1.blue * c2.blue; |
| 2630 |
| 2631 return result; |
| 2632 }, |
| 2633 |
| 2634 multiplyScalar : function(c1, f) { |
| 2635 var result = new Flog.RayTracer.Color(0,0,0); |
| 2636 |
| 2637 result.red = c1.red * f; |
| 2638 result.green = c1.green * f; |
| 2639 result.blue = c1.blue * f; |
| 2640 |
| 2641 return result; |
| 2642 }, |
| 2643 |
| 2644 divideFactor : function(c1, f) { |
| 2645 var result = new Flog.RayTracer.Color(0,0,0); |
| 2646 |
| 2647 result.red = c1.red / f; |
| 2648 result.green = c1.green / f; |
| 2649 result.blue = c1.blue / f; |
| 2650 |
| 2651 return result; |
| 2652 }, |
| 2653 |
| 2654 limit: function(){ |
| 2655 this.red = (this.red > 0.0) ? ( (this.red > 1.0) ? 1.0 : this.red ) : 0.
0; |
| 2656 this.green = (this.green > 0.0) ? ( (this.green > 1.0) ? 1.0 : this.gree
n ) : 0.0; |
| 2657 this.blue = (this.blue > 0.0) ? ( (this.blue > 1.0) ? 1.0 : this.blue )
: 0.0; |
| 2658 }, |
| 2659 |
| 2660 distance : function(color) { |
| 2661 var d = Math.abs(this.red - color.red) + Math.abs(this.green - color.gre
en) + Math.abs(this.blue - color.blue); |
| 2662 return d; |
| 2663 }, |
| 2664 |
| 2665 blend: function(c1, c2, w){ |
| 2666 var result = new Flog.RayTracer.Color(0,0,0); |
| 2667 result = Flog.RayTracer.Color.prototype.add( |
| 2668 Flog.RayTracer.Color.prototype.multiplyScalar(c1, 1 - w), |
| 2669 Flog.RayTracer.Color.prototype.multiplyScalar(c2, w) |
| 2670 ); |
| 2671 return result; |
| 2672 }, |
| 2673 |
| 2674 brightness : function() { |
| 2675 var r = Math.floor(this.red*255); |
| 2676 var g = Math.floor(this.green*255); |
| 2677 var b = Math.floor(this.blue*255); |
| 2678 return (r * 77 + g * 150 + b * 29) >> 8; |
| 2679 }, |
| 2680 |
| 2681 toString : function () { |
| 2682 var r = Math.floor(this.red*255); |
| 2683 var g = Math.floor(this.green*255); |
| 2684 var b = Math.floor(this.blue*255); |
| 2685 |
| 2686 return "rgb("+ r +","+ g +","+ b +")"; |
| 2687 } |
| 2688 } |
| 2689 /* Fake a Flog.* namespace */ |
| 2690 if(typeof(Flog) == 'undefined') var Flog = {}; |
| 2691 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; |
| 2692 |
| 2693 Flog.RayTracer.Light = Class.create(); |
| 2694 |
| 2695 Flog.RayTracer.Light.prototype = { |
| 2696 position: null, |
| 2697 color: null, |
| 2698 intensity: 10.0, |
| 2699 |
| 2700 initialize : function(pos, color, intensity) { |
| 2701 this.position = pos; |
| 2702 this.color = color; |
| 2703 this.intensity = (intensity ? intensity : 10.0); |
| 2704 }, |
| 2705 |
| 2706 getIntensity: function(distance){ |
| 2707 if(distance >= intensity) return 0; |
| 2708 |
| 2709 return Math.pow((intensity - distance) / strength, 0.2); |
| 2710 }, |
| 2711 |
| 2712 toString : function () { |
| 2713 return 'Light [' + this.position.x + ',' + this.position.y + ',' + this.
position.z + ']'; |
| 2714 } |
| 2715 } |
| 2716 /* Fake a Flog.* namespace */ |
| 2717 if(typeof(Flog) == 'undefined') var Flog = {}; |
| 2718 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; |
| 2719 |
| 2720 Flog.RayTracer.Vector = Class.create(); |
| 2721 |
| 2722 Flog.RayTracer.Vector.prototype = { |
| 2723 x : 0.0, |
| 2724 y : 0.0, |
| 2725 z : 0.0, |
| 2726 |
| 2727 initialize : function(x, y, z) { |
| 2728 this.x = (x ? x : 0); |
| 2729 this.y = (y ? y : 0); |
| 2730 this.z = (z ? z : 0); |
| 2731 }, |
| 2732 |
| 2733 copy: function(vector){ |
| 2734 this.x = vector.x; |
| 2735 this.y = vector.y; |
| 2736 this.z = vector.z; |
| 2737 }, |
| 2738 |
| 2739 normalize : function() { |
| 2740 var m = this.magnitude(); |
| 2741 return new Flog.RayTracer.Vector(this.x / m, this.y / m, this.z / m); |
| 2742 }, |
| 2743 |
| 2744 magnitude : function() { |
| 2745 return Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.
z)); |
| 2746 }, |
| 2747 |
| 2748 cross : function(w) { |
| 2749 return new Flog.RayTracer.Vector( |
| 2750 -this.z * w.y + this.y * w.z, |
| 2751 this.z * w.x - this.x * w.z, |
| 2752 -this.y * w.x + this.x * w.y); |
| 2753 }, |
| 2754 |
| 2755 dot : function(w) { |
| 2756 return this.x * w.x + this.y * w.y + this.z * w.z; |
| 2757 }, |
| 2758 |
| 2759 add : function(v, w) { |
| 2760 return new Flog.RayTracer.Vector(w.x + v.x, w.y + v.y, w.z + v.z); |
| 2761 }, |
| 2762 |
| 2763 subtract : function(v, w) { |
| 2764 if(!w || !v) throw 'Vectors must be defined [' + v + ',' + w + ']'; |
| 2765 return new Flog.RayTracer.Vector(v.x - w.x, v.y - w.y, v.z - w.z); |
| 2766 }, |
| 2767 |
| 2768 multiplyVector : function(v, w) { |
| 2769 return new Flog.RayTracer.Vector(v.x * w.x, v.y * w.y, v.z * w.z); |
| 2770 }, |
| 2771 |
| 2772 multiplyScalar : function(v, w) { |
| 2773 return new Flog.RayTracer.Vector(v.x * w, v.y * w, v.z * w); |
| 2774 }, |
| 2775 |
| 2776 toString : function () { |
| 2777 return 'Vector [' + this.x + ',' + this.y + ',' + this.z + ']'; |
| 2778 } |
| 2779 } |
| 2780 /* Fake a Flog.* namespace */ |
| 2781 if(typeof(Flog) == 'undefined') var Flog = {}; |
| 2782 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; |
| 2783 |
| 2784 Flog.RayTracer.Ray = Class.create(); |
| 2785 |
| 2786 Flog.RayTracer.Ray.prototype = { |
| 2787 position : null, |
| 2788 direction : null, |
| 2789 initialize : function(pos, dir) { |
| 2790 this.position = pos; |
| 2791 this.direction = dir; |
| 2792 }, |
| 2793 |
| 2794 toString : function () { |
| 2795 return 'Ray [' + this.position + ',' + this.direction + ']'; |
| 2796 } |
| 2797 } |
| 2798 /* Fake a Flog.* namespace */ |
| 2799 if(typeof(Flog) == 'undefined') var Flog = {}; |
| 2800 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; |
| 2801 |
| 2802 Flog.RayTracer.Scene = Class.create(); |
| 2803 |
| 2804 Flog.RayTracer.Scene.prototype = { |
| 2805 camera : null, |
| 2806 shapes : [], |
| 2807 lights : [], |
| 2808 background : null, |
| 2809 |
| 2810 initialize : function() { |
| 2811 this.camera = new Flog.RayTracer.Camera( |
| 2812 new Flog.RayTracer.Vector(0,0,-5), |
| 2813 new Flog.RayTracer.Vector(0,0,1), |
| 2814 new Flog.RayTracer.Vector(0,1,0) |
| 2815 ); |
| 2816 this.shapes = new Array(); |
| 2817 this.lights = new Array(); |
| 2818 this.background = new Flog.RayTracer.Background(new Flog.RayTracer.Color
(0,0,0.5), 0.2); |
| 2819 } |
| 2820 } |
| 2821 /* Fake a Flog.* namespace */ |
| 2822 if(typeof(Flog) == 'undefined') var Flog = {}; |
| 2823 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; |
| 2824 if(typeof(Flog.RayTracer.Material) == 'undefined') Flog.RayTracer.Material = {}; |
| 2825 |
| 2826 Flog.RayTracer.Material.BaseMaterial = Class.create(); |
| 2827 |
| 2828 Flog.RayTracer.Material.BaseMaterial.prototype = { |
| 2829 |
| 2830 gloss: 2.0, // [0...infinity] 0 = matt |
| 2831 transparency: 0.0, // 0=opaque |
| 2832 reflection: 0.0, // [0...infinity] 0 = no reflection |
| 2833 refraction: 0.50, |
| 2834 hasTexture: false, |
| 2835 |
| 2836 initialize : function() { |
| 2837 |
| 2838 }, |
| 2839 |
| 2840 getColor: function(u, v){ |
| 2841 |
| 2842 }, |
| 2843 |
| 2844 wrapUp: function(t){ |
| 2845 t = t % 2.0; |
| 2846 if(t < -1) t += 2.0; |
| 2847 if(t >= 1) t -= 2.0; |
| 2848 return t; |
| 2849 }, |
| 2850 |
| 2851 toString : function () { |
| 2852 return 'Material [gloss=' + this.gloss + ', transparency=' + this.transp
arency + ', hasTexture=' + this.hasTexture +']'; |
| 2853 } |
| 2854 } |
| 2855 /* Fake a Flog.* namespace */ |
| 2856 if(typeof(Flog) == 'undefined') var Flog = {}; |
| 2857 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; |
| 2858 |
| 2859 Flog.RayTracer.Material.Solid = Class.create(); |
| 2860 |
| 2861 Flog.RayTracer.Material.Solid.prototype = Object.extend( |
| 2862 new Flog.RayTracer.Material.BaseMaterial(), { |
| 2863 initialize : function(color, reflection, refraction, transparency, gloss
) { |
| 2864 this.color = color; |
| 2865 this.reflection = reflection; |
| 2866 this.transparency = transparency; |
| 2867 this.gloss = gloss; |
| 2868 this.hasTexture = false; |
| 2869 }, |
| 2870 |
| 2871 getColor: function(u, v){ |
| 2872 return this.color; |
| 2873 }, |
| 2874 |
| 2875 toString : function () { |
| 2876 return 'SolidMaterial [gloss=' + this.gloss + ', transparency=' + th
is.transparency + ', hasTexture=' + this.hasTexture +']'; |
| 2877 } |
| 2878 } |
| 2879 ); |
| 2880 /* Fake a Flog.* namespace */ |
| 2881 if(typeof(Flog) == 'undefined') var Flog = {}; |
| 2882 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; |
| 2883 |
| 2884 Flog.RayTracer.Material.Chessboard = Class.create(); |
| 2885 |
| 2886 Flog.RayTracer.Material.Chessboard.prototype = Object.extend( |
| 2887 new Flog.RayTracer.Material.BaseMaterial(), { |
| 2888 colorEven: null, |
| 2889 colorOdd: null, |
| 2890 density: 0.5, |
| 2891 |
| 2892 initialize : function(colorEven, colorOdd, reflection, transparency, glo
ss, density) { |
| 2893 this.colorEven = colorEven; |
| 2894 this.colorOdd = colorOdd; |
| 2895 this.reflection = reflection; |
| 2896 this.transparency = transparency; |
| 2897 this.gloss = gloss; |
| 2898 this.density = density; |
| 2899 this.hasTexture = true; |
| 2900 }, |
| 2901 |
| 2902 getColor: function(u, v){ |
| 2903 var t = this.wrapUp(u * this.density) * this.wrapUp(v * this.density
); |
| 2904 |
| 2905 if(t < 0.0) |
| 2906 return this.colorEven; |
| 2907 else |
| 2908 return this.colorOdd; |
| 2909 }, |
| 2910 |
| 2911 toString : function () { |
| 2912 return 'ChessMaterial [gloss=' + this.gloss + ', transparency=' + th
is.transparency + ', hasTexture=' + this.hasTexture +']'; |
| 2913 } |
| 2914 } |
| 2915 ); |
| 2916 /* Fake a Flog.* namespace */ |
| 2917 if(typeof(Flog) == 'undefined') var Flog = {}; |
| 2918 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; |
| 2919 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {}; |
| 2920 |
| 2921 Flog.RayTracer.Shape.BaseShape = Class.create(); |
| 2922 |
| 2923 Flog.RayTracer.Shape.BaseShape.prototype = { |
| 2924 position: null, |
| 2925 material: null, |
| 2926 |
| 2927 initialize : function() { |
| 2928 this.position = new Vector(0,0,0); |
| 2929 this.material = new Flog.RayTracer.Material.SolidMaterial( |
| 2930 new Flog.RayTracer.Color(1,0,1), |
| 2931 0, |
| 2932 0, |
| 2933 0 |
| 2934 ); |
| 2935 }, |
| 2936 |
| 2937 toString : function () { |
| 2938 return 'Material [gloss=' + this.gloss + ', transparency=' + this.transp
arency + ', hasTexture=' + this.hasTexture +']'; |
| 2939 } |
| 2940 } |
| 2941 /* Fake a Flog.* namespace */ |
| 2942 if(typeof(Flog) == 'undefined') var Flog = {}; |
| 2943 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; |
| 2944 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {}; |
| 2945 |
| 2946 Flog.RayTracer.Shape.Sphere = Class.create(); |
| 2947 |
| 2948 Flog.RayTracer.Shape.Sphere.prototype = { |
| 2949 initialize : function(pos, radius, material) { |
| 2950 this.radius = radius; |
| 2951 this.position = pos; |
| 2952 this.material = material; |
| 2953 }, |
| 2954 |
| 2955 intersect: function(ray){ |
| 2956 var info = new Flog.RayTracer.IntersectionInfo(); |
| 2957 info.shape = this; |
| 2958 |
| 2959 var dst = Flog.RayTracer.Vector.prototype.subtract(ray.position, this.po
sition); |
| 2960 |
| 2961 var B = dst.dot(ray.direction); |
| 2962 var C = dst.dot(dst) - (this.radius * this.radius); |
| 2963 var D = (B * B) - C; |
| 2964 |
| 2965 if(D > 0){ // intersection! |
| 2966 info.isHit = true; |
| 2967 info.distance = (-B) - Math.sqrt(D); |
| 2968 info.position = Flog.RayTracer.Vector.prototype.add( |
| 2969 ray.position, |
| 2970 Flog.RayTracer.Vector.prototype.
multiplyScalar( |
| 2971 ray.direction, |
| 2972 info.distance |
| 2973 ) |
| 2974 ); |
| 2975 info.normal = Flog.RayTracer.Vector.prototype.subtract( |
| 2976 info.position, |
| 2977 this.position |
| 2978 ).normalize(); |
| 2979 |
| 2980 info.color = this.material.getColor(0,0); |
| 2981 } else { |
| 2982 info.isHit = false; |
| 2983 } |
| 2984 return info; |
| 2985 }, |
| 2986 |
| 2987 toString : function () { |
| 2988 return 'Sphere [position=' + this.position + ', radius=' + this.radius +
']'; |
| 2989 } |
| 2990 } |
| 2991 /* Fake a Flog.* namespace */ |
| 2992 if(typeof(Flog) == 'undefined') var Flog = {}; |
| 2993 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; |
| 2994 if(typeof(Flog.RayTracer.Shape) == 'undefined') Flog.RayTracer.Shape = {}; |
| 2995 |
| 2996 Flog.RayTracer.Shape.Plane = Class.create(); |
| 2997 |
| 2998 Flog.RayTracer.Shape.Plane.prototype = { |
| 2999 d: 0.0, |
| 3000 |
| 3001 initialize : function(pos, d, material) { |
| 3002 this.position = pos; |
| 3003 this.d = d; |
| 3004 this.material = material; |
| 3005 }, |
| 3006 |
| 3007 intersect: function(ray){ |
| 3008 var info = new Flog.RayTracer.IntersectionInfo(); |
| 3009 |
| 3010 var Vd = this.position.dot(ray.direction); |
| 3011 if(Vd == 0) return info; // no intersection |
| 3012 |
| 3013 var t = -(this.position.dot(ray.position) + this.d) / Vd; |
| 3014 if(t <= 0) return info; |
| 3015 |
| 3016 info.shape = this; |
| 3017 info.isHit = true; |
| 3018 info.position = Flog.RayTracer.Vector.prototype.add( |
| 3019 ray.position, |
| 3020 Flog.RayTracer.Vector.prototype.mult
iplyScalar( |
| 3021 ray.direction, |
| 3022 t |
| 3023 ) |
| 3024 ); |
| 3025 info.normal = this.position; |
| 3026 info.distance = t; |
| 3027 |
| 3028 if(this.material.hasTexture){ |
| 3029 var vU = new Flog.RayTracer.Vector(this.position.y, this.position.z,
-this.position.x); |
| 3030 var vV = vU.cross(this.position); |
| 3031 var u = info.position.dot(vU); |
| 3032 var v = info.position.dot(vV); |
| 3033 info.color = this.material.getColor(u,v); |
| 3034 } else { |
| 3035 info.color = this.material.getColor(0,0); |
| 3036 } |
| 3037 |
| 3038 return info; |
| 3039 }, |
| 3040 |
| 3041 toString : function () { |
| 3042 return 'Plane [' + this.position + ', d=' + this.d + ']'; |
| 3043 } |
| 3044 } |
| 3045 /* Fake a Flog.* namespace */ |
| 3046 if(typeof(Flog) == 'undefined') var Flog = {}; |
| 3047 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; |
| 3048 |
| 3049 Flog.RayTracer.IntersectionInfo = Class.create(); |
| 3050 |
| 3051 Flog.RayTracer.IntersectionInfo.prototype = { |
| 3052 isHit: false, |
| 3053 hitCount: 0, |
| 3054 shape: null, |
| 3055 position: null, |
| 3056 normal: null, |
| 3057 color: null, |
| 3058 distance: null, |
| 3059 |
| 3060 initialize : function() { |
| 3061 this.color = new Flog.RayTracer.Color(0,0,0); |
| 3062 }, |
| 3063 |
| 3064 toString : function () { |
| 3065 return 'Intersection [' + this.position + ']'; |
| 3066 } |
| 3067 } |
| 3068 /* Fake a Flog.* namespace */ |
| 3069 if(typeof(Flog) == 'undefined') var Flog = {}; |
| 3070 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; |
| 3071 |
| 3072 Flog.RayTracer.Camera = Class.create(); |
| 3073 |
| 3074 Flog.RayTracer.Camera.prototype = { |
| 3075 position: null, |
| 3076 lookAt: null, |
| 3077 equator: null, |
| 3078 up: null, |
| 3079 screen: null, |
| 3080 |
| 3081 initialize : function(pos, lookAt, up) { |
| 3082 this.position = pos; |
| 3083 this.lookAt = lookAt; |
| 3084 this.up = up; |
| 3085 this.equator = lookAt.normalize().cross(this.up); |
| 3086 this.screen = Flog.RayTracer.Vector.prototype.add(this.position, this.lo
okAt); |
| 3087 }, |
| 3088 |
| 3089 getRay: function(vx, vy){ |
| 3090 var pos = Flog.RayTracer.Vector.prototype.subtract( |
| 3091 this.screen, |
| 3092 Flog.RayTracer.Vector.prototype.subtract( |
| 3093 Flog.RayTracer.Vector.prototype.multiplyScalar(this.equator, vx)
, |
| 3094 Flog.RayTracer.Vector.prototype.multiplyScalar(this.up, vy) |
| 3095 ) |
| 3096 ); |
| 3097 pos.y = pos.y * -1; |
| 3098 var dir = Flog.RayTracer.Vector.prototype.subtract( |
| 3099 pos, |
| 3100 this.position |
| 3101 ); |
| 3102 |
| 3103 var ray = new Flog.RayTracer.Ray(pos, dir.normalize()); |
| 3104 |
| 3105 return ray; |
| 3106 }, |
| 3107 |
| 3108 toString : function () { |
| 3109 return 'Ray []'; |
| 3110 } |
| 3111 } |
| 3112 /* Fake a Flog.* namespace */ |
| 3113 if(typeof(Flog) == 'undefined') var Flog = {}; |
| 3114 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; |
| 3115 |
| 3116 Flog.RayTracer.Background = Class.create(); |
| 3117 |
| 3118 Flog.RayTracer.Background.prototype = { |
| 3119 color : null, |
| 3120 ambience : 0.0, |
| 3121 |
| 3122 initialize : function(color, ambience) { |
| 3123 this.color = color; |
| 3124 this.ambience = ambience; |
| 3125 } |
| 3126 } |
| 3127 /* Fake a Flog.* namespace */ |
| 3128 if(typeof(Flog) == 'undefined') var Flog = {}; |
| 3129 if(typeof(Flog.RayTracer) == 'undefined') Flog.RayTracer = {}; |
| 3130 |
| 3131 Flog.RayTracer.Engine = Class.create(); |
| 3132 |
| 3133 Flog.RayTracer.Engine.prototype = { |
| 3134 canvas: null, /* 2d context we can render to */ |
| 3135 |
| 3136 initialize: function(options){ |
| 3137 this.options = Object.extend({ |
| 3138 canvasHeight: 100, |
| 3139 canvasWidth: 100, |
| 3140 pixelWidth: 2, |
| 3141 pixelHeight: 2, |
| 3142 renderDiffuse: false, |
| 3143 renderShadows: false, |
| 3144 renderHighlights: false, |
| 3145 renderReflections: false, |
| 3146 rayDepth: 2 |
| 3147 }, options || {}); |
| 3148 |
| 3149 this.options.canvasHeight /= this.options.pixelHeight; |
| 3150 this.options.canvasWidth /= this.options.pixelWidth; |
| 3151 |
| 3152 /* TODO: dynamically include other scripts */ |
| 3153 }, |
| 3154 |
| 3155 setPixel: function(x, y, color){ |
| 3156 var pxW, pxH; |
| 3157 pxW = this.options.pixelWidth; |
| 3158 pxH = this.options.pixelHeight; |
| 3159 |
| 3160 if (this.canvas) { |
| 3161 this.canvas.fillStyle = color.toString(); |
| 3162 this.canvas.fillRect (x * pxW, y * pxH, pxW, pxH); |
| 3163 } else { |
| 3164 if (x === y) { |
| 3165 checkNumber += color.brightness(); |
| 3166 } |
| 3167 // print(x * pxW, y * pxH, pxW, pxH); |
| 3168 } |
| 3169 }, |
| 3170 |
| 3171 renderScene: function(scene, canvas){ |
| 3172 checkNumber = 0; |
| 3173 /* Get canvas */ |
| 3174 if (canvas) { |
| 3175 this.canvas = canvas.getContext("2d"); |
| 3176 } else { |
| 3177 this.canvas = null; |
| 3178 } |
| 3179 |
| 3180 var canvasHeight = this.options.canvasHeight; |
| 3181 var canvasWidth = this.options.canvasWidth; |
| 3182 |
| 3183 for(var y=0; y < canvasHeight; y++){ |
| 3184 for(var x=0; x < canvasWidth; x++){ |
| 3185 var yp = y * 1.0 / canvasHeight * 2 - 1; |
| 3186 var xp = x * 1.0 / canvasWidth * 2 - 1; |
| 3187 |
| 3188 var ray = scene.camera.getRay(xp, yp); |
| 3189 |
| 3190 var color = this.getPixelColor(ray, scene); |
| 3191 |
| 3192 this.setPixel(x, y, color); |
| 3193 } |
| 3194 } |
| 3195 if (checkNumber !== 2321) { |
| 3196 throw new Error("Scene rendered incorrectly"); |
| 3197 } |
| 3198 }, |
| 3199 |
| 3200 getPixelColor: function(ray, scene){ |
| 3201 var info = this.testIntersection(ray, scene, null); |
| 3202 if(info.isHit){ |
| 3203 var color = this.rayTrace(info, ray, scene, 0); |
| 3204 return color; |
| 3205 } |
| 3206 return scene.background.color; |
| 3207 }, |
| 3208 |
| 3209 testIntersection: function(ray, scene, exclude){ |
| 3210 var hits = 0; |
| 3211 var best = new Flog.RayTracer.IntersectionInfo(); |
| 3212 best.distance = 2000; |
| 3213 |
| 3214 for(var i=0; i<scene.shapes.length; i++){ |
| 3215 var shape = scene.shapes[i]; |
| 3216 |
| 3217 if(shape != exclude){ |
| 3218 var info = shape.intersect(ray); |
| 3219 if(info.isHit && info.distance >= 0 && info.distance < best.dist
ance){ |
| 3220 best = info; |
| 3221 hits++; |
| 3222 } |
| 3223 } |
| 3224 } |
| 3225 best.hitCount = hits; |
| 3226 return best; |
| 3227 }, |
| 3228 |
| 3229 getReflectionRay: function(P,N,V){ |
| 3230 var c1 = -N.dot(V); |
| 3231 var R1 = Flog.RayTracer.Vector.prototype.add( |
| 3232 Flog.RayTracer.Vector.prototype.multiplyScalar(N, 2*c1), |
| 3233 V |
| 3234 ); |
| 3235 return new Flog.RayTracer.Ray(P, R1); |
| 3236 }, |
| 3237 |
| 3238 rayTrace: function(info, ray, scene, depth){ |
| 3239 // Calc ambient |
| 3240 var color = Flog.RayTracer.Color.prototype.multiplyScalar(info.color, sc
ene.background.ambience); |
| 3241 var oldColor = color; |
| 3242 var shininess = Math.pow(10, info.shape.material.gloss + 1); |
| 3243 |
| 3244 for(var i=0; i<scene.lights.length; i++){ |
| 3245 var light = scene.lights[i]; |
| 3246 |
| 3247 // Calc diffuse lighting |
| 3248 var v = Flog.RayTracer.Vector.prototype.subtract( |
| 3249 light.position, |
| 3250 info.position |
| 3251 ).normalize(); |
| 3252 |
| 3253 if(this.options.renderDiffuse){ |
| 3254 var L = v.dot(info.normal); |
| 3255 if(L > 0.0){ |
| 3256 color = Flog.RayTracer.Color.prototype.add( |
| 3257 color, |
| 3258 Flog.RayTracer.Color.prototype.multiply( |
| 3259 info.color, |
| 3260 Flog.RayTracer.Color.prototype.multi
plyScalar( |
| 3261 light.color, |
| 3262 L |
| 3263 ) |
| 3264 ) |
| 3265 ); |
| 3266 } |
| 3267 } |
| 3268 |
| 3269 // The greater the depth the more accurate the colours, but |
| 3270 // this is exponentially (!) expensive |
| 3271 if(depth <= this.options.rayDepth){ |
| 3272 // calculate reflection ray |
| 3273 if(this.options.renderReflections && info.shape.material.reflection >
0) |
| 3274 { |
| 3275 var reflectionRay = this.getReflectionRay(info.position, info.norm
al, ray.direction); |
| 3276 var refl = this.testIntersection(reflectionRay, scene, info.shape)
; |
| 3277 |
| 3278 if (refl.isHit && refl.distance > 0){ |
| 3279 refl.color = this.rayTrace(refl, reflectionRay, scene, depth +
1); |
| 3280 } else { |
| 3281 refl.color = scene.background.color; |
| 3282 } |
| 3283 |
| 3284 color = Flog.RayTracer.Color.prototype.blend( |
| 3285 color, |
| 3286 refl.color, |
| 3287 info.shape.material.reflection |
| 3288 ); |
| 3289 } |
| 3290 |
| 3291 // Refraction |
| 3292 /* TODO */ |
| 3293 } |
| 3294 |
| 3295 /* Render shadows and highlights */ |
| 3296 |
| 3297 var shadowInfo = new Flog.RayTracer.IntersectionInfo(); |
| 3298 |
| 3299 if(this.options.renderShadows){ |
| 3300 var shadowRay = new Flog.RayTracer.Ray(info.position, v); |
| 3301 |
| 3302 shadowInfo = this.testIntersection(shadowRay, scene, info.shape)
; |
| 3303 if(shadowInfo.isHit && shadowInfo.shape != info.shape /*&& shado
wInfo.shape.type != 'PLANE'*/){ |
| 3304 var vA = Flog.RayTracer.Color.prototype.multiplyScalar(color
, 0.5); |
| 3305 var dB = (0.5 * Math.pow(shadowInfo.shape.material.transpare
ncy, 0.5)); |
| 3306 color = Flog.RayTracer.Color.prototype.addScalar(vA,dB); |
| 3307 } |
| 3308 } |
| 3309 |
| 3310 // Phong specular highlights |
| 3311 if(this.options.renderHighlights && !shadowInfo.isHit && info.shape.materi
al.gloss > 0){ |
| 3312 var Lv = Flog.RayTracer.Vector.prototype.subtract( |
| 3313 info.shape.position, |
| 3314 light.position |
| 3315 ).normalize(); |
| 3316 |
| 3317 var E = Flog.RayTracer.Vector.prototype.subtract( |
| 3318 scene.camera.position, |
| 3319 info.shape.position |
| 3320 ).normalize(); |
| 3321 |
| 3322 var H = Flog.RayTracer.Vector.prototype.subtract( |
| 3323 E, |
| 3324 Lv |
| 3325 ).normalize(); |
| 3326 |
| 3327 var glossWeight = Math.pow(Math.max(info.normal.dot(H), 0), shininess); |
| 3328 color = Flog.RayTracer.Color.prototype.add( |
| 3329 Flog.RayTracer.Color.prototype.multiplyScalar(light.
color, glossWeight), |
| 3330 color |
| 3331 ); |
| 3332 } |
| 3333 } |
| 3334 color.limit(); |
| 3335 return color; |
| 3336 } |
| 3337 }; |
| 3338 |
| 3339 |
| 3340 function renderScene(){ |
| 3341 var scene = new Flog.RayTracer.Scene(); |
| 3342 |
| 3343 scene.camera = new Flog.RayTracer.Camera( |
| 3344 new Flog.RayTracer.Vector(0, 0, -15), |
| 3345 new Flog.RayTracer.Vector(-0.2, 0, 5), |
| 3346 new Flog.RayTracer.Vector(0, 1, 0) |
| 3347 ); |
| 3348 |
| 3349 scene.background = new Flog.RayTracer.Background( |
| 3350 new Flog.RayTracer.Color(0.5, 0.5, 0.5), |
| 3351 0.4 |
| 3352 ); |
| 3353 |
| 3354 var sphere = new Flog.RayTracer.Shape.Sphere( |
| 3355 new Flog.RayTracer.Vector(-1.5, 1.5, 2), |
| 3356 1.5, |
| 3357 new Flog.RayTracer.Material.Solid( |
| 3358 new Flog.RayTracer.Color(0,0.5,0.5), |
| 3359 0.3, |
| 3360 0.0, |
| 3361 0.0, |
| 3362 2.0 |
| 3363 ) |
| 3364 ); |
| 3365 |
| 3366 var sphere1 = new Flog.RayTracer.Shape.Sphere( |
| 3367 new Flog.RayTracer.Vector(1, 0.25, 1), |
| 3368 0.5, |
| 3369 new Flog.RayTracer.Material.Solid( |
| 3370 new Flog.RayTracer.Color(0.9,0.9,0.9), |
| 3371 0.1, |
| 3372 0.0, |
| 3373 0.0, |
| 3374 1.5 |
| 3375 ) |
| 3376 ); |
| 3377 |
| 3378 var plane = new Flog.RayTracer.Shape.Plane( |
| 3379 new Flog.RayTracer.Vector(0.1, 0.9, -0.5).normal
ize(), |
| 3380 1.2, |
| 3381 new Flog.RayTracer.Material.Chessboard( |
| 3382 new Flog.RayTracer.Color(1,1,1), |
| 3383 new Flog.RayTracer.Color(0,0,0), |
| 3384 0.2, |
| 3385 0.0, |
| 3386 1.0, |
| 3387 0.7 |
| 3388 ) |
| 3389 ); |
| 3390 |
| 3391 scene.shapes.push(plane); |
| 3392 scene.shapes.push(sphere); |
| 3393 scene.shapes.push(sphere1); |
| 3394 |
| 3395 var light = new Flog.RayTracer.Light( |
| 3396 new Flog.RayTracer.Vector(5, 10, -1), |
| 3397 new Flog.RayTracer.Color(0.8, 0.8, 0.8) |
| 3398 ); |
| 3399 |
| 3400 var light1 = new Flog.RayTracer.Light( |
| 3401 new Flog.RayTracer.Vector(-3, 5, -15), |
| 3402 new Flog.RayTracer.Color(0.8, 0.8, 0.8), |
| 3403 100 |
| 3404 ); |
| 3405 |
| 3406 scene.lights.push(light); |
| 3407 scene.lights.push(light1); |
| 3408 |
| 3409 var imageWidth = 100; // $F('imageWidth'); |
| 3410 var imageHeight = 100; // $F('imageHeight'); |
| 3411 var pixelSize = "5,5".split(','); // $F('pixelSize').split(','); |
| 3412 var renderDiffuse = true; // $F('renderDiffuse'); |
| 3413 var renderShadows = true; // $F('renderShadows'); |
| 3414 var renderHighlights = true; // $F('renderHighlights'); |
| 3415 var renderReflections = true; // $F('renderReflections'); |
| 3416 var rayDepth = 2;//$F('rayDepth'); |
| 3417 |
| 3418 var raytracer = new Flog.RayTracer.Engine( |
| 3419 { |
| 3420 canvasWidth: imageWidth, |
| 3421 canvasHeight: imageHeight, |
| 3422 pixelWidth: pixelSize[0], |
| 3423 pixelHeight: pixelSize[1], |
| 3424 "renderDiffuse": renderDiffuse, |
| 3425 "renderHighlights": renderHighlights, |
| 3426 "renderShadows": renderShadows, |
| 3427 "renderReflections": renderReflections, |
| 3428 "rayDepth": rayDepth |
| 3429 } |
| 3430 ); |
| 3431 |
| 3432 raytracer.renderScene(scene, null, 0); |
| 3433 } |
| 3434 |
| 3435 window.onload = function(){ |
| 3436 startTest("v8-raytrace", ''); |
| 3437 |
| 3438 test("RayTrace", renderScene); |
| 3439 |
| 3440 endTest(); |
| 3441 }; |
| 3442 </script> |
| 3443 </body> |
| 3444 </html> |
OLD | NEW |