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