annotate public/javascripts/prototype.js @ 874:12771bc38f20 feature_123

Close obsolete branch feature_123
author Chris Cannam
date Sat, 02 Apr 2011 09:37:51 +0100
parents 513646585e45
children cbce1fd3b1b7
rev   line source
Chris@0 1 /* Prototype JavaScript framework, version 1.6.0.3
Chris@0 2 * (c) 2005-2008 Sam Stephenson
Chris@0 3 *
Chris@0 4 * Prototype is freely distributable under the terms of an MIT-style license.
Chris@0 5 * For details, see the Prototype web site: http://www.prototypejs.org/
Chris@0 6 *
Chris@0 7 *--------------------------------------------------------------------------*/
Chris@0 8
Chris@0 9 var Prototype = {
Chris@0 10 Version: '1.6.0.3',
Chris@0 11
Chris@0 12 Browser: {
Chris@0 13 IE: !!(window.attachEvent &&
Chris@0 14 navigator.userAgent.indexOf('Opera') === -1),
Chris@0 15 Opera: navigator.userAgent.indexOf('Opera') > -1,
Chris@0 16 WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
Chris@0 17 Gecko: navigator.userAgent.indexOf('Gecko') > -1 &&
Chris@0 18 navigator.userAgent.indexOf('KHTML') === -1,
Chris@0 19 MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
Chris@0 20 },
Chris@0 21
Chris@0 22 BrowserFeatures: {
Chris@0 23 XPath: !!document.evaluate,
Chris@0 24 SelectorsAPI: !!document.querySelector,
Chris@0 25 ElementExtensions: !!window.HTMLElement,
Chris@0 26 SpecificElementExtensions:
Chris@0 27 document.createElement('div')['__proto__'] &&
Chris@0 28 document.createElement('div')['__proto__'] !==
Chris@0 29 document.createElement('form')['__proto__']
Chris@0 30 },
Chris@0 31
Chris@0 32 ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
Chris@0 33 JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
Chris@0 34
Chris@0 35 emptyFunction: function() { },
Chris@0 36 K: function(x) { return x }
Chris@0 37 };
Chris@0 38
Chris@0 39 if (Prototype.Browser.MobileSafari)
Chris@0 40 Prototype.BrowserFeatures.SpecificElementExtensions = false;
Chris@0 41
Chris@0 42
Chris@0 43 /* Based on Alex Arnell's inheritance implementation. */
Chris@0 44 var Class = {
Chris@0 45 create: function() {
Chris@0 46 var parent = null, properties = $A(arguments);
Chris@0 47 if (Object.isFunction(properties[0]))
Chris@0 48 parent = properties.shift();
Chris@0 49
Chris@0 50 function klass() {
Chris@0 51 this.initialize.apply(this, arguments);
Chris@0 52 }
Chris@0 53
Chris@0 54 Object.extend(klass, Class.Methods);
Chris@0 55 klass.superclass = parent;
Chris@0 56 klass.subclasses = [];
Chris@0 57
Chris@0 58 if (parent) {
Chris@0 59 var subclass = function() { };
Chris@0 60 subclass.prototype = parent.prototype;
Chris@0 61 klass.prototype = new subclass;
Chris@0 62 parent.subclasses.push(klass);
Chris@0 63 }
Chris@0 64
Chris@0 65 for (var i = 0; i < properties.length; i++)
Chris@0 66 klass.addMethods(properties[i]);
Chris@0 67
Chris@0 68 if (!klass.prototype.initialize)
Chris@0 69 klass.prototype.initialize = Prototype.emptyFunction;
Chris@0 70
Chris@0 71 klass.prototype.constructor = klass;
Chris@0 72
Chris@0 73 return klass;
Chris@0 74 }
Chris@0 75 };
Chris@0 76
Chris@0 77 Class.Methods = {
Chris@0 78 addMethods: function(source) {
Chris@0 79 var ancestor = this.superclass && this.superclass.prototype;
Chris@0 80 var properties = Object.keys(source);
Chris@0 81
Chris@0 82 if (!Object.keys({ toString: true }).length)
Chris@0 83 properties.push("toString", "valueOf");
Chris@0 84
Chris@0 85 for (var i = 0, length = properties.length; i < length; i++) {
Chris@0 86 var property = properties[i], value = source[property];
Chris@0 87 if (ancestor && Object.isFunction(value) &&
Chris@0 88 value.argumentNames().first() == "$super") {
Chris@0 89 var method = value;
Chris@0 90 value = (function(m) {
Chris@0 91 return function() { return ancestor[m].apply(this, arguments) };
Chris@0 92 })(property).wrap(method);
Chris@0 93
Chris@0 94 value.valueOf = method.valueOf.bind(method);
Chris@0 95 value.toString = method.toString.bind(method);
Chris@0 96 }
Chris@0 97 this.prototype[property] = value;
Chris@0 98 }
Chris@0 99
Chris@0 100 return this;
Chris@0 101 }
Chris@0 102 };
Chris@0 103
Chris@0 104 var Abstract = { };
Chris@0 105
Chris@0 106 Object.extend = function(destination, source) {
Chris@0 107 for (var property in source)
Chris@0 108 destination[property] = source[property];
Chris@0 109 return destination;
Chris@0 110 };
Chris@0 111
Chris@0 112 Object.extend(Object, {
Chris@0 113 inspect: function(object) {
Chris@0 114 try {
Chris@0 115 if (Object.isUndefined(object)) return 'undefined';
Chris@0 116 if (object === null) return 'null';
Chris@0 117 return object.inspect ? object.inspect() : String(object);
Chris@0 118 } catch (e) {
Chris@0 119 if (e instanceof RangeError) return '...';
Chris@0 120 throw e;
Chris@0 121 }
Chris@0 122 },
Chris@0 123
Chris@0 124 toJSON: function(object) {
Chris@0 125 var type = typeof object;
Chris@0 126 switch (type) {
Chris@0 127 case 'undefined':
Chris@0 128 case 'function':
Chris@0 129 case 'unknown': return;
Chris@0 130 case 'boolean': return object.toString();
Chris@0 131 }
Chris@0 132
Chris@0 133 if (object === null) return 'null';
Chris@0 134 if (object.toJSON) return object.toJSON();
Chris@0 135 if (Object.isElement(object)) return;
Chris@0 136
Chris@0 137 var results = [];
Chris@0 138 for (var property in object) {
Chris@0 139 var value = Object.toJSON(object[property]);
Chris@0 140 if (!Object.isUndefined(value))
Chris@0 141 results.push(property.toJSON() + ': ' + value);
Chris@0 142 }
Chris@0 143
Chris@0 144 return '{' + results.join(', ') + '}';
Chris@0 145 },
Chris@0 146
Chris@0 147 toQueryString: function(object) {
Chris@0 148 return $H(object).toQueryString();
Chris@0 149 },
Chris@0 150
Chris@0 151 toHTML: function(object) {
Chris@0 152 return object && object.toHTML ? object.toHTML() : String.interpret(object);
Chris@0 153 },
Chris@0 154
Chris@0 155 keys: function(object) {
Chris@0 156 var keys = [];
Chris@0 157 for (var property in object)
Chris@0 158 keys.push(property);
Chris@0 159 return keys;
Chris@0 160 },
Chris@0 161
Chris@0 162 values: function(object) {
Chris@0 163 var values = [];
Chris@0 164 for (var property in object)
Chris@0 165 values.push(object[property]);
Chris@0 166 return values;
Chris@0 167 },
Chris@0 168
Chris@0 169 clone: function(object) {
Chris@0 170 return Object.extend({ }, object);
Chris@0 171 },
Chris@0 172
Chris@0 173 isElement: function(object) {
Chris@0 174 return !!(object && object.nodeType == 1);
Chris@0 175 },
Chris@0 176
Chris@0 177 isArray: function(object) {
Chris@0 178 return object != null && typeof object == "object" &&
Chris@0 179 'splice' in object && 'join' in object;
Chris@0 180 },
Chris@0 181
Chris@0 182 isHash: function(object) {
Chris@0 183 return object instanceof Hash;
Chris@0 184 },
Chris@0 185
Chris@0 186 isFunction: function(object) {
Chris@0 187 return typeof object == "function";
Chris@0 188 },
Chris@0 189
Chris@0 190 isString: function(object) {
Chris@0 191 return typeof object == "string";
Chris@0 192 },
Chris@0 193
Chris@0 194 isNumber: function(object) {
Chris@0 195 return typeof object == "number";
Chris@0 196 },
Chris@0 197
Chris@0 198 isUndefined: function(object) {
Chris@0 199 return typeof object == "undefined";
Chris@0 200 }
Chris@0 201 });
Chris@0 202
Chris@0 203 Object.extend(Function.prototype, {
Chris@0 204 argumentNames: function() {
Chris@0 205 var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1]
Chris@0 206 .replace(/\s+/g, '').split(',');
Chris@0 207 return names.length == 1 && !names[0] ? [] : names;
Chris@0 208 },
Chris@0 209
Chris@0 210 bind: function() {
Chris@0 211 if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
Chris@0 212 var __method = this, args = $A(arguments), object = args.shift();
Chris@0 213 return function() {
Chris@0 214 return __method.apply(object, args.concat($A(arguments)));
Chris@0 215 }
Chris@0 216 },
Chris@0 217
Chris@0 218 bindAsEventListener: function() {
Chris@0 219 var __method = this, args = $A(arguments), object = args.shift();
Chris@0 220 return function(event) {
Chris@0 221 return __method.apply(object, [event || window.event].concat(args));
Chris@0 222 }
Chris@0 223 },
Chris@0 224
Chris@0 225 curry: function() {
Chris@0 226 if (!arguments.length) return this;
Chris@0 227 var __method = this, args = $A(arguments);
Chris@0 228 return function() {
Chris@0 229 return __method.apply(this, args.concat($A(arguments)));
Chris@0 230 }
Chris@0 231 },
Chris@0 232
Chris@0 233 delay: function() {
Chris@0 234 var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
Chris@0 235 return window.setTimeout(function() {
Chris@0 236 return __method.apply(__method, args);
Chris@0 237 }, timeout);
Chris@0 238 },
Chris@0 239
Chris@0 240 defer: function() {
Chris@0 241 var args = [0.01].concat($A(arguments));
Chris@0 242 return this.delay.apply(this, args);
Chris@0 243 },
Chris@0 244
Chris@0 245 wrap: function(wrapper) {
Chris@0 246 var __method = this;
Chris@0 247 return function() {
Chris@0 248 return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
Chris@0 249 }
Chris@0 250 },
Chris@0 251
Chris@0 252 methodize: function() {
Chris@0 253 if (this._methodized) return this._methodized;
Chris@0 254 var __method = this;
Chris@0 255 return this._methodized = function() {
Chris@0 256 return __method.apply(null, [this].concat($A(arguments)));
Chris@0 257 };
Chris@0 258 }
Chris@0 259 });
Chris@0 260
Chris@0 261 Date.prototype.toJSON = function() {
Chris@0 262 return '"' + this.getUTCFullYear() + '-' +
Chris@0 263 (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
Chris@0 264 this.getUTCDate().toPaddedString(2) + 'T' +
Chris@0 265 this.getUTCHours().toPaddedString(2) + ':' +
Chris@0 266 this.getUTCMinutes().toPaddedString(2) + ':' +
Chris@0 267 this.getUTCSeconds().toPaddedString(2) + 'Z"';
Chris@0 268 };
Chris@0 269
Chris@0 270 var Try = {
Chris@0 271 these: function() {
Chris@0 272 var returnValue;
Chris@0 273
Chris@0 274 for (var i = 0, length = arguments.length; i < length; i++) {
Chris@0 275 var lambda = arguments[i];
Chris@0 276 try {
Chris@0 277 returnValue = lambda();
Chris@0 278 break;
Chris@0 279 } catch (e) { }
Chris@0 280 }
Chris@0 281
Chris@0 282 return returnValue;
Chris@0 283 }
Chris@0 284 };
Chris@0 285
Chris@0 286 RegExp.prototype.match = RegExp.prototype.test;
Chris@0 287
Chris@0 288 RegExp.escape = function(str) {
Chris@0 289 return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
Chris@0 290 };
Chris@0 291
Chris@0 292 /*--------------------------------------------------------------------------*/
Chris@0 293
Chris@0 294 var PeriodicalExecuter = Class.create({
Chris@0 295 initialize: function(callback, frequency) {
Chris@0 296 this.callback = callback;
Chris@0 297 this.frequency = frequency;
Chris@0 298 this.currentlyExecuting = false;
Chris@0 299
Chris@0 300 this.registerCallback();
Chris@0 301 },
Chris@0 302
Chris@0 303 registerCallback: function() {
Chris@0 304 this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
Chris@0 305 },
Chris@0 306
Chris@0 307 execute: function() {
Chris@0 308 this.callback(this);
Chris@0 309 },
Chris@0 310
Chris@0 311 stop: function() {
Chris@0 312 if (!this.timer) return;
Chris@0 313 clearInterval(this.timer);
Chris@0 314 this.timer = null;
Chris@0 315 },
Chris@0 316
Chris@0 317 onTimerEvent: function() {
Chris@0 318 if (!this.currentlyExecuting) {
Chris@0 319 try {
Chris@0 320 this.currentlyExecuting = true;
Chris@0 321 this.execute();
Chris@0 322 } finally {
Chris@0 323 this.currentlyExecuting = false;
Chris@0 324 }
Chris@0 325 }
Chris@0 326 }
Chris@0 327 });
Chris@0 328 Object.extend(String, {
Chris@0 329 interpret: function(value) {
Chris@0 330 return value == null ? '' : String(value);
Chris@0 331 },
Chris@0 332 specialChar: {
Chris@0 333 '\b': '\\b',
Chris@0 334 '\t': '\\t',
Chris@0 335 '\n': '\\n',
Chris@0 336 '\f': '\\f',
Chris@0 337 '\r': '\\r',
Chris@0 338 '\\': '\\\\'
Chris@0 339 }
Chris@0 340 });
Chris@0 341
Chris@0 342 Object.extend(String.prototype, {
Chris@0 343 gsub: function(pattern, replacement) {
Chris@0 344 var result = '', source = this, match;
Chris@0 345 replacement = arguments.callee.prepareReplacement(replacement);
Chris@0 346
Chris@0 347 while (source.length > 0) {
Chris@0 348 if (match = source.match(pattern)) {
Chris@0 349 result += source.slice(0, match.index);
Chris@0 350 result += String.interpret(replacement(match));
Chris@0 351 source = source.slice(match.index + match[0].length);
Chris@0 352 } else {
Chris@0 353 result += source, source = '';
Chris@0 354 }
Chris@0 355 }
Chris@0 356 return result;
Chris@0 357 },
Chris@0 358
Chris@0 359 sub: function(pattern, replacement, count) {
Chris@0 360 replacement = this.gsub.prepareReplacement(replacement);
Chris@0 361 count = Object.isUndefined(count) ? 1 : count;
Chris@0 362
Chris@0 363 return this.gsub(pattern, function(match) {
Chris@0 364 if (--count < 0) return match[0];
Chris@0 365 return replacement(match);
Chris@0 366 });
Chris@0 367 },
Chris@0 368
Chris@0 369 scan: function(pattern, iterator) {
Chris@0 370 this.gsub(pattern, iterator);
Chris@0 371 return String(this);
Chris@0 372 },
Chris@0 373
Chris@0 374 truncate: function(length, truncation) {
Chris@0 375 length = length || 30;
Chris@0 376 truncation = Object.isUndefined(truncation) ? '...' : truncation;
Chris@0 377 return this.length > length ?
Chris@0 378 this.slice(0, length - truncation.length) + truncation : String(this);
Chris@0 379 },
Chris@0 380
Chris@0 381 strip: function() {
Chris@0 382 return this.replace(/^\s+/, '').replace(/\s+$/, '');
Chris@0 383 },
Chris@0 384
Chris@0 385 stripTags: function() {
Chris@0 386 return this.replace(/<\/?[^>]+>/gi, '');
Chris@0 387 },
Chris@0 388
Chris@0 389 stripScripts: function() {
Chris@0 390 return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
Chris@0 391 },
Chris@0 392
Chris@0 393 extractScripts: function() {
Chris@0 394 var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
Chris@0 395 var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
Chris@0 396 return (this.match(matchAll) || []).map(function(scriptTag) {
Chris@0 397 return (scriptTag.match(matchOne) || ['', ''])[1];
Chris@0 398 });
Chris@0 399 },
Chris@0 400
Chris@0 401 evalScripts: function() {
Chris@0 402 return this.extractScripts().map(function(script) { return eval(script) });
Chris@0 403 },
Chris@0 404
Chris@0 405 escapeHTML: function() {
Chris@0 406 var self = arguments.callee;
Chris@0 407 self.text.data = this;
Chris@0 408 return self.div.innerHTML;
Chris@0 409 },
Chris@0 410
Chris@0 411 unescapeHTML: function() {
Chris@0 412 var div = new Element('div');
Chris@0 413 div.innerHTML = this.stripTags();
Chris@0 414 return div.childNodes[0] ? (div.childNodes.length > 1 ?
Chris@0 415 $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
Chris@0 416 div.childNodes[0].nodeValue) : '';
Chris@0 417 },
Chris@0 418
Chris@0 419 toQueryParams: function(separator) {
Chris@0 420 var match = this.strip().match(/([^?#]*)(#.*)?$/);
Chris@0 421 if (!match) return { };
Chris@0 422
Chris@0 423 return match[1].split(separator || '&').inject({ }, function(hash, pair) {
Chris@0 424 if ((pair = pair.split('='))[0]) {
Chris@0 425 var key = decodeURIComponent(pair.shift());
Chris@0 426 var value = pair.length > 1 ? pair.join('=') : pair[0];
Chris@0 427 if (value != undefined) value = decodeURIComponent(value);
Chris@0 428
Chris@0 429 if (key in hash) {
Chris@0 430 if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
Chris@0 431 hash[key].push(value);
Chris@0 432 }
Chris@0 433 else hash[key] = value;
Chris@0 434 }
Chris@0 435 return hash;
Chris@0 436 });
Chris@0 437 },
Chris@0 438
Chris@0 439 toArray: function() {
Chris@0 440 return this.split('');
Chris@0 441 },
Chris@0 442
Chris@0 443 succ: function() {
Chris@0 444 return this.slice(0, this.length - 1) +
Chris@0 445 String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
Chris@0 446 },
Chris@0 447
Chris@0 448 times: function(count) {
Chris@0 449 return count < 1 ? '' : new Array(count + 1).join(this);
Chris@0 450 },
Chris@0 451
Chris@0 452 camelize: function() {
Chris@0 453 var parts = this.split('-'), len = parts.length;
Chris@0 454 if (len == 1) return parts[0];
Chris@0 455
Chris@0 456 var camelized = this.charAt(0) == '-'
Chris@0 457 ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
Chris@0 458 : parts[0];
Chris@0 459
Chris@0 460 for (var i = 1; i < len; i++)
Chris@0 461 camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
Chris@0 462
Chris@0 463 return camelized;
Chris@0 464 },
Chris@0 465
Chris@0 466 capitalize: function() {
Chris@0 467 return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
Chris@0 468 },
Chris@0 469
Chris@0 470 underscore: function() {
Chris@0 471 return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
Chris@0 472 },
Chris@0 473
Chris@0 474 dasherize: function() {
Chris@0 475 return this.gsub(/_/,'-');
Chris@0 476 },
Chris@0 477
Chris@0 478 inspect: function(useDoubleQuotes) {
Chris@0 479 var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
Chris@0 480 var character = String.specialChar[match[0]];
Chris@0 481 return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
Chris@0 482 });
Chris@0 483 if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
Chris@0 484 return "'" + escapedString.replace(/'/g, '\\\'') + "'";
Chris@0 485 },
Chris@0 486
Chris@0 487 toJSON: function() {
Chris@0 488 return this.inspect(true);
Chris@0 489 },
Chris@0 490
Chris@0 491 unfilterJSON: function(filter) {
Chris@0 492 return this.sub(filter || Prototype.JSONFilter, '#{1}');
Chris@0 493 },
Chris@0 494
Chris@0 495 isJSON: function() {
Chris@0 496 var str = this;
Chris@0 497 if (str.blank()) return false;
Chris@0 498 str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
Chris@0 499 return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
Chris@0 500 },
Chris@0 501
Chris@0 502 evalJSON: function(sanitize) {
Chris@0 503 var json = this.unfilterJSON();
Chris@0 504 try {
Chris@0 505 if (!sanitize || json.isJSON()) return eval('(' + json + ')');
Chris@0 506 } catch (e) { }
Chris@0 507 throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
Chris@0 508 },
Chris@0 509
Chris@0 510 include: function(pattern) {
Chris@0 511 return this.indexOf(pattern) > -1;
Chris@0 512 },
Chris@0 513
Chris@0 514 startsWith: function(pattern) {
Chris@0 515 return this.indexOf(pattern) === 0;
Chris@0 516 },
Chris@0 517
Chris@0 518 endsWith: function(pattern) {
Chris@0 519 var d = this.length - pattern.length;
Chris@0 520 return d >= 0 && this.lastIndexOf(pattern) === d;
Chris@0 521 },
Chris@0 522
Chris@0 523 empty: function() {
Chris@0 524 return this == '';
Chris@0 525 },
Chris@0 526
Chris@0 527 blank: function() {
Chris@0 528 return /^\s*$/.test(this);
Chris@0 529 },
Chris@0 530
Chris@0 531 interpolate: function(object, pattern) {
Chris@0 532 return new Template(this, pattern).evaluate(object);
Chris@0 533 }
Chris@0 534 });
Chris@0 535
Chris@0 536 if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
Chris@0 537 escapeHTML: function() {
Chris@0 538 return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
Chris@0 539 },
Chris@0 540 unescapeHTML: function() {
Chris@0 541 return this.stripTags().replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
Chris@0 542 }
Chris@0 543 });
Chris@0 544
Chris@0 545 String.prototype.gsub.prepareReplacement = function(replacement) {
Chris@0 546 if (Object.isFunction(replacement)) return replacement;
Chris@0 547 var template = new Template(replacement);
Chris@0 548 return function(match) { return template.evaluate(match) };
Chris@0 549 };
Chris@0 550
Chris@0 551 String.prototype.parseQuery = String.prototype.toQueryParams;
Chris@0 552
Chris@0 553 Object.extend(String.prototype.escapeHTML, {
Chris@0 554 div: document.createElement('div'),
Chris@0 555 text: document.createTextNode('')
Chris@0 556 });
Chris@0 557
Chris@0 558 String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);
Chris@0 559
Chris@0 560 var Template = Class.create({
Chris@0 561 initialize: function(template, pattern) {
Chris@0 562 this.template = template.toString();
Chris@0 563 this.pattern = pattern || Template.Pattern;
Chris@0 564 },
Chris@0 565
Chris@0 566 evaluate: function(object) {
Chris@0 567 if (Object.isFunction(object.toTemplateReplacements))
Chris@0 568 object = object.toTemplateReplacements();
Chris@0 569
Chris@0 570 return this.template.gsub(this.pattern, function(match) {
Chris@0 571 if (object == null) return '';
Chris@0 572
Chris@0 573 var before = match[1] || '';
Chris@0 574 if (before == '\\') return match[2];
Chris@0 575
Chris@0 576 var ctx = object, expr = match[3];
Chris@0 577 var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
Chris@0 578 match = pattern.exec(expr);
Chris@0 579 if (match == null) return before;
Chris@0 580
Chris@0 581 while (match != null) {
Chris@0 582 var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
Chris@0 583 ctx = ctx[comp];
Chris@0 584 if (null == ctx || '' == match[3]) break;
Chris@0 585 expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
Chris@0 586 match = pattern.exec(expr);
Chris@0 587 }
Chris@0 588
Chris@0 589 return before + String.interpret(ctx);
Chris@0 590 });
Chris@0 591 }
Chris@0 592 });
Chris@0 593 Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
Chris@0 594
Chris@0 595 var $break = { };
Chris@0 596
Chris@0 597 var Enumerable = {
Chris@0 598 each: function(iterator, context) {
Chris@0 599 var index = 0;
Chris@0 600 try {
Chris@0 601 this._each(function(value) {
Chris@0 602 iterator.call(context, value, index++);
Chris@0 603 });
Chris@0 604 } catch (e) {
Chris@0 605 if (e != $break) throw e;
Chris@0 606 }
Chris@0 607 return this;
Chris@0 608 },
Chris@0 609
Chris@0 610 eachSlice: function(number, iterator, context) {
Chris@0 611 var index = -number, slices = [], array = this.toArray();
Chris@0 612 if (number < 1) return array;
Chris@0 613 while ((index += number) < array.length)
Chris@0 614 slices.push(array.slice(index, index+number));
Chris@0 615 return slices.collect(iterator, context);
Chris@0 616 },
Chris@0 617
Chris@0 618 all: function(iterator, context) {
Chris@0 619 iterator = iterator || Prototype.K;
Chris@0 620 var result = true;
Chris@0 621 this.each(function(value, index) {
Chris@0 622 result = result && !!iterator.call(context, value, index);
Chris@0 623 if (!result) throw $break;
Chris@0 624 });
Chris@0 625 return result;
Chris@0 626 },
Chris@0 627
Chris@0 628 any: function(iterator, context) {
Chris@0 629 iterator = iterator || Prototype.K;
Chris@0 630 var result = false;
Chris@0 631 this.each(function(value, index) {
Chris@0 632 if (result = !!iterator.call(context, value, index))
Chris@0 633 throw $break;
Chris@0 634 });
Chris@0 635 return result;
Chris@0 636 },
Chris@0 637
Chris@0 638 collect: function(iterator, context) {
Chris@0 639 iterator = iterator || Prototype.K;
Chris@0 640 var results = [];
Chris@0 641 this.each(function(value, index) {
Chris@0 642 results.push(iterator.call(context, value, index));
Chris@0 643 });
Chris@0 644 return results;
Chris@0 645 },
Chris@0 646
Chris@0 647 detect: function(iterator, context) {
Chris@0 648 var result;
Chris@0 649 this.each(function(value, index) {
Chris@0 650 if (iterator.call(context, value, index)) {
Chris@0 651 result = value;
Chris@0 652 throw $break;
Chris@0 653 }
Chris@0 654 });
Chris@0 655 return result;
Chris@0 656 },
Chris@0 657
Chris@0 658 findAll: function(iterator, context) {
Chris@0 659 var results = [];
Chris@0 660 this.each(function(value, index) {
Chris@0 661 if (iterator.call(context, value, index))
Chris@0 662 results.push(value);
Chris@0 663 });
Chris@0 664 return results;
Chris@0 665 },
Chris@0 666
Chris@0 667 grep: function(filter, iterator, context) {
Chris@0 668 iterator = iterator || Prototype.K;
Chris@0 669 var results = [];
Chris@0 670
Chris@0 671 if (Object.isString(filter))
Chris@0 672 filter = new RegExp(filter);
Chris@0 673
Chris@0 674 this.each(function(value, index) {
Chris@0 675 if (filter.match(value))
Chris@0 676 results.push(iterator.call(context, value, index));
Chris@0 677 });
Chris@0 678 return results;
Chris@0 679 },
Chris@0 680
Chris@0 681 include: function(object) {
Chris@0 682 if (Object.isFunction(this.indexOf))
Chris@0 683 if (this.indexOf(object) != -1) return true;
Chris@0 684
Chris@0 685 var found = false;
Chris@0 686 this.each(function(value) {
Chris@0 687 if (value == object) {
Chris@0 688 found = true;
Chris@0 689 throw $break;
Chris@0 690 }
Chris@0 691 });
Chris@0 692 return found;
Chris@0 693 },
Chris@0 694
Chris@0 695 inGroupsOf: function(number, fillWith) {
Chris@0 696 fillWith = Object.isUndefined(fillWith) ? null : fillWith;
Chris@0 697 return this.eachSlice(number, function(slice) {
Chris@0 698 while(slice.length < number) slice.push(fillWith);
Chris@0 699 return slice;
Chris@0 700 });
Chris@0 701 },
Chris@0 702
Chris@0 703 inject: function(memo, iterator, context) {
Chris@0 704 this.each(function(value, index) {
Chris@0 705 memo = iterator.call(context, memo, value, index);
Chris@0 706 });
Chris@0 707 return memo;
Chris@0 708 },
Chris@0 709
Chris@0 710 invoke: function(method) {
Chris@0 711 var args = $A(arguments).slice(1);
Chris@0 712 return this.map(function(value) {
Chris@0 713 return value[method].apply(value, args);
Chris@0 714 });
Chris@0 715 },
Chris@0 716
Chris@0 717 max: function(iterator, context) {
Chris@0 718 iterator = iterator || Prototype.K;
Chris@0 719 var result;
Chris@0 720 this.each(function(value, index) {
Chris@0 721 value = iterator.call(context, value, index);
Chris@0 722 if (result == null || value >= result)
Chris@0 723 result = value;
Chris@0 724 });
Chris@0 725 return result;
Chris@0 726 },
Chris@0 727
Chris@0 728 min: function(iterator, context) {
Chris@0 729 iterator = iterator || Prototype.K;
Chris@0 730 var result;
Chris@0 731 this.each(function(value, index) {
Chris@0 732 value = iterator.call(context, value, index);
Chris@0 733 if (result == null || value < result)
Chris@0 734 result = value;
Chris@0 735 });
Chris@0 736 return result;
Chris@0 737 },
Chris@0 738
Chris@0 739 partition: function(iterator, context) {
Chris@0 740 iterator = iterator || Prototype.K;
Chris@0 741 var trues = [], falses = [];
Chris@0 742 this.each(function(value, index) {
Chris@0 743 (iterator.call(context, value, index) ?
Chris@0 744 trues : falses).push(value);
Chris@0 745 });
Chris@0 746 return [trues, falses];
Chris@0 747 },
Chris@0 748
Chris@0 749 pluck: function(property) {
Chris@0 750 var results = [];
Chris@0 751 this.each(function(value) {
Chris@0 752 results.push(value[property]);
Chris@0 753 });
Chris@0 754 return results;
Chris@0 755 },
Chris@0 756
Chris@0 757 reject: function(iterator, context) {
Chris@0 758 var results = [];
Chris@0 759 this.each(function(value, index) {
Chris@0 760 if (!iterator.call(context, value, index))
Chris@0 761 results.push(value);
Chris@0 762 });
Chris@0 763 return results;
Chris@0 764 },
Chris@0 765
Chris@0 766 sortBy: function(iterator, context) {
Chris@0 767 return this.map(function(value, index) {
Chris@0 768 return {
Chris@0 769 value: value,
Chris@0 770 criteria: iterator.call(context, value, index)
Chris@0 771 };
Chris@0 772 }).sort(function(left, right) {
Chris@0 773 var a = left.criteria, b = right.criteria;
Chris@0 774 return a < b ? -1 : a > b ? 1 : 0;
Chris@0 775 }).pluck('value');
Chris@0 776 },
Chris@0 777
Chris@0 778 toArray: function() {
Chris@0 779 return this.map();
Chris@0 780 },
Chris@0 781
Chris@0 782 zip: function() {
Chris@0 783 var iterator = Prototype.K, args = $A(arguments);
Chris@0 784 if (Object.isFunction(args.last()))
Chris@0 785 iterator = args.pop();
Chris@0 786
Chris@0 787 var collections = [this].concat(args).map($A);
Chris@0 788 return this.map(function(value, index) {
Chris@0 789 return iterator(collections.pluck(index));
Chris@0 790 });
Chris@0 791 },
Chris@0 792
Chris@0 793 size: function() {
Chris@0 794 return this.toArray().length;
Chris@0 795 },
Chris@0 796
Chris@0 797 inspect: function() {
Chris@0 798 return '#<Enumerable:' + this.toArray().inspect() + '>';
Chris@0 799 }
Chris@0 800 };
Chris@0 801
Chris@0 802 Object.extend(Enumerable, {
Chris@0 803 map: Enumerable.collect,
Chris@0 804 find: Enumerable.detect,
Chris@0 805 select: Enumerable.findAll,
Chris@0 806 filter: Enumerable.findAll,
Chris@0 807 member: Enumerable.include,
Chris@0 808 entries: Enumerable.toArray,
Chris@0 809 every: Enumerable.all,
Chris@0 810 some: Enumerable.any
Chris@0 811 });
Chris@0 812 function $A(iterable) {
Chris@0 813 if (!iterable) return [];
Chris@0 814 if (iterable.toArray) return iterable.toArray();
Chris@0 815 var length = iterable.length || 0, results = new Array(length);
Chris@0 816 while (length--) results[length] = iterable[length];
Chris@0 817 return results;
Chris@0 818 }
Chris@0 819
Chris@0 820 if (Prototype.Browser.WebKit) {
Chris@0 821 $A = function(iterable) {
Chris@0 822 if (!iterable) return [];
Chris@0 823 // In Safari, only use the `toArray` method if it's not a NodeList.
Chris@0 824 // A NodeList is a function, has an function `item` property, and a numeric
Chris@0 825 // `length` property. Adapted from Google Doctype.
Chris@0 826 if (!(typeof iterable === 'function' && typeof iterable.length ===
Chris@0 827 'number' && typeof iterable.item === 'function') && iterable.toArray)
Chris@0 828 return iterable.toArray();
Chris@0 829 var length = iterable.length || 0, results = new Array(length);
Chris@0 830 while (length--) results[length] = iterable[length];
Chris@0 831 return results;
Chris@0 832 };
Chris@0 833 }
Chris@0 834
Chris@0 835 Array.from = $A;
Chris@0 836
Chris@0 837 Object.extend(Array.prototype, Enumerable);
Chris@0 838
Chris@0 839 if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
Chris@0 840
Chris@0 841 Object.extend(Array.prototype, {
Chris@0 842 _each: function(iterator) {
Chris@0 843 for (var i = 0, length = this.length; i < length; i++)
Chris@0 844 iterator(this[i]);
Chris@0 845 },
Chris@0 846
Chris@0 847 clear: function() {
Chris@0 848 this.length = 0;
Chris@0 849 return this;
Chris@0 850 },
Chris@0 851
Chris@0 852 first: function() {
Chris@0 853 return this[0];
Chris@0 854 },
Chris@0 855
Chris@0 856 last: function() {
Chris@0 857 return this[this.length - 1];
Chris@0 858 },
Chris@0 859
Chris@0 860 compact: function() {
Chris@0 861 return this.select(function(value) {
Chris@0 862 return value != null;
Chris@0 863 });
Chris@0 864 },
Chris@0 865
Chris@0 866 flatten: function() {
Chris@0 867 return this.inject([], function(array, value) {
Chris@0 868 return array.concat(Object.isArray(value) ?
Chris@0 869 value.flatten() : [value]);
Chris@0 870 });
Chris@0 871 },
Chris@0 872
Chris@0 873 without: function() {
Chris@0 874 var values = $A(arguments);
Chris@0 875 return this.select(function(value) {
Chris@0 876 return !values.include(value);
Chris@0 877 });
Chris@0 878 },
Chris@0 879
Chris@0 880 reverse: function(inline) {
Chris@0 881 return (inline !== false ? this : this.toArray())._reverse();
Chris@0 882 },
Chris@0 883
Chris@0 884 reduce: function() {
Chris@0 885 return this.length > 1 ? this : this[0];
Chris@0 886 },
Chris@0 887
Chris@0 888 uniq: function(sorted) {
Chris@0 889 return this.inject([], function(array, value, index) {
Chris@0 890 if (0 == index || (sorted ? array.last() != value : !array.include(value)))
Chris@0 891 array.push(value);
Chris@0 892 return array;
Chris@0 893 });
Chris@0 894 },
Chris@0 895
Chris@0 896 intersect: function(array) {
Chris@0 897 return this.uniq().findAll(function(item) {
Chris@0 898 return array.detect(function(value) { return item === value });
Chris@0 899 });
Chris@0 900 },
Chris@0 901
Chris@0 902 clone: function() {
Chris@0 903 return [].concat(this);
Chris@0 904 },
Chris@0 905
Chris@0 906 size: function() {
Chris@0 907 return this.length;
Chris@0 908 },
Chris@0 909
Chris@0 910 inspect: function() {
Chris@0 911 return '[' + this.map(Object.inspect).join(', ') + ']';
Chris@0 912 },
Chris@0 913
Chris@0 914 toJSON: function() {
Chris@0 915 var results = [];
Chris@0 916 this.each(function(object) {
Chris@0 917 var value = Object.toJSON(object);
Chris@0 918 if (!Object.isUndefined(value)) results.push(value);
Chris@0 919 });
Chris@0 920 return '[' + results.join(', ') + ']';
Chris@0 921 }
Chris@0 922 });
Chris@0 923
Chris@0 924 // use native browser JS 1.6 implementation if available
Chris@0 925 if (Object.isFunction(Array.prototype.forEach))
Chris@0 926 Array.prototype._each = Array.prototype.forEach;
Chris@0 927
Chris@0 928 if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
Chris@0 929 i || (i = 0);
Chris@0 930 var length = this.length;
Chris@0 931 if (i < 0) i = length + i;
Chris@0 932 for (; i < length; i++)
Chris@0 933 if (this[i] === item) return i;
Chris@0 934 return -1;
Chris@0 935 };
Chris@0 936
Chris@0 937 if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
Chris@0 938 i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
Chris@0 939 var n = this.slice(0, i).reverse().indexOf(item);
Chris@0 940 return (n < 0) ? n : i - n - 1;
Chris@0 941 };
Chris@0 942
Chris@0 943 Array.prototype.toArray = Array.prototype.clone;
Chris@0 944
Chris@0 945 function $w(string) {
Chris@0 946 if (!Object.isString(string)) return [];
Chris@0 947 string = string.strip();
Chris@0 948 return string ? string.split(/\s+/) : [];
Chris@0 949 }
Chris@0 950
Chris@0 951 if (Prototype.Browser.Opera){
Chris@0 952 Array.prototype.concat = function() {
Chris@0 953 var array = [];
Chris@0 954 for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
Chris@0 955 for (var i = 0, length = arguments.length; i < length; i++) {
Chris@0 956 if (Object.isArray(arguments[i])) {
Chris@0 957 for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
Chris@0 958 array.push(arguments[i][j]);
Chris@0 959 } else {
Chris@0 960 array.push(arguments[i]);
Chris@0 961 }
Chris@0 962 }
Chris@0 963 return array;
Chris@0 964 };
Chris@0 965 }
Chris@0 966 Object.extend(Number.prototype, {
Chris@0 967 toColorPart: function() {
Chris@0 968 return this.toPaddedString(2, 16);
Chris@0 969 },
Chris@0 970
Chris@0 971 succ: function() {
Chris@0 972 return this + 1;
Chris@0 973 },
Chris@0 974
Chris@0 975 times: function(iterator, context) {
Chris@0 976 $R(0, this, true).each(iterator, context);
Chris@0 977 return this;
Chris@0 978 },
Chris@0 979
Chris@0 980 toPaddedString: function(length, radix) {
Chris@0 981 var string = this.toString(radix || 10);
Chris@0 982 return '0'.times(length - string.length) + string;
Chris@0 983 },
Chris@0 984
Chris@0 985 toJSON: function() {
Chris@0 986 return isFinite(this) ? this.toString() : 'null';
Chris@0 987 }
Chris@0 988 });
Chris@0 989
Chris@0 990 $w('abs round ceil floor').each(function(method){
Chris@0 991 Number.prototype[method] = Math[method].methodize();
Chris@0 992 });
Chris@0 993 function $H(object) {
Chris@0 994 return new Hash(object);
Chris@0 995 };
Chris@0 996
Chris@0 997 var Hash = Class.create(Enumerable, (function() {
Chris@0 998
Chris@0 999 function toQueryPair(key, value) {
Chris@0 1000 if (Object.isUndefined(value)) return key;
Chris@0 1001 return key + '=' + encodeURIComponent(String.interpret(value));
Chris@0 1002 }
Chris@0 1003
Chris@0 1004 return {
Chris@0 1005 initialize: function(object) {
Chris@0 1006 this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
Chris@0 1007 },
Chris@0 1008
Chris@0 1009 _each: function(iterator) {
Chris@0 1010 for (var key in this._object) {
Chris@0 1011 var value = this._object[key], pair = [key, value];
Chris@0 1012 pair.key = key;
Chris@0 1013 pair.value = value;
Chris@0 1014 iterator(pair);
Chris@0 1015 }
Chris@0 1016 },
Chris@0 1017
Chris@0 1018 set: function(key, value) {
Chris@0 1019 return this._object[key] = value;
Chris@0 1020 },
Chris@0 1021
Chris@0 1022 get: function(key) {
Chris@0 1023 // simulating poorly supported hasOwnProperty
Chris@0 1024 if (this._object[key] !== Object.prototype[key])
Chris@0 1025 return this._object[key];
Chris@0 1026 },
Chris@0 1027
Chris@0 1028 unset: function(key) {
Chris@0 1029 var value = this._object[key];
Chris@0 1030 delete this._object[key];
Chris@0 1031 return value;
Chris@0 1032 },
Chris@0 1033
Chris@0 1034 toObject: function() {
Chris@0 1035 return Object.clone(this._object);
Chris@0 1036 },
Chris@0 1037
Chris@0 1038 keys: function() {
Chris@0 1039 return this.pluck('key');
Chris@0 1040 },
Chris@0 1041
Chris@0 1042 values: function() {
Chris@0 1043 return this.pluck('value');
Chris@0 1044 },
Chris@0 1045
Chris@0 1046 index: function(value) {
Chris@0 1047 var match = this.detect(function(pair) {
Chris@0 1048 return pair.value === value;
Chris@0 1049 });
Chris@0 1050 return match && match.key;
Chris@0 1051 },
Chris@0 1052
Chris@0 1053 merge: function(object) {
Chris@0 1054 return this.clone().update(object);
Chris@0 1055 },
Chris@0 1056
Chris@0 1057 update: function(object) {
Chris@0 1058 return new Hash(object).inject(this, function(result, pair) {
Chris@0 1059 result.set(pair.key, pair.value);
Chris@0 1060 return result;
Chris@0 1061 });
Chris@0 1062 },
Chris@0 1063
Chris@0 1064 toQueryString: function() {
Chris@0 1065 return this.inject([], function(results, pair) {
Chris@0 1066 var key = encodeURIComponent(pair.key), values = pair.value;
Chris@0 1067
Chris@0 1068 if (values && typeof values == 'object') {
Chris@0 1069 if (Object.isArray(values))
Chris@0 1070 return results.concat(values.map(toQueryPair.curry(key)));
Chris@0 1071 } else results.push(toQueryPair(key, values));
Chris@0 1072 return results;
Chris@0 1073 }).join('&');
Chris@0 1074 },
Chris@0 1075
Chris@0 1076 inspect: function() {
Chris@0 1077 return '#<Hash:{' + this.map(function(pair) {
Chris@0 1078 return pair.map(Object.inspect).join(': ');
Chris@0 1079 }).join(', ') + '}>';
Chris@0 1080 },
Chris@0 1081
Chris@0 1082 toJSON: function() {
Chris@0 1083 return Object.toJSON(this.toObject());
Chris@0 1084 },
Chris@0 1085
Chris@0 1086 clone: function() {
Chris@0 1087 return new Hash(this);
Chris@0 1088 }
Chris@0 1089 }
Chris@0 1090 })());
Chris@0 1091
Chris@0 1092 Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
Chris@0 1093 Hash.from = $H;
Chris@0 1094 var ObjectRange = Class.create(Enumerable, {
Chris@0 1095 initialize: function(start, end, exclusive) {
Chris@0 1096 this.start = start;
Chris@0 1097 this.end = end;
Chris@0 1098 this.exclusive = exclusive;
Chris@0 1099 },
Chris@0 1100
Chris@0 1101 _each: function(iterator) {
Chris@0 1102 var value = this.start;
Chris@0 1103 while (this.include(value)) {
Chris@0 1104 iterator(value);
Chris@0 1105 value = value.succ();
Chris@0 1106 }
Chris@0 1107 },
Chris@0 1108
Chris@0 1109 include: function(value) {
Chris@0 1110 if (value < this.start)
Chris@0 1111 return false;
Chris@0 1112 if (this.exclusive)
Chris@0 1113 return value < this.end;
Chris@0 1114 return value <= this.end;
Chris@0 1115 }
Chris@0 1116 });
Chris@0 1117
Chris@0 1118 var $R = function(start, end, exclusive) {
Chris@0 1119 return new ObjectRange(start, end, exclusive);
Chris@0 1120 };
Chris@0 1121
Chris@0 1122 var Ajax = {
Chris@0 1123 getTransport: function() {
Chris@0 1124 return Try.these(
Chris@0 1125 function() {return new XMLHttpRequest()},
Chris@0 1126 function() {return new ActiveXObject('Msxml2.XMLHTTP')},
Chris@0 1127 function() {return new ActiveXObject('Microsoft.XMLHTTP')}
Chris@0 1128 ) || false;
Chris@0 1129 },
Chris@0 1130
Chris@0 1131 activeRequestCount: 0
Chris@0 1132 };
Chris@0 1133
Chris@0 1134 Ajax.Responders = {
Chris@0 1135 responders: [],
Chris@0 1136
Chris@0 1137 _each: function(iterator) {
Chris@0 1138 this.responders._each(iterator);
Chris@0 1139 },
Chris@0 1140
Chris@0 1141 register: function(responder) {
Chris@0 1142 if (!this.include(responder))
Chris@0 1143 this.responders.push(responder);
Chris@0 1144 },
Chris@0 1145
Chris@0 1146 unregister: function(responder) {
Chris@0 1147 this.responders = this.responders.without(responder);
Chris@0 1148 },
Chris@0 1149
Chris@0 1150 dispatch: function(callback, request, transport, json) {
Chris@0 1151 this.each(function(responder) {
Chris@0 1152 if (Object.isFunction(responder[callback])) {
Chris@0 1153 try {
Chris@0 1154 responder[callback].apply(responder, [request, transport, json]);
Chris@0 1155 } catch (e) { }
Chris@0 1156 }
Chris@0 1157 });
Chris@0 1158 }
Chris@0 1159 };
Chris@0 1160
Chris@0 1161 Object.extend(Ajax.Responders, Enumerable);
Chris@0 1162
Chris@0 1163 Ajax.Responders.register({
Chris@0 1164 onCreate: function() { Ajax.activeRequestCount++ },
Chris@0 1165 onComplete: function() { Ajax.activeRequestCount-- }
Chris@0 1166 });
Chris@0 1167
Chris@0 1168 Ajax.Base = Class.create({
Chris@0 1169 initialize: function(options) {
Chris@0 1170 this.options = {
Chris@0 1171 method: 'post',
Chris@0 1172 asynchronous: true,
Chris@0 1173 contentType: 'application/x-www-form-urlencoded',
Chris@0 1174 encoding: 'UTF-8',
Chris@0 1175 parameters: '',
Chris@0 1176 evalJSON: true,
Chris@0 1177 evalJS: true
Chris@0 1178 };
Chris@0 1179 Object.extend(this.options, options || { });
Chris@0 1180
Chris@0 1181 this.options.method = this.options.method.toLowerCase();
Chris@0 1182
Chris@0 1183 if (Object.isString(this.options.parameters))
Chris@0 1184 this.options.parameters = this.options.parameters.toQueryParams();
Chris@0 1185 else if (Object.isHash(this.options.parameters))
Chris@0 1186 this.options.parameters = this.options.parameters.toObject();
Chris@0 1187 }
Chris@0 1188 });
Chris@0 1189
Chris@0 1190 Ajax.Request = Class.create(Ajax.Base, {
Chris@0 1191 _complete: false,
Chris@0 1192
Chris@0 1193 initialize: function($super, url, options) {
Chris@0 1194 $super(options);
Chris@0 1195 this.transport = Ajax.getTransport();
Chris@0 1196 this.request(url);
Chris@0 1197 },
Chris@0 1198
Chris@0 1199 request: function(url) {
Chris@0 1200 this.url = url;
Chris@0 1201 this.method = this.options.method;
Chris@0 1202 var params = Object.clone(this.options.parameters);
Chris@0 1203
Chris@0 1204 if (!['get', 'post'].include(this.method)) {
Chris@0 1205 // simulate other verbs over post
Chris@0 1206 params['_method'] = this.method;
Chris@0 1207 this.method = 'post';
Chris@0 1208 }
Chris@0 1209
Chris@0 1210 this.parameters = params;
Chris@0 1211
Chris@0 1212 if (params = Object.toQueryString(params)) {
Chris@0 1213 // when GET, append parameters to URL
Chris@0 1214 if (this.method == 'get')
Chris@0 1215 this.url += (this.url.include('?') ? '&' : '?') + params;
Chris@0 1216 else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
Chris@0 1217 params += '&_=';
Chris@0 1218 }
Chris@0 1219
Chris@0 1220 try {
Chris@0 1221 var response = new Ajax.Response(this);
Chris@0 1222 if (this.options.onCreate) this.options.onCreate(response);
Chris@0 1223 Ajax.Responders.dispatch('onCreate', this, response);
Chris@0 1224
Chris@0 1225 this.transport.open(this.method.toUpperCase(), this.url,
Chris@0 1226 this.options.asynchronous);
Chris@0 1227
Chris@0 1228 if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
Chris@0 1229
Chris@0 1230 this.transport.onreadystatechange = this.onStateChange.bind(this);
Chris@0 1231 this.setRequestHeaders();
Chris@0 1232
Chris@0 1233 this.body = this.method == 'post' ? (this.options.postBody || params) : null;
Chris@0 1234 this.transport.send(this.body);
Chris@0 1235
Chris@0 1236 /* Force Firefox to handle ready state 4 for synchronous requests */
Chris@0 1237 if (!this.options.asynchronous && this.transport.overrideMimeType)
Chris@0 1238 this.onStateChange();
Chris@0 1239
Chris@0 1240 }
Chris@0 1241 catch (e) {
Chris@0 1242 this.dispatchException(e);
Chris@0 1243 }
Chris@0 1244 },
Chris@0 1245
Chris@0 1246 onStateChange: function() {
Chris@0 1247 var readyState = this.transport.readyState;
Chris@0 1248 if (readyState > 1 && !((readyState == 4) && this._complete))
Chris@0 1249 this.respondToReadyState(this.transport.readyState);
Chris@0 1250 },
Chris@0 1251
Chris@0 1252 setRequestHeaders: function() {
Chris@0 1253 var headers = {
Chris@0 1254 'X-Requested-With': 'XMLHttpRequest',
Chris@0 1255 'X-Prototype-Version': Prototype.Version,
Chris@0 1256 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
Chris@0 1257 };
Chris@0 1258
Chris@0 1259 if (this.method == 'post') {
Chris@0 1260 headers['Content-type'] = this.options.contentType +
Chris@0 1261 (this.options.encoding ? '; charset=' + this.options.encoding : '');
Chris@0 1262
Chris@0 1263 /* Force "Connection: close" for older Mozilla browsers to work
Chris@0 1264 * around a bug where XMLHttpRequest sends an incorrect
Chris@0 1265 * Content-length header. See Mozilla Bugzilla #246651.
Chris@0 1266 */
Chris@0 1267 if (this.transport.overrideMimeType &&
Chris@0 1268 (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
Chris@0 1269 headers['Connection'] = 'close';
Chris@0 1270 }
Chris@0 1271
Chris@0 1272 // user-defined headers
Chris@0 1273 if (typeof this.options.requestHeaders == 'object') {
Chris@0 1274 var extras = this.options.requestHeaders;
Chris@0 1275
Chris@0 1276 if (Object.isFunction(extras.push))
Chris@0 1277 for (var i = 0, length = extras.length; i < length; i += 2)
Chris@0 1278 headers[extras[i]] = extras[i+1];
Chris@0 1279 else
Chris@0 1280 $H(extras).each(function(pair) { headers[pair.key] = pair.value });
Chris@0 1281 }
Chris@0 1282
Chris@0 1283 for (var name in headers)
Chris@0 1284 this.transport.setRequestHeader(name, headers[name]);
Chris@0 1285 },
Chris@0 1286
Chris@0 1287 success: function() {
Chris@0 1288 var status = this.getStatus();
Chris@0 1289 return !status || (status >= 200 && status < 300);
Chris@0 1290 },
Chris@0 1291
Chris@0 1292 getStatus: function() {
Chris@0 1293 try {
Chris@0 1294 return this.transport.status || 0;
Chris@0 1295 } catch (e) { return 0 }
Chris@0 1296 },
Chris@0 1297
Chris@0 1298 respondToReadyState: function(readyState) {
Chris@0 1299 var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
Chris@0 1300
Chris@0 1301 if (state == 'Complete') {
Chris@0 1302 try {
Chris@0 1303 this._complete = true;
Chris@0 1304 (this.options['on' + response.status]
Chris@0 1305 || this.options['on' + (this.success() ? 'Success' : 'Failure')]
Chris@0 1306 || Prototype.emptyFunction)(response, response.headerJSON);
Chris@0 1307 } catch (e) {
Chris@0 1308 this.dispatchException(e);
Chris@0 1309 }
Chris@0 1310
Chris@0 1311 var contentType = response.getHeader('Content-type');
Chris@0 1312 if (this.options.evalJS == 'force'
Chris@0 1313 || (this.options.evalJS && this.isSameOrigin() && contentType
Chris@0 1314 && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
Chris@0 1315 this.evalResponse();
Chris@0 1316 }
Chris@0 1317
Chris@0 1318 try {
Chris@0 1319 (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
Chris@0 1320 Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
Chris@0 1321 } catch (e) {
Chris@0 1322 this.dispatchException(e);
Chris@0 1323 }
Chris@0 1324
Chris@0 1325 if (state == 'Complete') {
Chris@0 1326 // avoid memory leak in MSIE: clean up
Chris@0 1327 this.transport.onreadystatechange = Prototype.emptyFunction;
Chris@0 1328 }
Chris@0 1329 },
Chris@0 1330
Chris@0 1331 isSameOrigin: function() {
Chris@0 1332 var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
Chris@0 1333 return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
Chris@0 1334 protocol: location.protocol,
Chris@0 1335 domain: document.domain,
Chris@0 1336 port: location.port ? ':' + location.port : ''
Chris@0 1337 }));
Chris@0 1338 },
Chris@0 1339
Chris@0 1340 getHeader: function(name) {
Chris@0 1341 try {
Chris@0 1342 return this.transport.getResponseHeader(name) || null;
Chris@0 1343 } catch (e) { return null }
Chris@0 1344 },
Chris@0 1345
Chris@0 1346 evalResponse: function() {
Chris@0 1347 try {
Chris@0 1348 return eval((this.transport.responseText || '').unfilterJSON());
Chris@0 1349 } catch (e) {
Chris@0 1350 this.dispatchException(e);
Chris@0 1351 }
Chris@0 1352 },
Chris@0 1353
Chris@0 1354 dispatchException: function(exception) {
Chris@0 1355 (this.options.onException || Prototype.emptyFunction)(this, exception);
Chris@0 1356 Ajax.Responders.dispatch('onException', this, exception);
Chris@0 1357 }
Chris@0 1358 });
Chris@0 1359
Chris@0 1360 Ajax.Request.Events =
Chris@0 1361 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
Chris@0 1362
Chris@0 1363 Ajax.Response = Class.create({
Chris@0 1364 initialize: function(request){
Chris@0 1365 this.request = request;
Chris@0 1366 var transport = this.transport = request.transport,
Chris@0 1367 readyState = this.readyState = transport.readyState;
Chris@0 1368
Chris@0 1369 if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
Chris@0 1370 this.status = this.getStatus();
Chris@0 1371 this.statusText = this.getStatusText();
Chris@0 1372 this.responseText = String.interpret(transport.responseText);
Chris@0 1373 this.headerJSON = this._getHeaderJSON();
Chris@0 1374 }
Chris@0 1375
Chris@0 1376 if(readyState == 4) {
Chris@0 1377 var xml = transport.responseXML;
Chris@0 1378 this.responseXML = Object.isUndefined(xml) ? null : xml;
Chris@0 1379 this.responseJSON = this._getResponseJSON();
Chris@0 1380 }
Chris@0 1381 },
Chris@0 1382
Chris@0 1383 status: 0,
Chris@0 1384 statusText: '',
Chris@0 1385
Chris@0 1386 getStatus: Ajax.Request.prototype.getStatus,
Chris@0 1387
Chris@0 1388 getStatusText: function() {
Chris@0 1389 try {
Chris@0 1390 return this.transport.statusText || '';
Chris@0 1391 } catch (e) { return '' }
Chris@0 1392 },
Chris@0 1393
Chris@0 1394 getHeader: Ajax.Request.prototype.getHeader,
Chris@0 1395
Chris@0 1396 getAllHeaders: function() {
Chris@0 1397 try {
Chris@0 1398 return this.getAllResponseHeaders();
Chris@0 1399 } catch (e) { return null }
Chris@0 1400 },
Chris@0 1401
Chris@0 1402 getResponseHeader: function(name) {
Chris@0 1403 return this.transport.getResponseHeader(name);
Chris@0 1404 },
Chris@0 1405
Chris@0 1406 getAllResponseHeaders: function() {
Chris@0 1407 return this.transport.getAllResponseHeaders();
Chris@0 1408 },
Chris@0 1409
Chris@0 1410 _getHeaderJSON: function() {
Chris@0 1411 var json = this.getHeader('X-JSON');
Chris@0 1412 if (!json) return null;
Chris@0 1413 json = decodeURIComponent(escape(json));
Chris@0 1414 try {
Chris@0 1415 return json.evalJSON(this.request.options.sanitizeJSON ||
Chris@0 1416 !this.request.isSameOrigin());
Chris@0 1417 } catch (e) {
Chris@0 1418 this.request.dispatchException(e);
Chris@0 1419 }
Chris@0 1420 },
Chris@0 1421
Chris@0 1422 _getResponseJSON: function() {
Chris@0 1423 var options = this.request.options;
Chris@0 1424 if (!options.evalJSON || (options.evalJSON != 'force' &&
Chris@0 1425 !(this.getHeader('Content-type') || '').include('application/json')) ||
Chris@0 1426 this.responseText.blank())
Chris@0 1427 return null;
Chris@0 1428 try {
Chris@0 1429 return this.responseText.evalJSON(options.sanitizeJSON ||
Chris@0 1430 !this.request.isSameOrigin());
Chris@0 1431 } catch (e) {
Chris@0 1432 this.request.dispatchException(e);
Chris@0 1433 }
Chris@0 1434 }
Chris@0 1435 });
Chris@0 1436
Chris@0 1437 Ajax.Updater = Class.create(Ajax.Request, {
Chris@0 1438 initialize: function($super, container, url, options) {
Chris@0 1439 this.container = {
Chris@0 1440 success: (container.success || container),
Chris@0 1441 failure: (container.failure || (container.success ? null : container))
Chris@0 1442 };
Chris@0 1443
Chris@0 1444 options = Object.clone(options);
Chris@0 1445 var onComplete = options.onComplete;
Chris@0 1446 options.onComplete = (function(response, json) {
Chris@0 1447 this.updateContent(response.responseText);
Chris@0 1448 if (Object.isFunction(onComplete)) onComplete(response, json);
Chris@0 1449 }).bind(this);
Chris@0 1450
Chris@0 1451 $super(url, options);
Chris@0 1452 },
Chris@0 1453
Chris@0 1454 updateContent: function(responseText) {
Chris@0 1455 var receiver = this.container[this.success() ? 'success' : 'failure'],
Chris@0 1456 options = this.options;
Chris@0 1457
Chris@0 1458 if (!options.evalScripts) responseText = responseText.stripScripts();
Chris@0 1459
Chris@0 1460 if (receiver = $(receiver)) {
Chris@0 1461 if (options.insertion) {
Chris@0 1462 if (Object.isString(options.insertion)) {
Chris@0 1463 var insertion = { }; insertion[options.insertion] = responseText;
Chris@0 1464 receiver.insert(insertion);
Chris@0 1465 }
Chris@0 1466 else options.insertion(receiver, responseText);
Chris@0 1467 }
Chris@0 1468 else receiver.update(responseText);
Chris@0 1469 }
Chris@0 1470 }
Chris@0 1471 });
Chris@0 1472
Chris@0 1473 Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
Chris@0 1474 initialize: function($super, container, url, options) {
Chris@0 1475 $super(options);
Chris@0 1476 this.onComplete = this.options.onComplete;
Chris@0 1477
Chris@0 1478 this.frequency = (this.options.frequency || 2);
Chris@0 1479 this.decay = (this.options.decay || 1);
Chris@0 1480
Chris@0 1481 this.updater = { };
Chris@0 1482 this.container = container;
Chris@0 1483 this.url = url;
Chris@0 1484
Chris@0 1485 this.start();
Chris@0 1486 },
Chris@0 1487
Chris@0 1488 start: function() {
Chris@0 1489 this.options.onComplete = this.updateComplete.bind(this);
Chris@0 1490 this.onTimerEvent();
Chris@0 1491 },
Chris@0 1492
Chris@0 1493 stop: function() {
Chris@0 1494 this.updater.options.onComplete = undefined;
Chris@0 1495 clearTimeout(this.timer);
Chris@0 1496 (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
Chris@0 1497 },
Chris@0 1498
Chris@0 1499 updateComplete: function(response) {
Chris@0 1500 if (this.options.decay) {
Chris@0 1501 this.decay = (response.responseText == this.lastText ?
Chris@0 1502 this.decay * this.options.decay : 1);
Chris@0 1503
Chris@0 1504 this.lastText = response.responseText;
Chris@0 1505 }
Chris@0 1506 this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
Chris@0 1507 },
Chris@0 1508
Chris@0 1509 onTimerEvent: function() {
Chris@0 1510 this.updater = new Ajax.Updater(this.container, this.url, this.options);
Chris@0 1511 }
Chris@0 1512 });
Chris@0 1513 function $(element) {
Chris@0 1514 if (arguments.length > 1) {
Chris@0 1515 for (var i = 0, elements = [], length = arguments.length; i < length; i++)
Chris@0 1516 elements.push($(arguments[i]));
Chris@0 1517 return elements;
Chris@0 1518 }
Chris@0 1519 if (Object.isString(element))
Chris@0 1520 element = document.getElementById(element);
Chris@0 1521 return Element.extend(element);
Chris@0 1522 }
Chris@0 1523
Chris@0 1524 if (Prototype.BrowserFeatures.XPath) {
Chris@0 1525 document._getElementsByXPath = function(expression, parentElement) {
Chris@0 1526 var results = [];
Chris@0 1527 var query = document.evaluate(expression, $(parentElement) || document,
Chris@0 1528 null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
Chris@0 1529 for (var i = 0, length = query.snapshotLength; i < length; i++)
Chris@0 1530 results.push(Element.extend(query.snapshotItem(i)));
Chris@0 1531 return results;
Chris@0 1532 };
Chris@0 1533 }
Chris@0 1534
Chris@0 1535 /*--------------------------------------------------------------------------*/
Chris@0 1536
Chris@0 1537 if (!window.Node) var Node = { };
Chris@0 1538
Chris@0 1539 if (!Node.ELEMENT_NODE) {
Chris@0 1540 // DOM level 2 ECMAScript Language Binding
Chris@0 1541 Object.extend(Node, {
Chris@0 1542 ELEMENT_NODE: 1,
Chris@0 1543 ATTRIBUTE_NODE: 2,
Chris@0 1544 TEXT_NODE: 3,
Chris@0 1545 CDATA_SECTION_NODE: 4,
Chris@0 1546 ENTITY_REFERENCE_NODE: 5,
Chris@0 1547 ENTITY_NODE: 6,
Chris@0 1548 PROCESSING_INSTRUCTION_NODE: 7,
Chris@0 1549 COMMENT_NODE: 8,
Chris@0 1550 DOCUMENT_NODE: 9,
Chris@0 1551 DOCUMENT_TYPE_NODE: 10,
Chris@0 1552 DOCUMENT_FRAGMENT_NODE: 11,
Chris@0 1553 NOTATION_NODE: 12
Chris@0 1554 });
Chris@0 1555 }
Chris@0 1556
Chris@0 1557 (function() {
Chris@0 1558 var element = this.Element;
Chris@0 1559 this.Element = function(tagName, attributes) {
Chris@0 1560 attributes = attributes || { };
Chris@0 1561 tagName = tagName.toLowerCase();
Chris@0 1562 var cache = Element.cache;
Chris@0 1563 if (Prototype.Browser.IE && attributes.name) {
Chris@0 1564 tagName = '<' + tagName + ' name="' + attributes.name + '">';
Chris@0 1565 delete attributes.name;
Chris@0 1566 return Element.writeAttribute(document.createElement(tagName), attributes);
Chris@0 1567 }
Chris@0 1568 if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
Chris@0 1569 return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
Chris@0 1570 };
Chris@0 1571 Object.extend(this.Element, element || { });
Chris@0 1572 if (element) this.Element.prototype = element.prototype;
Chris@0 1573 }).call(window);
Chris@0 1574
Chris@0 1575 Element.cache = { };
Chris@0 1576
Chris@0 1577 Element.Methods = {
Chris@0 1578 visible: function(element) {
Chris@0 1579 return $(element).style.display != 'none';
Chris@0 1580 },
Chris@0 1581
Chris@0 1582 toggle: function(element) {
Chris@0 1583 element = $(element);
Chris@0 1584 Element[Element.visible(element) ? 'hide' : 'show'](element);
Chris@0 1585 return element;
Chris@0 1586 },
Chris@0 1587
Chris@0 1588 hide: function(element) {
Chris@0 1589 element = $(element);
Chris@0 1590 element.style.display = 'none';
Chris@0 1591 return element;
Chris@0 1592 },
Chris@0 1593
Chris@0 1594 show: function(element) {
Chris@0 1595 element = $(element);
Chris@0 1596 element.style.display = '';
Chris@0 1597 return element;
Chris@0 1598 },
Chris@0 1599
Chris@0 1600 remove: function(element) {
Chris@0 1601 element = $(element);
Chris@0 1602 element.parentNode.removeChild(element);
Chris@0 1603 return element;
Chris@0 1604 },
Chris@0 1605
Chris@0 1606 update: function(element, content) {
Chris@0 1607 element = $(element);
Chris@0 1608 if (content && content.toElement) content = content.toElement();
Chris@0 1609 if (Object.isElement(content)) return element.update().insert(content);
Chris@0 1610 content = Object.toHTML(content);
Chris@0 1611 element.innerHTML = content.stripScripts();
Chris@0 1612 content.evalScripts.bind(content).defer();
Chris@0 1613 return element;
Chris@0 1614 },
Chris@0 1615
Chris@0 1616 replace: function(element, content) {
Chris@0 1617 element = $(element);
Chris@0 1618 if (content && content.toElement) content = content.toElement();
Chris@0 1619 else if (!Object.isElement(content)) {
Chris@0 1620 content = Object.toHTML(content);
Chris@0 1621 var range = element.ownerDocument.createRange();
Chris@0 1622 range.selectNode(element);
Chris@0 1623 content.evalScripts.bind(content).defer();
Chris@0 1624 content = range.createContextualFragment(content.stripScripts());
Chris@0 1625 }
Chris@0 1626 element.parentNode.replaceChild(content, element);
Chris@0 1627 return element;
Chris@0 1628 },
Chris@0 1629
Chris@0 1630 insert: function(element, insertions) {
Chris@0 1631 element = $(element);
Chris@0 1632
Chris@0 1633 if (Object.isString(insertions) || Object.isNumber(insertions) ||
Chris@0 1634 Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
Chris@0 1635 insertions = {bottom:insertions};
Chris@0 1636
Chris@0 1637 var content, insert, tagName, childNodes;
Chris@0 1638
Chris@0 1639 for (var position in insertions) {
Chris@0 1640 content = insertions[position];
Chris@0 1641 position = position.toLowerCase();
Chris@0 1642 insert = Element._insertionTranslations[position];
Chris@0 1643
Chris@0 1644 if (content && content.toElement) content = content.toElement();
Chris@0 1645 if (Object.isElement(content)) {
Chris@0 1646 insert(element, content);
Chris@0 1647 continue;
Chris@0 1648 }
Chris@0 1649
Chris@0 1650 content = Object.toHTML(content);
Chris@0 1651
Chris@0 1652 tagName = ((position == 'before' || position == 'after')
Chris@0 1653 ? element.parentNode : element).tagName.toUpperCase();
Chris@0 1654
Chris@0 1655 childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
Chris@0 1656
Chris@0 1657 if (position == 'top' || position == 'after') childNodes.reverse();
Chris@0 1658 childNodes.each(insert.curry(element));
Chris@0 1659
Chris@0 1660 content.evalScripts.bind(content).defer();
Chris@0 1661 }
Chris@0 1662
Chris@0 1663 return element;
Chris@0 1664 },
Chris@0 1665
Chris@0 1666 wrap: function(element, wrapper, attributes) {
Chris@0 1667 element = $(element);
Chris@0 1668 if (Object.isElement(wrapper))
Chris@0 1669 $(wrapper).writeAttribute(attributes || { });
Chris@0 1670 else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
Chris@0 1671 else wrapper = new Element('div', wrapper);
Chris@0 1672 if (element.parentNode)
Chris@0 1673 element.parentNode.replaceChild(wrapper, element);
Chris@0 1674 wrapper.appendChild(element);
Chris@0 1675 return wrapper;
Chris@0 1676 },
Chris@0 1677
Chris@0 1678 inspect: function(element) {
Chris@0 1679 element = $(element);
Chris@0 1680 var result = '<' + element.tagName.toLowerCase();
Chris@0 1681 $H({'id': 'id', 'className': 'class'}).each(function(pair) {
Chris@0 1682 var property = pair.first(), attribute = pair.last();
Chris@0 1683 var value = (element[property] || '').toString();
Chris@0 1684 if (value) result += ' ' + attribute + '=' + value.inspect(true);
Chris@0 1685 });
Chris@0 1686 return result + '>';
Chris@0 1687 },
Chris@0 1688
Chris@0 1689 recursivelyCollect: function(element, property) {
Chris@0 1690 element = $(element);
Chris@0 1691 var elements = [];
Chris@0 1692 while (element = element[property])
Chris@0 1693 if (element.nodeType == 1)
Chris@0 1694 elements.push(Element.extend(element));
Chris@0 1695 return elements;
Chris@0 1696 },
Chris@0 1697
Chris@0 1698 ancestors: function(element) {
Chris@0 1699 return $(element).recursivelyCollect('parentNode');
Chris@0 1700 },
Chris@0 1701
Chris@0 1702 descendants: function(element) {
Chris@0 1703 return $(element).select("*");
Chris@0 1704 },
Chris@0 1705
Chris@0 1706 firstDescendant: function(element) {
Chris@0 1707 element = $(element).firstChild;
Chris@0 1708 while (element && element.nodeType != 1) element = element.nextSibling;
Chris@0 1709 return $(element);
Chris@0 1710 },
Chris@0 1711
Chris@0 1712 immediateDescendants: function(element) {
Chris@0 1713 if (!(element = $(element).firstChild)) return [];
Chris@0 1714 while (element && element.nodeType != 1) element = element.nextSibling;
Chris@0 1715 if (element) return [element].concat($(element).nextSiblings());
Chris@0 1716 return [];
Chris@0 1717 },
Chris@0 1718
Chris@0 1719 previousSiblings: function(element) {
Chris@0 1720 return $(element).recursivelyCollect('previousSibling');
Chris@0 1721 },
Chris@0 1722
Chris@0 1723 nextSiblings: function(element) {
Chris@0 1724 return $(element).recursivelyCollect('nextSibling');
Chris@0 1725 },
Chris@0 1726
Chris@0 1727 siblings: function(element) {
Chris@0 1728 element = $(element);
Chris@0 1729 return element.previousSiblings().reverse().concat(element.nextSiblings());
Chris@0 1730 },
Chris@0 1731
Chris@0 1732 match: function(element, selector) {
Chris@0 1733 if (Object.isString(selector))
Chris@0 1734 selector = new Selector(selector);
Chris@0 1735 return selector.match($(element));
Chris@0 1736 },
Chris@0 1737
Chris@0 1738 up: function(element, expression, index) {
Chris@0 1739 element = $(element);
Chris@0 1740 if (arguments.length == 1) return $(element.parentNode);
Chris@0 1741 var ancestors = element.ancestors();
Chris@0 1742 return Object.isNumber(expression) ? ancestors[expression] :
Chris@0 1743 Selector.findElement(ancestors, expression, index);
Chris@0 1744 },
Chris@0 1745
Chris@0 1746 down: function(element, expression, index) {
Chris@0 1747 element = $(element);
Chris@0 1748 if (arguments.length == 1) return element.firstDescendant();
Chris@0 1749 return Object.isNumber(expression) ? element.descendants()[expression] :
Chris@0 1750 Element.select(element, expression)[index || 0];
Chris@0 1751 },
Chris@0 1752
Chris@0 1753 previous: function(element, expression, index) {
Chris@0 1754 element = $(element);
Chris@0 1755 if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
Chris@0 1756 var previousSiblings = element.previousSiblings();
Chris@0 1757 return Object.isNumber(expression) ? previousSiblings[expression] :
Chris@0 1758 Selector.findElement(previousSiblings, expression, index);
Chris@0 1759 },
Chris@0 1760
Chris@0 1761 next: function(element, expression, index) {
Chris@0 1762 element = $(element);
Chris@0 1763 if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
Chris@0 1764 var nextSiblings = element.nextSiblings();
Chris@0 1765 return Object.isNumber(expression) ? nextSiblings[expression] :
Chris@0 1766 Selector.findElement(nextSiblings, expression, index);
Chris@0 1767 },
Chris@0 1768
Chris@0 1769 select: function() {
Chris@0 1770 var args = $A(arguments), element = $(args.shift());
Chris@0 1771 return Selector.findChildElements(element, args);
Chris@0 1772 },
Chris@0 1773
Chris@0 1774 adjacent: function() {
Chris@0 1775 var args = $A(arguments), element = $(args.shift());
Chris@0 1776 return Selector.findChildElements(element.parentNode, args).without(element);
Chris@0 1777 },
Chris@0 1778
Chris@0 1779 identify: function(element) {
Chris@0 1780 element = $(element);
Chris@0 1781 var id = element.readAttribute('id'), self = arguments.callee;
Chris@0 1782 if (id) return id;
Chris@0 1783 do { id = 'anonymous_element_' + self.counter++ } while ($(id));
Chris@0 1784 element.writeAttribute('id', id);
Chris@0 1785 return id;
Chris@0 1786 },
Chris@0 1787
Chris@0 1788 readAttribute: function(element, name) {
Chris@0 1789 element = $(element);
Chris@0 1790 if (Prototype.Browser.IE) {
Chris@0 1791 var t = Element._attributeTranslations.read;
Chris@0 1792 if (t.values[name]) return t.values[name](element, name);
Chris@0 1793 if (t.names[name]) name = t.names[name];
Chris@0 1794 if (name.include(':')) {
Chris@0 1795 return (!element.attributes || !element.attributes[name]) ? null :
Chris@0 1796 element.attributes[name].value;
Chris@0 1797 }
Chris@0 1798 }
Chris@0 1799 return element.getAttribute(name);
Chris@0 1800 },
Chris@0 1801
Chris@0 1802 writeAttribute: function(element, name, value) {
Chris@0 1803 element = $(element);
Chris@0 1804 var attributes = { }, t = Element._attributeTranslations.write;
Chris@0 1805
Chris@0 1806 if (typeof name == 'object') attributes = name;
Chris@0 1807 else attributes[name] = Object.isUndefined(value) ? true : value;
Chris@0 1808
Chris@0 1809 for (var attr in attributes) {
Chris@0 1810 name = t.names[attr] || attr;
Chris@0 1811 value = attributes[attr];
Chris@0 1812 if (t.values[attr]) name = t.values[attr](element, value);
Chris@0 1813 if (value === false || value === null)
Chris@0 1814 element.removeAttribute(name);
Chris@0 1815 else if (value === true)
Chris@0 1816 element.setAttribute(name, name);
Chris@0 1817 else element.setAttribute(name, value);
Chris@0 1818 }
Chris@0 1819 return element;
Chris@0 1820 },
Chris@0 1821
Chris@0 1822 getHeight: function(element) {
Chris@0 1823 return $(element).getDimensions().height;
Chris@0 1824 },
Chris@0 1825
Chris@0 1826 getWidth: function(element) {
Chris@0 1827 return $(element).getDimensions().width;
Chris@0 1828 },
Chris@0 1829
Chris@0 1830 classNames: function(element) {
Chris@0 1831 return new Element.ClassNames(element);
Chris@0 1832 },
Chris@0 1833
Chris@0 1834 hasClassName: function(element, className) {
Chris@0 1835 if (!(element = $(element))) return;
Chris@0 1836 var elementClassName = element.className;
Chris@0 1837 return (elementClassName.length > 0 && (elementClassName == className ||
Chris@0 1838 new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
Chris@0 1839 },
Chris@0 1840
Chris@0 1841 addClassName: function(element, className) {
Chris@0 1842 if (!(element = $(element))) return;
Chris@0 1843 if (!element.hasClassName(className))
Chris@0 1844 element.className += (element.className ? ' ' : '') + className;
Chris@0 1845 return element;
Chris@0 1846 },
Chris@0 1847
Chris@0 1848 removeClassName: function(element, className) {
Chris@0 1849 if (!(element = $(element))) return;
Chris@0 1850 element.className = element.className.replace(
Chris@0 1851 new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
Chris@0 1852 return element;
Chris@0 1853 },
Chris@0 1854
Chris@0 1855 toggleClassName: function(element, className) {
Chris@0 1856 if (!(element = $(element))) return;
Chris@0 1857 return element[element.hasClassName(className) ?
Chris@0 1858 'removeClassName' : 'addClassName'](className);
Chris@0 1859 },
Chris@0 1860
Chris@0 1861 // removes whitespace-only text node children
Chris@0 1862 cleanWhitespace: function(element) {
Chris@0 1863 element = $(element);
Chris@0 1864 var node = element.firstChild;
Chris@0 1865 while (node) {
Chris@0 1866 var nextNode = node.nextSibling;
Chris@0 1867 if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
Chris@0 1868 element.removeChild(node);
Chris@0 1869 node = nextNode;
Chris@0 1870 }
Chris@0 1871 return element;
Chris@0 1872 },
Chris@0 1873
Chris@0 1874 empty: function(element) {
Chris@0 1875 return $(element).innerHTML.blank();
Chris@0 1876 },
Chris@0 1877
Chris@0 1878 descendantOf: function(element, ancestor) {
Chris@0 1879 element = $(element), ancestor = $(ancestor);
Chris@0 1880
Chris@0 1881 if (element.compareDocumentPosition)
Chris@0 1882 return (element.compareDocumentPosition(ancestor) & 8) === 8;
Chris@0 1883
Chris@0 1884 if (ancestor.contains)
Chris@0 1885 return ancestor.contains(element) && ancestor !== element;
Chris@0 1886
Chris@0 1887 while (element = element.parentNode)
Chris@0 1888 if (element == ancestor) return true;
Chris@0 1889
Chris@0 1890 return false;
Chris@0 1891 },
Chris@0 1892
Chris@0 1893 scrollTo: function(element) {
Chris@0 1894 element = $(element);
Chris@0 1895 var pos = element.cumulativeOffset();
Chris@0 1896 window.scrollTo(pos[0], pos[1]);
Chris@0 1897 return element;
Chris@0 1898 },
Chris@0 1899
Chris@0 1900 getStyle: function(element, style) {
Chris@0 1901 element = $(element);
Chris@0 1902 style = style == 'float' ? 'cssFloat' : style.camelize();
Chris@0 1903 var value = element.style[style];
Chris@0 1904 if (!value || value == 'auto') {
Chris@0 1905 var css = document.defaultView.getComputedStyle(element, null);
Chris@0 1906 value = css ? css[style] : null;
Chris@0 1907 }
Chris@0 1908 if (style == 'opacity') return value ? parseFloat(value) : 1.0;
Chris@0 1909 return value == 'auto' ? null : value;
Chris@0 1910 },
Chris@0 1911
Chris@0 1912 getOpacity: function(element) {
Chris@0 1913 return $(element).getStyle('opacity');
Chris@0 1914 },
Chris@0 1915
Chris@0 1916 setStyle: function(element, styles) {
Chris@0 1917 element = $(element);
Chris@0 1918 var elementStyle = element.style, match;
Chris@0 1919 if (Object.isString(styles)) {
Chris@0 1920 element.style.cssText += ';' + styles;
Chris@0 1921 return styles.include('opacity') ?
Chris@0 1922 element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
Chris@0 1923 }
Chris@0 1924 for (var property in styles)
Chris@0 1925 if (property == 'opacity') element.setOpacity(styles[property]);
Chris@0 1926 else
Chris@0 1927 elementStyle[(property == 'float' || property == 'cssFloat') ?
Chris@0 1928 (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
Chris@0 1929 property] = styles[property];
Chris@0 1930
Chris@0 1931 return element;
Chris@0 1932 },
Chris@0 1933
Chris@0 1934 setOpacity: function(element, value) {
Chris@0 1935 element = $(element);
Chris@0 1936 element.style.opacity = (value == 1 || value === '') ? '' :
Chris@0 1937 (value < 0.00001) ? 0 : value;
Chris@0 1938 return element;
Chris@0 1939 },
Chris@0 1940
Chris@0 1941 getDimensions: function(element) {
Chris@0 1942 element = $(element);
Chris@0 1943 var display = element.getStyle('display');
Chris@0 1944 if (display != 'none' && display != null) // Safari bug
Chris@0 1945 return {width: element.offsetWidth, height: element.offsetHeight};
Chris@0 1946
Chris@0 1947 // All *Width and *Height properties give 0 on elements with display none,
Chris@0 1948 // so enable the element temporarily
Chris@0 1949 var els = element.style;
Chris@0 1950 var originalVisibility = els.visibility;
Chris@0 1951 var originalPosition = els.position;
Chris@0 1952 var originalDisplay = els.display;
Chris@0 1953 els.visibility = 'hidden';
Chris@0 1954 els.position = 'absolute';
Chris@0 1955 els.display = 'block';
Chris@0 1956 var originalWidth = element.clientWidth;
Chris@0 1957 var originalHeight = element.clientHeight;
Chris@0 1958 els.display = originalDisplay;
Chris@0 1959 els.position = originalPosition;
Chris@0 1960 els.visibility = originalVisibility;
Chris@0 1961 return {width: originalWidth, height: originalHeight};
Chris@0 1962 },
Chris@0 1963
Chris@0 1964 makePositioned: function(element) {
Chris@0 1965 element = $(element);
Chris@0 1966 var pos = Element.getStyle(element, 'position');
Chris@0 1967 if (pos == 'static' || !pos) {
Chris@0 1968 element._madePositioned = true;
Chris@0 1969 element.style.position = 'relative';
Chris@0 1970 // Opera returns the offset relative to the positioning context, when an
Chris@0 1971 // element is position relative but top and left have not been defined
Chris@0 1972 if (Prototype.Browser.Opera) {
Chris@0 1973 element.style.top = 0;
Chris@0 1974 element.style.left = 0;
Chris@0 1975 }
Chris@0 1976 }
Chris@0 1977 return element;
Chris@0 1978 },
Chris@0 1979
Chris@0 1980 undoPositioned: function(element) {
Chris@0 1981 element = $(element);
Chris@0 1982 if (element._madePositioned) {
Chris@0 1983 element._madePositioned = undefined;
Chris@0 1984 element.style.position =
Chris@0 1985 element.style.top =
Chris@0 1986 element.style.left =
Chris@0 1987 element.style.bottom =
Chris@0 1988 element.style.right = '';
Chris@0 1989 }
Chris@0 1990 return element;
Chris@0 1991 },
Chris@0 1992
Chris@0 1993 makeClipping: function(element) {
Chris@0 1994 element = $(element);
Chris@0 1995 if (element._overflow) return element;
Chris@0 1996 element._overflow = Element.getStyle(element, 'overflow') || 'auto';
Chris@0 1997 if (element._overflow !== 'hidden')
Chris@0 1998 element.style.overflow = 'hidden';
Chris@0 1999 return element;
Chris@0 2000 },
Chris@0 2001
Chris@0 2002 undoClipping: function(element) {
Chris@0 2003 element = $(element);
Chris@0 2004 if (!element._overflow) return element;
Chris@0 2005 element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
Chris@0 2006 element._overflow = null;
Chris@0 2007 return element;
Chris@0 2008 },
Chris@0 2009
Chris@0 2010 cumulativeOffset: function(element) {
Chris@0 2011 var valueT = 0, valueL = 0;
Chris@0 2012 do {
Chris@0 2013 valueT += element.offsetTop || 0;
Chris@0 2014 valueL += element.offsetLeft || 0;
Chris@0 2015 element = element.offsetParent;
Chris@0 2016 } while (element);
Chris@0 2017 return Element._returnOffset(valueL, valueT);
Chris@0 2018 },
Chris@0 2019
Chris@0 2020 positionedOffset: function(element) {
Chris@0 2021 var valueT = 0, valueL = 0;
Chris@0 2022 do {
Chris@0 2023 valueT += element.offsetTop || 0;
Chris@0 2024 valueL += element.offsetLeft || 0;
Chris@0 2025 element = element.offsetParent;
Chris@0 2026 if (element) {
Chris@0 2027 if (element.tagName.toUpperCase() == 'BODY') break;
Chris@0 2028 var p = Element.getStyle(element, 'position');
Chris@0 2029 if (p !== 'static') break;
Chris@0 2030 }
Chris@0 2031 } while (element);
Chris@0 2032 return Element._returnOffset(valueL, valueT);
Chris@0 2033 },
Chris@0 2034
Chris@0 2035 absolutize: function(element) {
Chris@0 2036 element = $(element);
Chris@0 2037 if (element.getStyle('position') == 'absolute') return element;
Chris@0 2038 // Position.prepare(); // To be done manually by Scripty when it needs it.
Chris@0 2039
Chris@0 2040 var offsets = element.positionedOffset();
Chris@0 2041 var top = offsets[1];
Chris@0 2042 var left = offsets[0];
Chris@0 2043 var width = element.clientWidth;
Chris@0 2044 var height = element.clientHeight;
Chris@0 2045
Chris@0 2046 element._originalLeft = left - parseFloat(element.style.left || 0);
Chris@0 2047 element._originalTop = top - parseFloat(element.style.top || 0);
Chris@0 2048 element._originalWidth = element.style.width;
Chris@0 2049 element._originalHeight = element.style.height;
Chris@0 2050
Chris@0 2051 element.style.position = 'absolute';
Chris@0 2052 element.style.top = top + 'px';
Chris@0 2053 element.style.left = left + 'px';
Chris@0 2054 element.style.width = width + 'px';
Chris@0 2055 element.style.height = height + 'px';
Chris@0 2056 return element;
Chris@0 2057 },
Chris@0 2058
Chris@0 2059 relativize: function(element) {
Chris@0 2060 element = $(element);
Chris@0 2061 if (element.getStyle('position') == 'relative') return element;
Chris@0 2062 // Position.prepare(); // To be done manually by Scripty when it needs it.
Chris@0 2063
Chris@0 2064 element.style.position = 'relative';
Chris@0 2065 var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
Chris@0 2066 var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
Chris@0 2067
Chris@0 2068 element.style.top = top + 'px';
Chris@0 2069 element.style.left = left + 'px';
Chris@0 2070 element.style.height = element._originalHeight;
Chris@0 2071 element.style.width = element._originalWidth;
Chris@0 2072 return element;
Chris@0 2073 },
Chris@0 2074
Chris@0 2075 cumulativeScrollOffset: function(element) {
Chris@0 2076 var valueT = 0, valueL = 0;
Chris@0 2077 do {
Chris@0 2078 valueT += element.scrollTop || 0;
Chris@0 2079 valueL += element.scrollLeft || 0;
Chris@0 2080 element = element.parentNode;
Chris@0 2081 } while (element);
Chris@0 2082 return Element._returnOffset(valueL, valueT);
Chris@0 2083 },
Chris@0 2084
Chris@0 2085 getOffsetParent: function(element) {
Chris@0 2086 if (element.offsetParent) return $(element.offsetParent);
Chris@0 2087 if (element == document.body) return $(element);
Chris@0 2088
Chris@0 2089 while ((element = element.parentNode) && element != document.body)
Chris@0 2090 if (Element.getStyle(element, 'position') != 'static')
Chris@0 2091 return $(element);
Chris@0 2092
Chris@0 2093 return $(document.body);
Chris@0 2094 },
Chris@0 2095
Chris@0 2096 viewportOffset: function(forElement) {
Chris@0 2097 var valueT = 0, valueL = 0;
Chris@0 2098
Chris@0 2099 var element = forElement;
Chris@0 2100 do {
Chris@0 2101 valueT += element.offsetTop || 0;
Chris@0 2102 valueL += element.offsetLeft || 0;
Chris@0 2103
Chris@0 2104 // Safari fix
Chris@0 2105 if (element.offsetParent == document.body &&
Chris@0 2106 Element.getStyle(element, 'position') == 'absolute') break;
Chris@0 2107
Chris@0 2108 } while (element = element.offsetParent);
Chris@0 2109
Chris@0 2110 element = forElement;
Chris@0 2111 do {
Chris@0 2112 if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {
Chris@0 2113 valueT -= element.scrollTop || 0;
Chris@0 2114 valueL -= element.scrollLeft || 0;
Chris@0 2115 }
Chris@0 2116 } while (element = element.parentNode);
Chris@0 2117
Chris@0 2118 return Element._returnOffset(valueL, valueT);
Chris@0 2119 },
Chris@0 2120
Chris@0 2121 clonePosition: function(element, source) {
Chris@0 2122 var options = Object.extend({
Chris@0 2123 setLeft: true,
Chris@0 2124 setTop: true,
Chris@0 2125 setWidth: true,
Chris@0 2126 setHeight: true,
Chris@0 2127 offsetTop: 0,
Chris@0 2128 offsetLeft: 0
Chris@0 2129 }, arguments[2] || { });
Chris@0 2130
Chris@0 2131 // find page position of source
Chris@0 2132 source = $(source);
Chris@0 2133 var p = source.viewportOffset();
Chris@0 2134
Chris@0 2135 // find coordinate system to use
Chris@0 2136 element = $(element);
Chris@0 2137 var delta = [0, 0];
Chris@0 2138 var parent = null;
Chris@0 2139 // delta [0,0] will do fine with position: fixed elements,
Chris@0 2140 // position:absolute needs offsetParent deltas
Chris@0 2141 if (Element.getStyle(element, 'position') == 'absolute') {
Chris@0 2142 parent = element.getOffsetParent();
Chris@0 2143 delta = parent.viewportOffset();
Chris@0 2144 }
Chris@0 2145
Chris@0 2146 // correct by body offsets (fixes Safari)
Chris@0 2147 if (parent == document.body) {
Chris@0 2148 delta[0] -= document.body.offsetLeft;
Chris@0 2149 delta[1] -= document.body.offsetTop;
Chris@0 2150 }
Chris@0 2151
Chris@0 2152 // set position
Chris@0 2153 if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
Chris@0 2154 if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
Chris@0 2155 if (options.setWidth) element.style.width = source.offsetWidth + 'px';
Chris@0 2156 if (options.setHeight) element.style.height = source.offsetHeight + 'px';
Chris@0 2157 return element;
Chris@0 2158 }
Chris@0 2159 };
Chris@0 2160
Chris@0 2161 Element.Methods.identify.counter = 1;
Chris@0 2162
Chris@0 2163 Object.extend(Element.Methods, {
Chris@0 2164 getElementsBySelector: Element.Methods.select,
Chris@0 2165 childElements: Element.Methods.immediateDescendants
Chris@0 2166 });
Chris@0 2167
Chris@0 2168 Element._attributeTranslations = {
Chris@0 2169 write: {
Chris@0 2170 names: {
Chris@0 2171 className: 'class',
Chris@0 2172 htmlFor: 'for'
Chris@0 2173 },
Chris@0 2174 values: { }
Chris@0 2175 }
Chris@0 2176 };
Chris@0 2177
Chris@0 2178 if (Prototype.Browser.Opera) {
Chris@0 2179 Element.Methods.getStyle = Element.Methods.getStyle.wrap(
Chris@0 2180 function(proceed, element, style) {
Chris@0 2181 switch (style) {
Chris@0 2182 case 'left': case 'top': case 'right': case 'bottom':
Chris@0 2183 if (proceed(element, 'position') === 'static') return null;
Chris@0 2184 case 'height': case 'width':
Chris@0 2185 // returns '0px' for hidden elements; we want it to return null
Chris@0 2186 if (!Element.visible(element)) return null;
Chris@0 2187
Chris@0 2188 // returns the border-box dimensions rather than the content-box
Chris@0 2189 // dimensions, so we subtract padding and borders from the value
Chris@0 2190 var dim = parseInt(proceed(element, style), 10);
Chris@0 2191
Chris@0 2192 if (dim !== element['offset' + style.capitalize()])
Chris@0 2193 return dim + 'px';
Chris@0 2194
Chris@0 2195 var properties;
Chris@0 2196 if (style === 'height') {
Chris@0 2197 properties = ['border-top-width', 'padding-top',
Chris@0 2198 'padding-bottom', 'border-bottom-width'];
Chris@0 2199 }
Chris@0 2200 else {
Chris@0 2201 properties = ['border-left-width', 'padding-left',
Chris@0 2202 'padding-right', 'border-right-width'];
Chris@0 2203 }
Chris@0 2204 return properties.inject(dim, function(memo, property) {
Chris@0 2205 var val = proceed(element, property);
Chris@0 2206 return val === null ? memo : memo - parseInt(val, 10);
Chris@0 2207 }) + 'px';
Chris@0 2208 default: return proceed(element, style);
Chris@0 2209 }
Chris@0 2210 }
Chris@0 2211 );
Chris@0 2212
Chris@0 2213 Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
Chris@0 2214 function(proceed, element, attribute) {
Chris@0 2215 if (attribute === 'title') return element.title;
Chris@0 2216 return proceed(element, attribute);
Chris@0 2217 }
Chris@0 2218 );
Chris@0 2219 }
Chris@0 2220
Chris@0 2221 else if (Prototype.Browser.IE) {
Chris@0 2222 // IE doesn't report offsets correctly for static elements, so we change them
Chris@0 2223 // to "relative" to get the values, then change them back.
Chris@0 2224 Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
Chris@0 2225 function(proceed, element) {
Chris@0 2226 element = $(element);
Chris@0 2227 // IE throws an error if element is not in document
Chris@0 2228 try { element.offsetParent }
Chris@0 2229 catch(e) { return $(document.body) }
Chris@0 2230 var position = element.getStyle('position');
Chris@0 2231 if (position !== 'static') return proceed(element);
Chris@0 2232 element.setStyle({ position: 'relative' });
Chris@0 2233 var value = proceed(element);
Chris@0 2234 element.setStyle({ position: position });
Chris@0 2235 return value;
Chris@0 2236 }
Chris@0 2237 );
Chris@0 2238
Chris@0 2239 $w('positionedOffset viewportOffset').each(function(method) {
Chris@0 2240 Element.Methods[method] = Element.Methods[method].wrap(
Chris@0 2241 function(proceed, element) {
Chris@0 2242 element = $(element);
Chris@0 2243 try { element.offsetParent }
Chris@0 2244 catch(e) { return Element._returnOffset(0,0) }
Chris@0 2245 var position = element.getStyle('position');
Chris@0 2246 if (position !== 'static') return proceed(element);
Chris@0 2247 // Trigger hasLayout on the offset parent so that IE6 reports
Chris@0 2248 // accurate offsetTop and offsetLeft values for position: fixed.
Chris@0 2249 var offsetParent = element.getOffsetParent();
Chris@0 2250 if (offsetParent && offsetParent.getStyle('position') === 'fixed')
Chris@0 2251 offsetParent.setStyle({ zoom: 1 });
Chris@0 2252 element.setStyle({ position: 'relative' });
Chris@0 2253 var value = proceed(element);
Chris@0 2254 element.setStyle({ position: position });
Chris@0 2255 return value;
Chris@0 2256 }
Chris@0 2257 );
Chris@0 2258 });
Chris@0 2259
Chris@0 2260 Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
Chris@0 2261 function(proceed, element) {
Chris@0 2262 try { element.offsetParent }
Chris@0 2263 catch(e) { return Element._returnOffset(0,0) }
Chris@0 2264 return proceed(element);
Chris@0 2265 }
Chris@0 2266 );
Chris@0 2267
Chris@0 2268 Element.Methods.getStyle = function(element, style) {
Chris@0 2269 element = $(element);
Chris@0 2270 style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
Chris@0 2271 var value = element.style[style];
Chris@0 2272 if (!value && element.currentStyle) value = element.currentStyle[style];
Chris@0 2273
Chris@0 2274 if (style == 'opacity') {
Chris@0 2275 if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
Chris@0 2276 if (value[1]) return parseFloat(value[1]) / 100;
Chris@0 2277 return 1.0;
Chris@0 2278 }
Chris@0 2279
Chris@0 2280 if (value == 'auto') {
Chris@0 2281 if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
Chris@0 2282 return element['offset' + style.capitalize()] + 'px';
Chris@0 2283 return null;
Chris@0 2284 }
Chris@0 2285 return value;
Chris@0 2286 };
Chris@0 2287
Chris@0 2288 Element.Methods.setOpacity = function(element, value) {
Chris@0 2289 function stripAlpha(filter){
Chris@0 2290 return filter.replace(/alpha\([^\)]*\)/gi,'');
Chris@0 2291 }
Chris@0 2292 element = $(element);
Chris@0 2293 var currentStyle = element.currentStyle;
Chris@0 2294 if ((currentStyle && !currentStyle.hasLayout) ||
Chris@0 2295 (!currentStyle && element.style.zoom == 'normal'))
Chris@0 2296 element.style.zoom = 1;
Chris@0 2297
Chris@0 2298 var filter = element.getStyle('filter'), style = element.style;
Chris@0 2299 if (value == 1 || value === '') {
Chris@0 2300 (filter = stripAlpha(filter)) ?
Chris@0 2301 style.filter = filter : style.removeAttribute('filter');
Chris@0 2302 return element;
Chris@0 2303 } else if (value < 0.00001) value = 0;
Chris@0 2304 style.filter = stripAlpha(filter) +
Chris@0 2305 'alpha(opacity=' + (value * 100) + ')';
Chris@0 2306 return element;
Chris@0 2307 };
Chris@0 2308
Chris@0 2309 Element._attributeTranslations = {
Chris@0 2310 read: {
Chris@0 2311 names: {
Chris@0 2312 'class': 'className',
Chris@0 2313 'for': 'htmlFor'
Chris@0 2314 },
Chris@0 2315 values: {
Chris@0 2316 _getAttr: function(element, attribute) {
Chris@0 2317 return element.getAttribute(attribute, 2);
Chris@0 2318 },
Chris@0 2319 _getAttrNode: function(element, attribute) {
Chris@0 2320 var node = element.getAttributeNode(attribute);
Chris@0 2321 return node ? node.value : "";
Chris@0 2322 },
Chris@0 2323 _getEv: function(element, attribute) {
Chris@0 2324 attribute = element.getAttribute(attribute);
Chris@0 2325 return attribute ? attribute.toString().slice(23, -2) : null;
Chris@0 2326 },
Chris@0 2327 _flag: function(element, attribute) {
Chris@0 2328 return $(element).hasAttribute(attribute) ? attribute : null;
Chris@0 2329 },
Chris@0 2330 style: function(element) {
Chris@0 2331 return element.style.cssText.toLowerCase();
Chris@0 2332 },
Chris@0 2333 title: function(element) {
Chris@0 2334 return element.title;
Chris@0 2335 }
Chris@0 2336 }
Chris@0 2337 }
Chris@0 2338 };
Chris@0 2339
Chris@0 2340 Element._attributeTranslations.write = {
Chris@0 2341 names: Object.extend({
Chris@0 2342 cellpadding: 'cellPadding',
Chris@0 2343 cellspacing: 'cellSpacing'
Chris@0 2344 }, Element._attributeTranslations.read.names),
Chris@0 2345 values: {
Chris@0 2346 checked: function(element, value) {
Chris@0 2347 element.checked = !!value;
Chris@0 2348 },
Chris@0 2349
Chris@0 2350 style: function(element, value) {
Chris@0 2351 element.style.cssText = value ? value : '';
Chris@0 2352 }
Chris@0 2353 }
Chris@0 2354 };
Chris@0 2355
Chris@0 2356 Element._attributeTranslations.has = {};
Chris@0 2357
Chris@0 2358 $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
Chris@0 2359 'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
Chris@0 2360 Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
Chris@0 2361 Element._attributeTranslations.has[attr.toLowerCase()] = attr;
Chris@0 2362 });
Chris@0 2363
Chris@0 2364 (function(v) {
Chris@0 2365 Object.extend(v, {
Chris@0 2366 href: v._getAttr,
Chris@0 2367 src: v._getAttr,
Chris@0 2368 type: v._getAttr,
Chris@0 2369 action: v._getAttrNode,
Chris@0 2370 disabled: v._flag,
Chris@0 2371 checked: v._flag,
Chris@0 2372 readonly: v._flag,
Chris@0 2373 multiple: v._flag,
Chris@0 2374 onload: v._getEv,
Chris@0 2375 onunload: v._getEv,
Chris@0 2376 onclick: v._getEv,
Chris@0 2377 ondblclick: v._getEv,
Chris@0 2378 onmousedown: v._getEv,
Chris@0 2379 onmouseup: v._getEv,
Chris@0 2380 onmouseover: v._getEv,
Chris@0 2381 onmousemove: v._getEv,
Chris@0 2382 onmouseout: v._getEv,
Chris@0 2383 onfocus: v._getEv,
Chris@0 2384 onblur: v._getEv,
Chris@0 2385 onkeypress: v._getEv,
Chris@0 2386 onkeydown: v._getEv,
Chris@0 2387 onkeyup: v._getEv,
Chris@0 2388 onsubmit: v._getEv,
Chris@0 2389 onreset: v._getEv,
Chris@0 2390 onselect: v._getEv,
Chris@0 2391 onchange: v._getEv
Chris@0 2392 });
Chris@0 2393 })(Element._attributeTranslations.read.values);
Chris@0 2394 }
Chris@0 2395
Chris@0 2396 else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
Chris@0 2397 Element.Methods.setOpacity = function(element, value) {
Chris@0 2398 element = $(element);
Chris@0 2399 element.style.opacity = (value == 1) ? 0.999999 :
Chris@0 2400 (value === '') ? '' : (value < 0.00001) ? 0 : value;
Chris@0 2401 return element;
Chris@0 2402 };
Chris@0 2403 }
Chris@0 2404
Chris@0 2405 else if (Prototype.Browser.WebKit) {
Chris@0 2406 Element.Methods.setOpacity = function(element, value) {
Chris@0 2407 element = $(element);
Chris@0 2408 element.style.opacity = (value == 1 || value === '') ? '' :
Chris@0 2409 (value < 0.00001) ? 0 : value;
Chris@0 2410
Chris@0 2411 if (value == 1)
Chris@0 2412 if(element.tagName.toUpperCase() == 'IMG' && element.width) {
Chris@0 2413 element.width++; element.width--;
Chris@0 2414 } else try {
Chris@0 2415 var n = document.createTextNode(' ');
Chris@0 2416 element.appendChild(n);
Chris@0 2417 element.removeChild(n);
Chris@0 2418 } catch (e) { }
Chris@0 2419
Chris@0 2420 return element;
Chris@0 2421 };
Chris@0 2422
Chris@0 2423 // Safari returns margins on body which is incorrect if the child is absolutely
Chris@0 2424 // positioned. For performance reasons, redefine Element#cumulativeOffset for
Chris@0 2425 // KHTML/WebKit only.
Chris@0 2426 Element.Methods.cumulativeOffset = function(element) {
Chris@0 2427 var valueT = 0, valueL = 0;
Chris@0 2428 do {
Chris@0 2429 valueT += element.offsetTop || 0;
Chris@0 2430 valueL += element.offsetLeft || 0;
Chris@0 2431 if (element.offsetParent == document.body)
Chris@0 2432 if (Element.getStyle(element, 'position') == 'absolute') break;
Chris@0 2433
Chris@0 2434 element = element.offsetParent;
Chris@0 2435 } while (element);
Chris@0 2436
Chris@0 2437 return Element._returnOffset(valueL, valueT);
Chris@0 2438 };
Chris@0 2439 }
Chris@0 2440
Chris@0 2441 if (Prototype.Browser.IE || Prototype.Browser.Opera) {
Chris@0 2442 // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
Chris@0 2443 Element.Methods.update = function(element, content) {
Chris@0 2444 element = $(element);
Chris@0 2445
Chris@0 2446 if (content && content.toElement) content = content.toElement();
Chris@0 2447 if (Object.isElement(content)) return element.update().insert(content);
Chris@0 2448
Chris@0 2449 content = Object.toHTML(content);
Chris@0 2450 var tagName = element.tagName.toUpperCase();
Chris@0 2451
Chris@0 2452 if (tagName in Element._insertionTranslations.tags) {
Chris@0 2453 $A(element.childNodes).each(function(node) { element.removeChild(node) });
Chris@0 2454 Element._getContentFromAnonymousElement(tagName, content.stripScripts())
Chris@0 2455 .each(function(node) { element.appendChild(node) });
Chris@0 2456 }
Chris@0 2457 else element.innerHTML = content.stripScripts();
Chris@0 2458
Chris@0 2459 content.evalScripts.bind(content).defer();
Chris@0 2460 return element;
Chris@0 2461 };
Chris@0 2462 }
Chris@0 2463
Chris@0 2464 if ('outerHTML' in document.createElement('div')) {
Chris@0 2465 Element.Methods.replace = function(element, content) {
Chris@0 2466 element = $(element);
Chris@0 2467
Chris@0 2468 if (content && content.toElement) content = content.toElement();
Chris@0 2469 if (Object.isElement(content)) {
Chris@0 2470 element.parentNode.replaceChild(content, element);
Chris@0 2471 return element;
Chris@0 2472 }
Chris@0 2473
Chris@0 2474 content = Object.toHTML(content);
Chris@0 2475 var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
Chris@0 2476
Chris@0 2477 if (Element._insertionTranslations.tags[tagName]) {
Chris@0 2478 var nextSibling = element.next();
Chris@0 2479 var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
Chris@0 2480 parent.removeChild(element);
Chris@0 2481 if (nextSibling)
Chris@0 2482 fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
Chris@0 2483 else
Chris@0 2484 fragments.each(function(node) { parent.appendChild(node) });
Chris@0 2485 }
Chris@0 2486 else element.outerHTML = content.stripScripts();
Chris@0 2487
Chris@0 2488 content.evalScripts.bind(content).defer();
Chris@0 2489 return element;
Chris@0 2490 };
Chris@0 2491 }
Chris@0 2492
Chris@0 2493 Element._returnOffset = function(l, t) {
Chris@0 2494 var result = [l, t];
Chris@0 2495 result.left = l;
Chris@0 2496 result.top = t;
Chris@0 2497 return result;
Chris@0 2498 };
Chris@0 2499
Chris@0 2500 Element._getContentFromAnonymousElement = function(tagName, html) {
Chris@0 2501 var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
Chris@0 2502 if (t) {
Chris@0 2503 div.innerHTML = t[0] + html + t[1];
Chris@0 2504 t[2].times(function() { div = div.firstChild });
Chris@0 2505 } else div.innerHTML = html;
Chris@0 2506 return $A(div.childNodes);
Chris@0 2507 };
Chris@0 2508
Chris@0 2509 Element._insertionTranslations = {
Chris@0 2510 before: function(element, node) {
Chris@0 2511 element.parentNode.insertBefore(node, element);
Chris@0 2512 },
Chris@0 2513 top: function(element, node) {
Chris@0 2514 element.insertBefore(node, element.firstChild);
Chris@0 2515 },
Chris@0 2516 bottom: function(element, node) {
Chris@0 2517 element.appendChild(node);
Chris@0 2518 },
Chris@0 2519 after: function(element, node) {
Chris@0 2520 element.parentNode.insertBefore(node, element.nextSibling);
Chris@0 2521 },
Chris@0 2522 tags: {
Chris@0 2523 TABLE: ['<table>', '</table>', 1],
Chris@0 2524 TBODY: ['<table><tbody>', '</tbody></table>', 2],
Chris@0 2525 TR: ['<table><tbody><tr>', '</tr></tbody></table>', 3],
Chris@0 2526 TD: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
Chris@0 2527 SELECT: ['<select>', '</select>', 1]
Chris@0 2528 }
Chris@0 2529 };
Chris@0 2530
Chris@0 2531 (function() {
Chris@0 2532 Object.extend(this.tags, {
Chris@0 2533 THEAD: this.tags.TBODY,
Chris@0 2534 TFOOT: this.tags.TBODY,
Chris@0 2535 TH: this.tags.TD
Chris@0 2536 });
Chris@0 2537 }).call(Element._insertionTranslations);
Chris@0 2538
Chris@0 2539 Element.Methods.Simulated = {
Chris@0 2540 hasAttribute: function(element, attribute) {
Chris@0 2541 attribute = Element._attributeTranslations.has[attribute] || attribute;
Chris@0 2542 var node = $(element).getAttributeNode(attribute);
Chris@0 2543 return !!(node && node.specified);
Chris@0 2544 }
Chris@0 2545 };
Chris@0 2546
Chris@0 2547 Element.Methods.ByTag = { };
Chris@0 2548
Chris@0 2549 Object.extend(Element, Element.Methods);
Chris@0 2550
Chris@0 2551 if (!Prototype.BrowserFeatures.ElementExtensions &&
Chris@0 2552 document.createElement('div')['__proto__']) {
Chris@0 2553 window.HTMLElement = { };
Chris@0 2554 window.HTMLElement.prototype = document.createElement('div')['__proto__'];
Chris@0 2555 Prototype.BrowserFeatures.ElementExtensions = true;
Chris@0 2556 }
Chris@0 2557
Chris@0 2558 Element.extend = (function() {
Chris@0 2559 if (Prototype.BrowserFeatures.SpecificElementExtensions)
Chris@0 2560 return Prototype.K;
Chris@0 2561
Chris@0 2562 var Methods = { }, ByTag = Element.Methods.ByTag;
Chris@0 2563
Chris@0 2564 var extend = Object.extend(function(element) {
Chris@0 2565 if (!element || element._extendedByPrototype ||
Chris@0 2566 element.nodeType != 1 || element == window) return element;
Chris@0 2567
Chris@0 2568 var methods = Object.clone(Methods),
Chris@0 2569 tagName = element.tagName.toUpperCase(), property, value;
Chris@0 2570
Chris@0 2571 // extend methods for specific tags
Chris@0 2572 if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
Chris@0 2573
Chris@0 2574 for (property in methods) {
Chris@0 2575 value = methods[property];
Chris@0 2576 if (Object.isFunction(value) && !(property in element))
Chris@0 2577 element[property] = value.methodize();
Chris@0 2578 }
Chris@0 2579
Chris@0 2580 element._extendedByPrototype = Prototype.emptyFunction;
Chris@0 2581 return element;
Chris@0 2582
Chris@0 2583 }, {
Chris@0 2584 refresh: function() {
Chris@0 2585 // extend methods for all tags (Safari doesn't need this)
Chris@0 2586 if (!Prototype.BrowserFeatures.ElementExtensions) {
Chris@0 2587 Object.extend(Methods, Element.Methods);
Chris@0 2588 Object.extend(Methods, Element.Methods.Simulated);
Chris@0 2589 }
Chris@0 2590 }
Chris@0 2591 });
Chris@0 2592
Chris@0 2593 extend.refresh();
Chris@0 2594 return extend;
Chris@0 2595 })();
Chris@0 2596
Chris@0 2597 Element.hasAttribute = function(element, attribute) {
Chris@0 2598 if (element.hasAttribute) return element.hasAttribute(attribute);
Chris@0 2599 return Element.Methods.Simulated.hasAttribute(element, attribute);
Chris@0 2600 };
Chris@0 2601
Chris@0 2602 Element.addMethods = function(methods) {
Chris@0 2603 var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
Chris@0 2604
Chris@0 2605 if (!methods) {
Chris@0 2606 Object.extend(Form, Form.Methods);
Chris@0 2607 Object.extend(Form.Element, Form.Element.Methods);
Chris@0 2608 Object.extend(Element.Methods.ByTag, {
Chris@0 2609 "FORM": Object.clone(Form.Methods),
Chris@0 2610 "INPUT": Object.clone(Form.Element.Methods),
Chris@0 2611 "SELECT": Object.clone(Form.Element.Methods),
Chris@0 2612 "TEXTAREA": Object.clone(Form.Element.Methods)
Chris@0 2613 });
Chris@0 2614 }
Chris@0 2615
Chris@0 2616 if (arguments.length == 2) {
Chris@0 2617 var tagName = methods;
Chris@0 2618 methods = arguments[1];
Chris@0 2619 }
Chris@0 2620
Chris@0 2621 if (!tagName) Object.extend(Element.Methods, methods || { });
Chris@0 2622 else {
Chris@0 2623 if (Object.isArray(tagName)) tagName.each(extend);
Chris@0 2624 else extend(tagName);
Chris@0 2625 }
Chris@0 2626
Chris@0 2627 function extend(tagName) {
Chris@0 2628 tagName = tagName.toUpperCase();
Chris@0 2629 if (!Element.Methods.ByTag[tagName])
Chris@0 2630 Element.Methods.ByTag[tagName] = { };
Chris@0 2631 Object.extend(Element.Methods.ByTag[tagName], methods);
Chris@0 2632 }
Chris@0 2633
Chris@0 2634 function copy(methods, destination, onlyIfAbsent) {
Chris@0 2635 onlyIfAbsent = onlyIfAbsent || false;
Chris@0 2636 for (var property in methods) {
Chris@0 2637 var value = methods[property];
Chris@0 2638 if (!Object.isFunction(value)) continue;
Chris@0 2639 if (!onlyIfAbsent || !(property in destination))
Chris@0 2640 destination[property] = value.methodize();
Chris@0 2641 }
Chris@0 2642 }
Chris@0 2643
Chris@0 2644 function findDOMClass(tagName) {
Chris@0 2645 var klass;
Chris@0 2646 var trans = {
Chris@0 2647 "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
Chris@0 2648 "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
Chris@0 2649 "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
Chris@0 2650 "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
Chris@0 2651 "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
Chris@0 2652 "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
Chris@0 2653 "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
Chris@0 2654 "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
Chris@0 2655 "FrameSet", "IFRAME": "IFrame"
Chris@0 2656 };
Chris@0 2657 if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
Chris@0 2658 if (window[klass]) return window[klass];
Chris@0 2659 klass = 'HTML' + tagName + 'Element';
Chris@0 2660 if (window[klass]) return window[klass];
Chris@0 2661 klass = 'HTML' + tagName.capitalize() + 'Element';
Chris@0 2662 if (window[klass]) return window[klass];
Chris@0 2663
Chris@0 2664 window[klass] = { };
Chris@0 2665 window[klass].prototype = document.createElement(tagName)['__proto__'];
Chris@0 2666 return window[klass];
Chris@0 2667 }
Chris@0 2668
Chris@0 2669 if (F.ElementExtensions) {
Chris@0 2670 copy(Element.Methods, HTMLElement.prototype);
Chris@0 2671 copy(Element.Methods.Simulated, HTMLElement.prototype, true);
Chris@0 2672 }
Chris@0 2673
Chris@0 2674 if (F.SpecificElementExtensions) {
Chris@0 2675 for (var tag in Element.Methods.ByTag) {
Chris@0 2676 var klass = findDOMClass(tag);
Chris@0 2677 if (Object.isUndefined(klass)) continue;
Chris@0 2678 copy(T[tag], klass.prototype);
Chris@0 2679 }
Chris@0 2680 }
Chris@0 2681
Chris@0 2682 Object.extend(Element, Element.Methods);
Chris@0 2683 delete Element.ByTag;
Chris@0 2684
Chris@0 2685 if (Element.extend.refresh) Element.extend.refresh();
Chris@0 2686 Element.cache = { };
Chris@0 2687 };
Chris@0 2688
Chris@0 2689 document.viewport = {
Chris@0 2690 getDimensions: function() {
Chris@0 2691 var dimensions = { }, B = Prototype.Browser;
Chris@0 2692 $w('width height').each(function(d) {
Chris@0 2693 var D = d.capitalize();
Chris@0 2694 if (B.WebKit && !document.evaluate) {
Chris@0 2695 // Safari <3.0 needs self.innerWidth/Height
Chris@0 2696 dimensions[d] = self['inner' + D];
Chris@0 2697 } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) {
Chris@0 2698 // Opera <9.5 needs document.body.clientWidth/Height
Chris@0 2699 dimensions[d] = document.body['client' + D]
Chris@0 2700 } else {
Chris@0 2701 dimensions[d] = document.documentElement['client' + D];
Chris@0 2702 }
Chris@0 2703 });
Chris@0 2704 return dimensions;
Chris@0 2705 },
Chris@0 2706
Chris@0 2707 getWidth: function() {
Chris@0 2708 return this.getDimensions().width;
Chris@0 2709 },
Chris@0 2710
Chris@0 2711 getHeight: function() {
Chris@0 2712 return this.getDimensions().height;
Chris@0 2713 },
Chris@0 2714
Chris@0 2715 getScrollOffsets: function() {
Chris@0 2716 return Element._returnOffset(
Chris@0 2717 window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
Chris@0 2718 window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
Chris@0 2719 }
Chris@0 2720 };
Chris@0 2721 /* Portions of the Selector class are derived from Jack Slocum's DomQuery,
Chris@0 2722 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
Chris@0 2723 * license. Please see http://www.yui-ext.com/ for more information. */
Chris@0 2724
Chris@0 2725 var Selector = Class.create({
Chris@0 2726 initialize: function(expression) {
Chris@0 2727 this.expression = expression.strip();
Chris@0 2728
Chris@0 2729 if (this.shouldUseSelectorsAPI()) {
Chris@0 2730 this.mode = 'selectorsAPI';
Chris@0 2731 } else if (this.shouldUseXPath()) {
Chris@0 2732 this.mode = 'xpath';
Chris@0 2733 this.compileXPathMatcher();
Chris@0 2734 } else {
Chris@0 2735 this.mode = "normal";
Chris@0 2736 this.compileMatcher();
Chris@0 2737 }
Chris@0 2738
Chris@0 2739 },
Chris@0 2740
Chris@0 2741 shouldUseXPath: function() {
Chris@0 2742 if (!Prototype.BrowserFeatures.XPath) return false;
Chris@0 2743
Chris@0 2744 var e = this.expression;
Chris@0 2745
Chris@0 2746 // Safari 3 chokes on :*-of-type and :empty
Chris@0 2747 if (Prototype.Browser.WebKit &&
Chris@0 2748 (e.include("-of-type") || e.include(":empty")))
Chris@0 2749 return false;
Chris@0 2750
Chris@0 2751 // XPath can't do namespaced attributes, nor can it read
Chris@0 2752 // the "checked" property from DOM nodes
Chris@0 2753 if ((/(\[[\w-]*?:|:checked)/).test(e))
Chris@0 2754 return false;
Chris@0 2755
Chris@0 2756 return true;
Chris@0 2757 },
Chris@0 2758
Chris@0 2759 shouldUseSelectorsAPI: function() {
Chris@0 2760 if (!Prototype.BrowserFeatures.SelectorsAPI) return false;
Chris@0 2761
Chris@0 2762 if (!Selector._div) Selector._div = new Element('div');
Chris@0 2763
Chris@0 2764 // Make sure the browser treats the selector as valid. Test on an
Chris@0 2765 // isolated element to minimize cost of this check.
Chris@0 2766 try {
Chris@0 2767 Selector._div.querySelector(this.expression);
Chris@0 2768 } catch(e) {
Chris@0 2769 return false;
Chris@0 2770 }
Chris@0 2771
Chris@0 2772 return true;
Chris@0 2773 },
Chris@0 2774
Chris@0 2775 compileMatcher: function() {
Chris@0 2776 var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
Chris@0 2777 c = Selector.criteria, le, p, m;
Chris@0 2778
Chris@0 2779 if (Selector._cache[e]) {
Chris@0 2780 this.matcher = Selector._cache[e];
Chris@0 2781 return;
Chris@0 2782 }
Chris@0 2783
Chris@0 2784 this.matcher = ["this.matcher = function(root) {",
Chris@0 2785 "var r = root, h = Selector.handlers, c = false, n;"];
Chris@0 2786
Chris@0 2787 while (e && le != e && (/\S/).test(e)) {
Chris@0 2788 le = e;
Chris@0 2789 for (var i in ps) {
Chris@0 2790 p = ps[i];
Chris@0 2791 if (m = e.match(p)) {
Chris@0 2792 this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
Chris@0 2793 new Template(c[i]).evaluate(m));
Chris@0 2794 e = e.replace(m[0], '');
Chris@0 2795 break;
Chris@0 2796 }
Chris@0 2797 }
Chris@0 2798 }
Chris@0 2799
Chris@0 2800 this.matcher.push("return h.unique(n);\n}");
Chris@0 2801 eval(this.matcher.join('\n'));
Chris@0 2802 Selector._cache[this.expression] = this.matcher;
Chris@0 2803 },
Chris@0 2804
Chris@0 2805 compileXPathMatcher: function() {
Chris@0 2806 var e = this.expression, ps = Selector.patterns,
Chris@0 2807 x = Selector.xpath, le, m;
Chris@0 2808
Chris@0 2809 if (Selector._cache[e]) {
Chris@0 2810 this.xpath = Selector._cache[e]; return;
Chris@0 2811 }
Chris@0 2812
Chris@0 2813 this.matcher = ['.//*'];
Chris@0 2814 while (e && le != e && (/\S/).test(e)) {
Chris@0 2815 le = e;
Chris@0 2816 for (var i in ps) {
Chris@0 2817 if (m = e.match(ps[i])) {
Chris@0 2818 this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
Chris@0 2819 new Template(x[i]).evaluate(m));
Chris@0 2820 e = e.replace(m[0], '');
Chris@0 2821 break;
Chris@0 2822 }
Chris@0 2823 }
Chris@0 2824 }
Chris@0 2825
Chris@0 2826 this.xpath = this.matcher.join('');
Chris@0 2827 Selector._cache[this.expression] = this.xpath;
Chris@0 2828 },
Chris@0 2829
Chris@0 2830 findElements: function(root) {
Chris@0 2831 root = root || document;
Chris@0 2832 var e = this.expression, results;
Chris@0 2833
Chris@0 2834 switch (this.mode) {
Chris@0 2835 case 'selectorsAPI':
Chris@0 2836 // querySelectorAll queries document-wide, then filters to descendants
Chris@0 2837 // of the context element. That's not what we want.
Chris@0 2838 // Add an explicit context to the selector if necessary.
Chris@0 2839 if (root !== document) {
Chris@0 2840 var oldId = root.id, id = $(root).identify();
Chris@0 2841 e = "#" + id + " " + e;
Chris@0 2842 }
Chris@0 2843
Chris@0 2844 results = $A(root.querySelectorAll(e)).map(Element.extend);
Chris@0 2845 root.id = oldId;
Chris@0 2846
Chris@0 2847 return results;
Chris@0 2848 case 'xpath':
Chris@0 2849 return document._getElementsByXPath(this.xpath, root);
Chris@0 2850 default:
Chris@0 2851 return this.matcher(root);
Chris@0 2852 }
Chris@0 2853 },
Chris@0 2854
Chris@0 2855 match: function(element) {
Chris@0 2856 this.tokens = [];
Chris@0 2857
Chris@0 2858 var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
Chris@0 2859 var le, p, m;
Chris@0 2860
Chris@0 2861 while (e && le !== e && (/\S/).test(e)) {
Chris@0 2862 le = e;
Chris@0 2863 for (var i in ps) {
Chris@0 2864 p = ps[i];
Chris@0 2865 if (m = e.match(p)) {
Chris@0 2866 // use the Selector.assertions methods unless the selector
Chris@0 2867 // is too complex.
Chris@0 2868 if (as[i]) {
Chris@0 2869 this.tokens.push([i, Object.clone(m)]);
Chris@0 2870 e = e.replace(m[0], '');
Chris@0 2871 } else {
Chris@0 2872 // reluctantly do a document-wide search
Chris@0 2873 // and look for a match in the array
Chris@0 2874 return this.findElements(document).include(element);
Chris@0 2875 }
Chris@0 2876 }
Chris@0 2877 }
Chris@0 2878 }
Chris@0 2879
Chris@0 2880 var match = true, name, matches;
Chris@0 2881 for (var i = 0, token; token = this.tokens[i]; i++) {
Chris@0 2882 name = token[0], matches = token[1];
Chris@0 2883 if (!Selector.assertions[name](element, matches)) {
Chris@0 2884 match = false; break;
Chris@0 2885 }
Chris@0 2886 }
Chris@0 2887
Chris@0 2888 return match;
Chris@0 2889 },
Chris@0 2890
Chris@0 2891 toString: function() {
Chris@0 2892 return this.expression;
Chris@0 2893 },
Chris@0 2894
Chris@0 2895 inspect: function() {
Chris@0 2896 return "#<Selector:" + this.expression.inspect() + ">";
Chris@0 2897 }
Chris@0 2898 });
Chris@0 2899
Chris@0 2900 Object.extend(Selector, {
Chris@0 2901 _cache: { },
Chris@0 2902
Chris@0 2903 xpath: {
Chris@0 2904 descendant: "//*",
Chris@0 2905 child: "/*",
Chris@0 2906 adjacent: "/following-sibling::*[1]",
Chris@0 2907 laterSibling: '/following-sibling::*',
Chris@0 2908 tagName: function(m) {
Chris@0 2909 if (m[1] == '*') return '';
Chris@0 2910 return "[local-name()='" + m[1].toLowerCase() +
Chris@0 2911 "' or local-name()='" + m[1].toUpperCase() + "']";
Chris@0 2912 },
Chris@0 2913 className: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
Chris@0 2914 id: "[@id='#{1}']",
Chris@0 2915 attrPresence: function(m) {
Chris@0 2916 m[1] = m[1].toLowerCase();
Chris@0 2917 return new Template("[@#{1}]").evaluate(m);
Chris@0 2918 },
Chris@0 2919 attr: function(m) {
Chris@0 2920 m[1] = m[1].toLowerCase();
Chris@0 2921 m[3] = m[5] || m[6];
Chris@0 2922 return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
Chris@0 2923 },
Chris@0 2924 pseudo: function(m) {
Chris@0 2925 var h = Selector.xpath.pseudos[m[1]];
Chris@0 2926 if (!h) return '';
Chris@0 2927 if (Object.isFunction(h)) return h(m);
Chris@0 2928 return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
Chris@0 2929 },
Chris@0 2930 operators: {
Chris@0 2931 '=': "[@#{1}='#{3}']",
Chris@0 2932 '!=': "[@#{1}!='#{3}']",
Chris@0 2933 '^=': "[starts-with(@#{1}, '#{3}')]",
Chris@0 2934 '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
Chris@0 2935 '*=': "[contains(@#{1}, '#{3}')]",
Chris@0 2936 '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
Chris@0 2937 '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
Chris@0 2938 },
Chris@0 2939 pseudos: {
Chris@0 2940 'first-child': '[not(preceding-sibling::*)]',
Chris@0 2941 'last-child': '[not(following-sibling::*)]',
Chris@0 2942 'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
Chris@0 2943 'empty': "[count(*) = 0 and (count(text()) = 0)]",
Chris@0 2944 'checked': "[@checked]",
Chris@0 2945 'disabled': "[(@disabled) and (@type!='hidden')]",
Chris@0 2946 'enabled': "[not(@disabled) and (@type!='hidden')]",
Chris@0 2947 'not': function(m) {
Chris@0 2948 var e = m[6], p = Selector.patterns,
Chris@0 2949 x = Selector.xpath, le, v;
Chris@0 2950
Chris@0 2951 var exclusion = [];
Chris@0 2952 while (e && le != e && (/\S/).test(e)) {
Chris@0 2953 le = e;
Chris@0 2954 for (var i in p) {
Chris@0 2955 if (m = e.match(p[i])) {
Chris@0 2956 v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
Chris@0 2957 exclusion.push("(" + v.substring(1, v.length - 1) + ")");
Chris@0 2958 e = e.replace(m[0], '');
Chris@0 2959 break;
Chris@0 2960 }
Chris@0 2961 }
Chris@0 2962 }
Chris@0 2963 return "[not(" + exclusion.join(" and ") + ")]";
Chris@0 2964 },
Chris@0 2965 'nth-child': function(m) {
Chris@0 2966 return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
Chris@0 2967 },
Chris@0 2968 'nth-last-child': function(m) {
Chris@0 2969 return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
Chris@0 2970 },
Chris@0 2971 'nth-of-type': function(m) {
Chris@0 2972 return Selector.xpath.pseudos.nth("position() ", m);
Chris@0 2973 },
Chris@0 2974 'nth-last-of-type': function(m) {
Chris@0 2975 return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
Chris@0 2976 },
Chris@0 2977 'first-of-type': function(m) {
Chris@0 2978 m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
Chris@0 2979 },
Chris@0 2980 'last-of-type': function(m) {
Chris@0 2981 m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
Chris@0 2982 },
Chris@0 2983 'only-of-type': function(m) {
Chris@0 2984 var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
Chris@0 2985 },
Chris@0 2986 nth: function(fragment, m) {
Chris@0 2987 var mm, formula = m[6], predicate;
Chris@0 2988 if (formula == 'even') formula = '2n+0';
Chris@0 2989 if (formula == 'odd') formula = '2n+1';
Chris@0 2990 if (mm = formula.match(/^(\d+)$/)) // digit only
Chris@0 2991 return '[' + fragment + "= " + mm[1] + ']';
Chris@0 2992 if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
Chris@0 2993 if (mm[1] == "-") mm[1] = -1;
Chris@0 2994 var a = mm[1] ? Number(mm[1]) : 1;
Chris@0 2995 var b = mm[2] ? Number(mm[2]) : 0;
Chris@0 2996 predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
Chris@0 2997 "((#{fragment} - #{b}) div #{a} >= 0)]";
Chris@0 2998 return new Template(predicate).evaluate({
Chris@0 2999 fragment: fragment, a: a, b: b });
Chris@0 3000 }
Chris@0 3001 }
Chris@0 3002 }
Chris@0 3003 },
Chris@0 3004
Chris@0 3005 criteria: {
Chris@0 3006 tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
Chris@0 3007 className: 'n = h.className(n, r, "#{1}", c); c = false;',
Chris@0 3008 id: 'n = h.id(n, r, "#{1}", c); c = false;',
Chris@0 3009 attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
Chris@0 3010 attr: function(m) {
Chris@0 3011 m[3] = (m[5] || m[6]);
Chris@0 3012 return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
Chris@0 3013 },
Chris@0 3014 pseudo: function(m) {
Chris@0 3015 if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
Chris@0 3016 return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
Chris@0 3017 },
Chris@0 3018 descendant: 'c = "descendant";',
Chris@0 3019 child: 'c = "child";',
Chris@0 3020 adjacent: 'c = "adjacent";',
Chris@0 3021 laterSibling: 'c = "laterSibling";'
Chris@0 3022 },
Chris@0 3023
Chris@0 3024 patterns: {
Chris@0 3025 // combinators must be listed first
Chris@0 3026 // (and descendant needs to be last combinator)
Chris@0 3027 laterSibling: /^\s*~\s*/,
Chris@0 3028 child: /^\s*>\s*/,
Chris@0 3029 adjacent: /^\s*\+\s*/,
Chris@0 3030 descendant: /^\s/,
Chris@0 3031
Chris@0 3032 // selectors follow
Chris@0 3033 tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
Chris@0 3034 id: /^#([\w\-\*]+)(\b|$)/,
Chris@0 3035 className: /^\.([\w\-\*]+)(\b|$)/,
Chris@0 3036 pseudo:
Chris@0 3037 /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
Chris@0 3038 attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/,
Chris@0 3039 attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
Chris@0 3040 },
Chris@0 3041
Chris@0 3042 // for Selector.match and Element#match
Chris@0 3043 assertions: {
Chris@0 3044 tagName: function(element, matches) {
Chris@0 3045 return matches[1].toUpperCase() == element.tagName.toUpperCase();
Chris@0 3046 },
Chris@0 3047
Chris@0 3048 className: function(element, matches) {
Chris@0 3049 return Element.hasClassName(element, matches[1]);
Chris@0 3050 },
Chris@0 3051
Chris@0 3052 id: function(element, matches) {
Chris@0 3053 return element.id === matches[1];
Chris@0 3054 },
Chris@0 3055
Chris@0 3056 attrPresence: function(element, matches) {
Chris@0 3057 return Element.hasAttribute(element, matches[1]);
Chris@0 3058 },
Chris@0 3059
Chris@0 3060 attr: function(element, matches) {
Chris@0 3061 var nodeValue = Element.readAttribute(element, matches[1]);
Chris@0 3062 return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
Chris@0 3063 }
Chris@0 3064 },
Chris@0 3065
Chris@0 3066 handlers: {
Chris@0 3067 // UTILITY FUNCTIONS
Chris@0 3068 // joins two collections
Chris@0 3069 concat: function(a, b) {
Chris@0 3070 for (var i = 0, node; node = b[i]; i++)
Chris@0 3071 a.push(node);
Chris@0 3072 return a;
Chris@0 3073 },
Chris@0 3074
Chris@0 3075 // marks an array of nodes for counting
Chris@0 3076 mark: function(nodes) {
Chris@0 3077 var _true = Prototype.emptyFunction;
Chris@0 3078 for (var i = 0, node; node = nodes[i]; i++)
Chris@0 3079 node._countedByPrototype = _true;
Chris@0 3080 return nodes;
Chris@0 3081 },
Chris@0 3082
Chris@0 3083 unmark: function(nodes) {
Chris@0 3084 for (var i = 0, node; node = nodes[i]; i++)
Chris@0 3085 node._countedByPrototype = undefined;
Chris@0 3086 return nodes;
Chris@0 3087 },
Chris@0 3088
Chris@0 3089 // mark each child node with its position (for nth calls)
Chris@0 3090 // "ofType" flag indicates whether we're indexing for nth-of-type
Chris@0 3091 // rather than nth-child
Chris@0 3092 index: function(parentNode, reverse, ofType) {
Chris@0 3093 parentNode._countedByPrototype = Prototype.emptyFunction;
Chris@0 3094 if (reverse) {
Chris@0 3095 for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
Chris@0 3096 var node = nodes[i];
Chris@0 3097 if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
Chris@0 3098 }
Chris@0 3099 } else {
Chris@0 3100 for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
Chris@0 3101 if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
Chris@0 3102 }
Chris@0 3103 },
Chris@0 3104
Chris@0 3105 // filters out duplicates and extends all nodes
Chris@0 3106 unique: function(nodes) {
Chris@0 3107 if (nodes.length == 0) return nodes;
Chris@0 3108 var results = [], n;
Chris@0 3109 for (var i = 0, l = nodes.length; i < l; i++)
Chris@0 3110 if (!(n = nodes[i])._countedByPrototype) {
Chris@0 3111 n._countedByPrototype = Prototype.emptyFunction;
Chris@0 3112 results.push(Element.extend(n));
Chris@0 3113 }
Chris@0 3114 return Selector.handlers.unmark(results);
Chris@0 3115 },
Chris@0 3116
Chris@0 3117 // COMBINATOR FUNCTIONS
Chris@0 3118 descendant: function(nodes) {
Chris@0 3119 var h = Selector.handlers;
Chris@0 3120 for (var i = 0, results = [], node; node = nodes[i]; i++)
Chris@0 3121 h.concat(results, node.getElementsByTagName('*'));
Chris@0 3122 return results;
Chris@0 3123 },
Chris@0 3124
Chris@0 3125 child: function(nodes) {
Chris@0 3126 var h = Selector.handlers;
Chris@0 3127 for (var i = 0, results = [], node; node = nodes[i]; i++) {
Chris@0 3128 for (var j = 0, child; child = node.childNodes[j]; j++)
Chris@0 3129 if (child.nodeType == 1 && child.tagName != '!') results.push(child);
Chris@0 3130 }
Chris@0 3131 return results;
Chris@0 3132 },
Chris@0 3133
Chris@0 3134 adjacent: function(nodes) {
Chris@0 3135 for (var i = 0, results = [], node; node = nodes[i]; i++) {
Chris@0 3136 var next = this.nextElementSibling(node);
Chris@0 3137 if (next) results.push(next);
Chris@0 3138 }
Chris@0 3139 return results;
Chris@0 3140 },
Chris@0 3141
Chris@0 3142 laterSibling: function(nodes) {
Chris@0 3143 var h = Selector.handlers;
Chris@0 3144 for (var i = 0, results = [], node; node = nodes[i]; i++)
Chris@0 3145 h.concat(results, Element.nextSiblings(node));
Chris@0 3146 return results;
Chris@0 3147 },
Chris@0 3148
Chris@0 3149 nextElementSibling: function(node) {
Chris@0 3150 while (node = node.nextSibling)
Chris@0 3151 if (node.nodeType == 1) return node;
Chris@0 3152 return null;
Chris@0 3153 },
Chris@0 3154
Chris@0 3155 previousElementSibling: function(node) {
Chris@0 3156 while (node = node.previousSibling)
Chris@0 3157 if (node.nodeType == 1) return node;
Chris@0 3158 return null;
Chris@0 3159 },
Chris@0 3160
Chris@0 3161 // TOKEN FUNCTIONS
Chris@0 3162 tagName: function(nodes, root, tagName, combinator) {
Chris@0 3163 var uTagName = tagName.toUpperCase();
Chris@0 3164 var results = [], h = Selector.handlers;
Chris@0 3165 if (nodes) {
Chris@0 3166 if (combinator) {
Chris@0 3167 // fastlane for ordinary descendant combinators
Chris@0 3168 if (combinator == "descendant") {
Chris@0 3169 for (var i = 0, node; node = nodes[i]; i++)
Chris@0 3170 h.concat(results, node.getElementsByTagName(tagName));
Chris@0 3171 return results;
Chris@0 3172 } else nodes = this[combinator](nodes);
Chris@0 3173 if (tagName == "*") return nodes;
Chris@0 3174 }
Chris@0 3175 for (var i = 0, node; node = nodes[i]; i++)
Chris@0 3176 if (node.tagName.toUpperCase() === uTagName) results.push(node);
Chris@0 3177 return results;
Chris@0 3178 } else return root.getElementsByTagName(tagName);
Chris@0 3179 },
Chris@0 3180
Chris@0 3181 id: function(nodes, root, id, combinator) {
Chris@0 3182 var targetNode = $(id), h = Selector.handlers;
Chris@0 3183 if (!targetNode) return [];
Chris@0 3184 if (!nodes && root == document) return [targetNode];
Chris@0 3185 if (nodes) {
Chris@0 3186 if (combinator) {
Chris@0 3187 if (combinator == 'child') {
Chris@0 3188 for (var i = 0, node; node = nodes[i]; i++)
Chris@0 3189 if (targetNode.parentNode == node) return [targetNode];
Chris@0 3190 } else if (combinator == 'descendant') {
Chris@0 3191 for (var i = 0, node; node = nodes[i]; i++)
Chris@0 3192 if (Element.descendantOf(targetNode, node)) return [targetNode];
Chris@0 3193 } else if (combinator == 'adjacent') {
Chris@0 3194 for (var i = 0, node; node = nodes[i]; i++)
Chris@0 3195 if (Selector.handlers.previousElementSibling(targetNode) == node)
Chris@0 3196 return [targetNode];
Chris@0 3197 } else nodes = h[combinator](nodes);
Chris@0 3198 }
Chris@0 3199 for (var i = 0, node; node = nodes[i]; i++)
Chris@0 3200 if (node == targetNode) return [targetNode];
Chris@0 3201 return [];
Chris@0 3202 }
Chris@0 3203 return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
Chris@0 3204 },
Chris@0 3205
Chris@0 3206 className: function(nodes, root, className, combinator) {
Chris@0 3207 if (nodes && combinator) nodes = this[combinator](nodes);
Chris@0 3208 return Selector.handlers.byClassName(nodes, root, className);
Chris@0 3209 },
Chris@0 3210
Chris@0 3211 byClassName: function(nodes, root, className) {
Chris@0 3212 if (!nodes) nodes = Selector.handlers.descendant([root]);
Chris@0 3213 var needle = ' ' + className + ' ';
Chris@0 3214 for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
Chris@0 3215 nodeClassName = node.className;
Chris@0 3216 if (nodeClassName.length == 0) continue;
Chris@0 3217 if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
Chris@0 3218 results.push(node);
Chris@0 3219 }
Chris@0 3220 return results;
Chris@0 3221 },
Chris@0 3222
Chris@0 3223 attrPresence: function(nodes, root, attr, combinator) {
Chris@0 3224 if (!nodes) nodes = root.getElementsByTagName("*");
Chris@0 3225 if (nodes && combinator) nodes = this[combinator](nodes);
Chris@0 3226 var results = [];
Chris@0 3227 for (var i = 0, node; node = nodes[i]; i++)
Chris@0 3228 if (Element.hasAttribute(node, attr)) results.push(node);
Chris@0 3229 return results;
Chris@0 3230 },
Chris@0 3231
Chris@0 3232 attr: function(nodes, root, attr, value, operator, combinator) {
Chris@0 3233 if (!nodes) nodes = root.getElementsByTagName("*");
Chris@0 3234 if (nodes && combinator) nodes = this[combinator](nodes);
Chris@0 3235 var handler = Selector.operators[operator], results = [];
Chris@0 3236 for (var i = 0, node; node = nodes[i]; i++) {
Chris@0 3237 var nodeValue = Element.readAttribute(node, attr);
Chris@0 3238 if (nodeValue === null) continue;
Chris@0 3239 if (handler(nodeValue, value)) results.push(node);
Chris@0 3240 }
Chris@0 3241 return results;
Chris@0 3242 },
Chris@0 3243
Chris@0 3244 pseudo: function(nodes, name, value, root, combinator) {
Chris@0 3245 if (nodes && combinator) nodes = this[combinator](nodes);
Chris@0 3246 if (!nodes) nodes = root.getElementsByTagName("*");
Chris@0 3247 return Selector.pseudos[name](nodes, value, root);
Chris@0 3248 }
Chris@0 3249 },
Chris@0 3250
Chris@0 3251 pseudos: {
Chris@0 3252 'first-child': function(nodes, value, root) {
Chris@0 3253 for (var i = 0, results = [], node; node = nodes[i]; i++) {
Chris@0 3254 if (Selector.handlers.previousElementSibling(node)) continue;
Chris@0 3255 results.push(node);
Chris@0 3256 }
Chris@0 3257 return results;
Chris@0 3258 },
Chris@0 3259 'last-child': function(nodes, value, root) {
Chris@0 3260 for (var i = 0, results = [], node; node = nodes[i]; i++) {
Chris@0 3261 if (Selector.handlers.nextElementSibling(node)) continue;
Chris@0 3262 results.push(node);
Chris@0 3263 }
Chris@0 3264 return results;
Chris@0 3265 },
Chris@0 3266 'only-child': function(nodes, value, root) {
Chris@0 3267 var h = Selector.handlers;
Chris@0 3268 for (var i = 0, results = [], node; node = nodes[i]; i++)
Chris@0 3269 if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
Chris@0 3270 results.push(node);
Chris@0 3271 return results;
Chris@0 3272 },
Chris@0 3273 'nth-child': function(nodes, formula, root) {
Chris@0 3274 return Selector.pseudos.nth(nodes, formula, root);
Chris@0 3275 },
Chris@0 3276 'nth-last-child': function(nodes, formula, root) {
Chris@0 3277 return Selector.pseudos.nth(nodes, formula, root, true);
Chris@0 3278 },
Chris@0 3279 'nth-of-type': function(nodes, formula, root) {
Chris@0 3280 return Selector.pseudos.nth(nodes, formula, root, false, true);
Chris@0 3281 },
Chris@0 3282 'nth-last-of-type': function(nodes, formula, root) {
Chris@0 3283 return Selector.pseudos.nth(nodes, formula, root, true, true);
Chris@0 3284 },
Chris@0 3285 'first-of-type': function(nodes, formula, root) {
Chris@0 3286 return Selector.pseudos.nth(nodes, "1", root, false, true);
Chris@0 3287 },
Chris@0 3288 'last-of-type': function(nodes, formula, root) {
Chris@0 3289 return Selector.pseudos.nth(nodes, "1", root, true, true);
Chris@0 3290 },
Chris@0 3291 'only-of-type': function(nodes, formula, root) {
Chris@0 3292 var p = Selector.pseudos;
Chris@0 3293 return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
Chris@0 3294 },
Chris@0 3295
Chris@0 3296 // handles the an+b logic
Chris@0 3297 getIndices: function(a, b, total) {
Chris@0 3298 if (a == 0) return b > 0 ? [b] : [];
Chris@0 3299 return $R(1, total).inject([], function(memo, i) {
Chris@0 3300 if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
Chris@0 3301 return memo;
Chris@0 3302 });
Chris@0 3303 },
Chris@0 3304
Chris@0 3305 // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
Chris@0 3306 nth: function(nodes, formula, root, reverse, ofType) {
Chris@0 3307 if (nodes.length == 0) return [];
Chris@0 3308 if (formula == 'even') formula = '2n+0';
Chris@0 3309 if (formula == 'odd') formula = '2n+1';
Chris@0 3310 var h = Selector.handlers, results = [], indexed = [], m;
Chris@0 3311 h.mark(nodes);
Chris@0 3312 for (var i = 0, node; node = nodes[i]; i++) {
Chris@0 3313 if (!node.parentNode._countedByPrototype) {
Chris@0 3314 h.index(node.parentNode, reverse, ofType);
Chris@0 3315 indexed.push(node.parentNode);
Chris@0 3316 }
Chris@0 3317 }
Chris@0 3318 if (formula.match(/^\d+$/)) { // just a number
Chris@0 3319 formula = Number(formula);
Chris@0 3320 for (var i = 0, node; node = nodes[i]; i++)
Chris@0 3321 if (node.nodeIndex == formula) results.push(node);
Chris@0 3322 } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
Chris@0 3323 if (m[1] == "-") m[1] = -1;
Chris@0 3324 var a = m[1] ? Number(m[1]) : 1;
Chris@0 3325 var b = m[2] ? Number(m[2]) : 0;
Chris@0 3326 var indices = Selector.pseudos.getIndices(a, b, nodes.length);
Chris@0 3327 for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
Chris@0 3328 for (var j = 0; j < l; j++)
Chris@0 3329 if (node.nodeIndex == indices[j]) results.push(node);
Chris@0 3330 }
Chris@0 3331 }
Chris@0 3332 h.unmark(nodes);
Chris@0 3333 h.unmark(indexed);
Chris@0 3334 return results;
Chris@0 3335 },
Chris@0 3336
Chris@0 3337 'empty': function(nodes, value, root) {
Chris@0 3338 for (var i = 0, results = [], node; node = nodes[i]; i++) {
Chris@0 3339 // IE treats comments as element nodes
Chris@0 3340 if (node.tagName == '!' || node.firstChild) continue;
Chris@0 3341 results.push(node);
Chris@0 3342 }
Chris@0 3343 return results;
Chris@0 3344 },
Chris@0 3345
Chris@0 3346 'not': function(nodes, selector, root) {
Chris@0 3347 var h = Selector.handlers, selectorType, m;
Chris@0 3348 var exclusions = new Selector(selector).findElements(root);
Chris@0 3349 h.mark(exclusions);
Chris@0 3350 for (var i = 0, results = [], node; node = nodes[i]; i++)
Chris@0 3351 if (!node._countedByPrototype) results.push(node);
Chris@0 3352 h.unmark(exclusions);
Chris@0 3353 return results;
Chris@0 3354 },
Chris@0 3355
Chris@0 3356 'enabled': function(nodes, value, root) {
Chris@0 3357 for (var i = 0, results = [], node; node = nodes[i]; i++)
Chris@0 3358 if (!node.disabled && (!node.type || node.type !== 'hidden'))
Chris@0 3359 results.push(node);
Chris@0 3360 return results;
Chris@0 3361 },
Chris@0 3362
Chris@0 3363 'disabled': function(nodes, value, root) {
Chris@0 3364 for (var i = 0, results = [], node; node = nodes[i]; i++)
Chris@0 3365 if (node.disabled) results.push(node);
Chris@0 3366 return results;
Chris@0 3367 },
Chris@0 3368
Chris@0 3369 'checked': function(nodes, value, root) {
Chris@0 3370 for (var i = 0, results = [], node; node = nodes[i]; i++)
Chris@0 3371 if (node.checked) results.push(node);
Chris@0 3372 return results;
Chris@0 3373 }
Chris@0 3374 },
Chris@0 3375
Chris@0 3376 operators: {
Chris@0 3377 '=': function(nv, v) { return nv == v; },
Chris@0 3378 '!=': function(nv, v) { return nv != v; },
Chris@0 3379 '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
Chris@0 3380 '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
Chris@0 3381 '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
Chris@0 3382 '$=': function(nv, v) { return nv.endsWith(v); },
Chris@0 3383 '*=': function(nv, v) { return nv.include(v); },
Chris@0 3384 '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
Chris@0 3385 '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
Chris@0 3386 '-').include('-' + (v || "").toUpperCase() + '-'); }
Chris@0 3387 },
Chris@0 3388
Chris@0 3389 split: function(expression) {
Chris@0 3390 var expressions = [];
Chris@0 3391 expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
Chris@0 3392 expressions.push(m[1].strip());
Chris@0 3393 });
Chris@0 3394 return expressions;
Chris@0 3395 },
Chris@0 3396
Chris@0 3397 matchElements: function(elements, expression) {
Chris@0 3398 var matches = $$(expression), h = Selector.handlers;
Chris@0 3399 h.mark(matches);
Chris@0 3400 for (var i = 0, results = [], element; element = elements[i]; i++)
Chris@0 3401 if (element._countedByPrototype) results.push(element);
Chris@0 3402 h.unmark(matches);
Chris@0 3403 return results;
Chris@0 3404 },
Chris@0 3405
Chris@0 3406 findElement: function(elements, expression, index) {
Chris@0 3407 if (Object.isNumber(expression)) {
Chris@0 3408 index = expression; expression = false;
Chris@0 3409 }
Chris@0 3410 return Selector.matchElements(elements, expression || '*')[index || 0];
Chris@0 3411 },
Chris@0 3412
Chris@0 3413 findChildElements: function(element, expressions) {
Chris@0 3414 expressions = Selector.split(expressions.join(','));
Chris@0 3415 var results = [], h = Selector.handlers;
Chris@0 3416 for (var i = 0, l = expressions.length, selector; i < l; i++) {
Chris@0 3417 selector = new Selector(expressions[i].strip());
Chris@0 3418 h.concat(results, selector.findElements(element));
Chris@0 3419 }
Chris@0 3420 return (l > 1) ? h.unique(results) : results;
Chris@0 3421 }
Chris@0 3422 });
Chris@0 3423
Chris@0 3424 if (Prototype.Browser.IE) {
Chris@0 3425 Object.extend(Selector.handlers, {
Chris@0 3426 // IE returns comment nodes on getElementsByTagName("*").
Chris@0 3427 // Filter them out.
Chris@0 3428 concat: function(a, b) {
Chris@0 3429 for (var i = 0, node; node = b[i]; i++)
Chris@0 3430 if (node.tagName !== "!") a.push(node);
Chris@0 3431 return a;
Chris@0 3432 },
Chris@0 3433
Chris@0 3434 // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
Chris@0 3435 unmark: function(nodes) {
Chris@0 3436 for (var i = 0, node; node = nodes[i]; i++)
Chris@0 3437 node.removeAttribute('_countedByPrototype');
Chris@0 3438 return nodes;
Chris@0 3439 }
Chris@0 3440 });
Chris@0 3441 }
Chris@0 3442
Chris@0 3443 function $$() {
Chris@0 3444 return Selector.findChildElements(document, $A(arguments));
Chris@0 3445 }
Chris@0 3446 var Form = {
Chris@0 3447 reset: function(form) {
Chris@0 3448 $(form).reset();
Chris@0 3449 return form;
Chris@0 3450 },
Chris@0 3451
Chris@0 3452 serializeElements: function(elements, options) {
Chris@0 3453 if (typeof options != 'object') options = { hash: !!options };
Chris@0 3454 else if (Object.isUndefined(options.hash)) options.hash = true;
Chris@0 3455 var key, value, submitted = false, submit = options.submit;
Chris@0 3456
Chris@0 3457 var data = elements.inject({ }, function(result, element) {
Chris@0 3458 if (!element.disabled && element.name) {
Chris@0 3459 key = element.name; value = $(element).getValue();
Chris@0 3460 if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
Chris@0 3461 submit !== false && (!submit || key == submit) && (submitted = true)))) {
Chris@0 3462 if (key in result) {
Chris@0 3463 // a key is already present; construct an array of values
Chris@0 3464 if (!Object.isArray(result[key])) result[key] = [result[key]];
Chris@0 3465 result[key].push(value);
Chris@0 3466 }
Chris@0 3467 else result[key] = value;
Chris@0 3468 }
Chris@0 3469 }
Chris@0 3470 return result;
Chris@0 3471 });
Chris@0 3472
Chris@0 3473 return options.hash ? data : Object.toQueryString(data);
Chris@0 3474 }
Chris@0 3475 };
Chris@0 3476
Chris@0 3477 Form.Methods = {
Chris@0 3478 serialize: function(form, options) {
Chris@0 3479 return Form.serializeElements(Form.getElements(form), options);
Chris@0 3480 },
Chris@0 3481
Chris@0 3482 getElements: function(form) {
Chris@0 3483 return $A($(form).getElementsByTagName('*')).inject([],
Chris@0 3484 function(elements, child) {
Chris@0 3485 if (Form.Element.Serializers[child.tagName.toLowerCase()])
Chris@0 3486 elements.push(Element.extend(child));
Chris@0 3487 return elements;
Chris@0 3488 }
Chris@0 3489 );
Chris@0 3490 },
Chris@0 3491
Chris@0 3492 getInputs: function(form, typeName, name) {
Chris@0 3493 form = $(form);
Chris@0 3494 var inputs = form.getElementsByTagName('input');
Chris@0 3495
Chris@0 3496 if (!typeName && !name) return $A(inputs).map(Element.extend);
Chris@0 3497
Chris@0 3498 for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
Chris@0 3499 var input = inputs[i];
Chris@0 3500 if ((typeName && input.type != typeName) || (name && input.name != name))
Chris@0 3501 continue;
Chris@0 3502 matchingInputs.push(Element.extend(input));
Chris@0 3503 }
Chris@0 3504
Chris@0 3505 return matchingInputs;
Chris@0 3506 },
Chris@0 3507
Chris@0 3508 disable: function(form) {
Chris@0 3509 form = $(form);
Chris@0 3510 Form.getElements(form).invoke('disable');
Chris@0 3511 return form;
Chris@0 3512 },
Chris@0 3513
Chris@0 3514 enable: function(form) {
Chris@0 3515 form = $(form);
Chris@0 3516 Form.getElements(form).invoke('enable');
Chris@0 3517 return form;
Chris@0 3518 },
Chris@0 3519
Chris@0 3520 findFirstElement: function(form) {
Chris@0 3521 var elements = $(form).getElements().findAll(function(element) {
Chris@0 3522 return 'hidden' != element.type && !element.disabled;
Chris@0 3523 });
Chris@0 3524 var firstByIndex = elements.findAll(function(element) {
Chris@0 3525 return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
Chris@0 3526 }).sortBy(function(element) { return element.tabIndex }).first();
Chris@0 3527
Chris@0 3528 return firstByIndex ? firstByIndex : elements.find(function(element) {
Chris@0 3529 return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
Chris@0 3530 });
Chris@0 3531 },
Chris@0 3532
Chris@0 3533 focusFirstElement: function(form) {
Chris@0 3534 form = $(form);
Chris@0 3535 form.findFirstElement().activate();
Chris@0 3536 return form;
Chris@0 3537 },
Chris@0 3538
Chris@0 3539 request: function(form, options) {
Chris@0 3540 form = $(form), options = Object.clone(options || { });
Chris@0 3541
Chris@0 3542 var params = options.parameters, action = form.readAttribute('action') || '';
Chris@0 3543 if (action.blank()) action = window.location.href;
Chris@0 3544 options.parameters = form.serialize(true);
Chris@0 3545
Chris@0 3546 if (params) {
Chris@0 3547 if (Object.isString(params)) params = params.toQueryParams();
Chris@0 3548 Object.extend(options.parameters, params);
Chris@0 3549 }
Chris@0 3550
Chris@0 3551 if (form.hasAttribute('method') && !options.method)
Chris@0 3552 options.method = form.method;
Chris@0 3553
Chris@0 3554 return new Ajax.Request(action, options);
Chris@0 3555 }
Chris@0 3556 };
Chris@0 3557
Chris@0 3558 /*--------------------------------------------------------------------------*/
Chris@0 3559
Chris@0 3560 Form.Element = {
Chris@0 3561 focus: function(element) {
Chris@0 3562 $(element).focus();
Chris@0 3563 return element;
Chris@0 3564 },
Chris@0 3565
Chris@0 3566 select: function(element) {
Chris@0 3567 $(element).select();
Chris@0 3568 return element;
Chris@0 3569 }
Chris@0 3570 };
Chris@0 3571
Chris@0 3572 Form.Element.Methods = {
Chris@0 3573 serialize: function(element) {
Chris@0 3574 element = $(element);
Chris@0 3575 if (!element.disabled && element.name) {
Chris@0 3576 var value = element.getValue();
Chris@0 3577 if (value != undefined) {
Chris@0 3578 var pair = { };
Chris@0 3579 pair[element.name] = value;
Chris@0 3580 return Object.toQueryString(pair);
Chris@0 3581 }
Chris@0 3582 }
Chris@0 3583 return '';
Chris@0 3584 },
Chris@0 3585
Chris@0 3586 getValue: function(element) {
Chris@0 3587 element = $(element);
Chris@0 3588 var method = element.tagName.toLowerCase();
Chris@0 3589 return Form.Element.Serializers[method](element);
Chris@0 3590 },
Chris@0 3591
Chris@0 3592 setValue: function(element, value) {
Chris@0 3593 element = $(element);
Chris@0 3594 var method = element.tagName.toLowerCase();
Chris@0 3595 Form.Element.Serializers[method](element, value);
Chris@0 3596 return element;
Chris@0 3597 },
Chris@0 3598
Chris@0 3599 clear: function(element) {
Chris@0 3600 $(element).value = '';
Chris@0 3601 return element;
Chris@0 3602 },
Chris@0 3603
Chris@0 3604 present: function(element) {
Chris@0 3605 return $(element).value != '';
Chris@0 3606 },
Chris@0 3607
Chris@0 3608 activate: function(element) {
Chris@0 3609 element = $(element);
Chris@0 3610 try {
Chris@0 3611 element.focus();
Chris@0 3612 if (element.select && (element.tagName.toLowerCase() != 'input' ||
Chris@0 3613 !['button', 'reset', 'submit'].include(element.type)))
Chris@0 3614 element.select();
Chris@0 3615 } catch (e) { }
Chris@0 3616 return element;
Chris@0 3617 },
Chris@0 3618
Chris@0 3619 disable: function(element) {
Chris@0 3620 element = $(element);
Chris@0 3621 element.disabled = true;
Chris@0 3622 return element;
Chris@0 3623 },
Chris@0 3624
Chris@0 3625 enable: function(element) {
Chris@0 3626 element = $(element);
Chris@0 3627 element.disabled = false;
Chris@0 3628 return element;
Chris@0 3629 }
Chris@0 3630 };
Chris@0 3631
Chris@0 3632 /*--------------------------------------------------------------------------*/
Chris@0 3633
Chris@0 3634 var Field = Form.Element;
Chris@0 3635 var $F = Form.Element.Methods.getValue;
Chris@0 3636
Chris@0 3637 /*--------------------------------------------------------------------------*/
Chris@0 3638
Chris@0 3639 Form.Element.Serializers = {
Chris@0 3640 input: function(element, value) {
Chris@0 3641 switch (element.type.toLowerCase()) {
Chris@0 3642 case 'checkbox':
Chris@0 3643 case 'radio':
Chris@0 3644 return Form.Element.Serializers.inputSelector(element, value);
Chris@0 3645 default:
Chris@0 3646 return Form.Element.Serializers.textarea(element, value);
Chris@0 3647 }
Chris@0 3648 },
Chris@0 3649
Chris@0 3650 inputSelector: function(element, value) {
Chris@0 3651 if (Object.isUndefined(value)) return element.checked ? element.value : null;
Chris@0 3652 else element.checked = !!value;
Chris@0 3653 },
Chris@0 3654
Chris@0 3655 textarea: function(element, value) {
Chris@0 3656 if (Object.isUndefined(value)) return element.value;
Chris@0 3657 else element.value = value;
Chris@0 3658 },
Chris@0 3659
Chris@0 3660 select: function(element, value) {
Chris@0 3661 if (Object.isUndefined(value))
Chris@0 3662 return this[element.type == 'select-one' ?
Chris@0 3663 'selectOne' : 'selectMany'](element);
Chris@0 3664 else {
Chris@0 3665 var opt, currentValue, single = !Object.isArray(value);
Chris@0 3666 for (var i = 0, length = element.length; i < length; i++) {
Chris@0 3667 opt = element.options[i];
Chris@0 3668 currentValue = this.optionValue(opt);
Chris@0 3669 if (single) {
Chris@0 3670 if (currentValue == value) {
Chris@0 3671 opt.selected = true;
Chris@0 3672 return;
Chris@0 3673 }
Chris@0 3674 }
Chris@0 3675 else opt.selected = value.include(currentValue);
Chris@0 3676 }
Chris@0 3677 }
Chris@0 3678 },
Chris@0 3679
Chris@0 3680 selectOne: function(element) {
Chris@0 3681 var index = element.selectedIndex;
Chris@0 3682 return index >= 0 ? this.optionValue(element.options[index]) : null;
Chris@0 3683 },
Chris@0 3684
Chris@0 3685 selectMany: function(element) {
Chris@0 3686 var values, length = element.length;
Chris@0 3687 if (!length) return null;
Chris@0 3688
Chris@0 3689 for (var i = 0, values = []; i < length; i++) {
Chris@0 3690 var opt = element.options[i];
Chris@0 3691 if (opt.selected) values.push(this.optionValue(opt));
Chris@0 3692 }
Chris@0 3693 return values;
Chris@0 3694 },
Chris@0 3695
Chris@0 3696 optionValue: function(opt) {
Chris@0 3697 // extend element because hasAttribute may not be native
Chris@0 3698 return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
Chris@0 3699 }
Chris@0 3700 };
Chris@0 3701
Chris@0 3702 /*--------------------------------------------------------------------------*/
Chris@0 3703
Chris@0 3704 Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
Chris@0 3705 initialize: function($super, element, frequency, callback) {
Chris@0 3706 $super(callback, frequency);
Chris@0 3707 this.element = $(element);
Chris@0 3708 this.lastValue = this.getValue();
Chris@0 3709 },
Chris@0 3710
Chris@0 3711 execute: function() {
Chris@0 3712 var value = this.getValue();
Chris@0 3713 if (Object.isString(this.lastValue) && Object.isString(value) ?
Chris@0 3714 this.lastValue != value : String(this.lastValue) != String(value)) {
Chris@0 3715 this.callback(this.element, value);
Chris@0 3716 this.lastValue = value;
Chris@0 3717 }
Chris@0 3718 }
Chris@0 3719 });
Chris@0 3720
Chris@0 3721 Form.Element.Observer = Class.create(Abstract.TimedObserver, {
Chris@0 3722 getValue: function() {
Chris@0 3723 return Form.Element.getValue(this.element);
Chris@0 3724 }
Chris@0 3725 });
Chris@0 3726
Chris@0 3727 Form.Observer = Class.create(Abstract.TimedObserver, {
Chris@0 3728 getValue: function() {
Chris@0 3729 return Form.serialize(this.element);
Chris@0 3730 }
Chris@0 3731 });
Chris@0 3732
Chris@0 3733 /*--------------------------------------------------------------------------*/
Chris@0 3734
Chris@0 3735 Abstract.EventObserver = Class.create({
Chris@0 3736 initialize: function(element, callback) {
Chris@0 3737 this.element = $(element);
Chris@0 3738 this.callback = callback;
Chris@0 3739
Chris@0 3740 this.lastValue = this.getValue();
Chris@0 3741 if (this.element.tagName.toLowerCase() == 'form')
Chris@0 3742 this.registerFormCallbacks();
Chris@0 3743 else
Chris@0 3744 this.registerCallback(this.element);
Chris@0 3745 },
Chris@0 3746
Chris@0 3747 onElementEvent: function() {
Chris@0 3748 var value = this.getValue();
Chris@0 3749 if (this.lastValue != value) {
Chris@0 3750 this.callback(this.element, value);
Chris@0 3751 this.lastValue = value;
Chris@0 3752 }
Chris@0 3753 },
Chris@0 3754
Chris@0 3755 registerFormCallbacks: function() {
Chris@0 3756 Form.getElements(this.element).each(this.registerCallback, this);
Chris@0 3757 },
Chris@0 3758
Chris@0 3759 registerCallback: function(element) {
Chris@0 3760 if (element.type) {
Chris@0 3761 switch (element.type.toLowerCase()) {
Chris@0 3762 case 'checkbox':
Chris@0 3763 case 'radio':
Chris@0 3764 Event.observe(element, 'click', this.onElementEvent.bind(this));
Chris@0 3765 break;
Chris@0 3766 default:
Chris@0 3767 Event.observe(element, 'change', this.onElementEvent.bind(this));
Chris@0 3768 break;
Chris@0 3769 }
Chris@0 3770 }
Chris@0 3771 }
Chris@0 3772 });
Chris@0 3773
Chris@0 3774 Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
Chris@0 3775 getValue: function() {
Chris@0 3776 return Form.Element.getValue(this.element);
Chris@0 3777 }
Chris@0 3778 });
Chris@0 3779
Chris@0 3780 Form.EventObserver = Class.create(Abstract.EventObserver, {
Chris@0 3781 getValue: function() {
Chris@0 3782 return Form.serialize(this.element);
Chris@0 3783 }
Chris@0 3784 });
Chris@0 3785 if (!window.Event) var Event = { };
Chris@0 3786
Chris@0 3787 Object.extend(Event, {
Chris@0 3788 KEY_BACKSPACE: 8,
Chris@0 3789 KEY_TAB: 9,
Chris@0 3790 KEY_RETURN: 13,
Chris@0 3791 KEY_ESC: 27,
Chris@0 3792 KEY_LEFT: 37,
Chris@0 3793 KEY_UP: 38,
Chris@0 3794 KEY_RIGHT: 39,
Chris@0 3795 KEY_DOWN: 40,
Chris@0 3796 KEY_DELETE: 46,
Chris@0 3797 KEY_HOME: 36,
Chris@0 3798 KEY_END: 35,
Chris@0 3799 KEY_PAGEUP: 33,
Chris@0 3800 KEY_PAGEDOWN: 34,
Chris@0 3801 KEY_INSERT: 45,
Chris@0 3802
Chris@0 3803 cache: { },
Chris@0 3804
Chris@0 3805 relatedTarget: function(event) {
Chris@0 3806 var element;
Chris@0 3807 switch(event.type) {
Chris@0 3808 case 'mouseover': element = event.fromElement; break;
Chris@0 3809 case 'mouseout': element = event.toElement; break;
Chris@0 3810 default: return null;
Chris@0 3811 }
Chris@0 3812 return Element.extend(element);
Chris@0 3813 }
Chris@0 3814 });
Chris@0 3815
Chris@0 3816 Event.Methods = (function() {
Chris@0 3817 var isButton;
Chris@0 3818
Chris@0 3819 if (Prototype.Browser.IE) {
Chris@0 3820 var buttonMap = { 0: 1, 1: 4, 2: 2 };
Chris@0 3821 isButton = function(event, code) {
Chris@0 3822 return event.button == buttonMap[code];
Chris@0 3823 };
Chris@0 3824
Chris@0 3825 } else if (Prototype.Browser.WebKit) {
Chris@0 3826 isButton = function(event, code) {
Chris@0 3827 switch (code) {
Chris@0 3828 case 0: return event.which == 1 && !event.metaKey;
Chris@0 3829 case 1: return event.which == 1 && event.metaKey;
Chris@0 3830 default: return false;
Chris@0 3831 }
Chris@0 3832 };
Chris@0 3833
Chris@0 3834 } else {
Chris@0 3835 isButton = function(event, code) {
Chris@0 3836 return event.which ? (event.which === code + 1) : (event.button === code);
Chris@0 3837 };
Chris@0 3838 }
Chris@0 3839
Chris@0 3840 return {
Chris@0 3841 isLeftClick: function(event) { return isButton(event, 0) },
Chris@0 3842 isMiddleClick: function(event) { return isButton(event, 1) },
Chris@0 3843 isRightClick: function(event) { return isButton(event, 2) },
Chris@0 3844
Chris@0 3845 element: function(event) {
Chris@0 3846 event = Event.extend(event);
Chris@0 3847
Chris@0 3848 var node = event.target,
Chris@0 3849 type = event.type,
Chris@0 3850 currentTarget = event.currentTarget;
Chris@0 3851
Chris@0 3852 if (currentTarget && currentTarget.tagName) {
Chris@0 3853 // Firefox screws up the "click" event when moving between radio buttons
Chris@0 3854 // via arrow keys. It also screws up the "load" and "error" events on images,
Chris@0 3855 // reporting the document as the target instead of the original image.
Chris@0 3856 if (type === 'load' || type === 'error' ||
Chris@0 3857 (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
Chris@0 3858 && currentTarget.type === 'radio'))
Chris@0 3859 node = currentTarget;
Chris@0 3860 }
Chris@0 3861 if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;
Chris@0 3862 return Element.extend(node);
Chris@0 3863 },
Chris@0 3864
Chris@0 3865 findElement: function(event, expression) {
Chris@0 3866 var element = Event.element(event);
Chris@0 3867 if (!expression) return element;
Chris@0 3868 var elements = [element].concat(element.ancestors());
Chris@0 3869 return Selector.findElement(elements, expression, 0);
Chris@0 3870 },
Chris@0 3871
Chris@0 3872 pointer: function(event) {
Chris@0 3873 var docElement = document.documentElement,
Chris@0 3874 body = document.body || { scrollLeft: 0, scrollTop: 0 };
Chris@0 3875 return {
Chris@0 3876 x: event.pageX || (event.clientX +
Chris@0 3877 (docElement.scrollLeft || body.scrollLeft) -
Chris@0 3878 (docElement.clientLeft || 0)),
Chris@0 3879 y: event.pageY || (event.clientY +
Chris@0 3880 (docElement.scrollTop || body.scrollTop) -
Chris@0 3881 (docElement.clientTop || 0))
Chris@0 3882 };
Chris@0 3883 },
Chris@0 3884
Chris@0 3885 pointerX: function(event) { return Event.pointer(event).x },
Chris@0 3886 pointerY: function(event) { return Event.pointer(event).y },
Chris@0 3887
Chris@0 3888 stop: function(event) {
Chris@0 3889 Event.extend(event);
Chris@0 3890 event.preventDefault();
Chris@0 3891 event.stopPropagation();
Chris@0 3892 event.stopped = true;
Chris@0 3893 }
Chris@0 3894 };
Chris@0 3895 })();
Chris@0 3896
Chris@0 3897 Event.extend = (function() {
Chris@0 3898 var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
Chris@0 3899 m[name] = Event.Methods[name].methodize();
Chris@0 3900 return m;
Chris@0 3901 });
Chris@0 3902
Chris@0 3903 if (Prototype.Browser.IE) {
Chris@0 3904 Object.extend(methods, {
Chris@0 3905 stopPropagation: function() { this.cancelBubble = true },
Chris@0 3906 preventDefault: function() { this.returnValue = false },
Chris@0 3907 inspect: function() { return "[object Event]" }
Chris@0 3908 });
Chris@0 3909
Chris@0 3910 return function(event) {
Chris@0 3911 if (!event) return false;
Chris@0 3912 if (event._extendedByPrototype) return event;
Chris@0 3913
Chris@0 3914 event._extendedByPrototype = Prototype.emptyFunction;
Chris@0 3915 var pointer = Event.pointer(event);
Chris@0 3916 Object.extend(event, {
Chris@0 3917 target: event.srcElement,
Chris@0 3918 relatedTarget: Event.relatedTarget(event),
Chris@0 3919 pageX: pointer.x,
Chris@0 3920 pageY: pointer.y
Chris@0 3921 });
Chris@0 3922 return Object.extend(event, methods);
Chris@0 3923 };
Chris@0 3924
Chris@0 3925 } else {
Chris@0 3926 Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__'];
Chris@0 3927 Object.extend(Event.prototype, methods);
Chris@0 3928 return Prototype.K;
Chris@0 3929 }
Chris@0 3930 })();
Chris@0 3931
Chris@0 3932 Object.extend(Event, (function() {
Chris@0 3933 var cache = Event.cache;
Chris@0 3934
Chris@0 3935 function getEventID(element) {
Chris@0 3936 if (element._prototypeEventID) return element._prototypeEventID[0];
Chris@0 3937 arguments.callee.id = arguments.callee.id || 1;
Chris@0 3938 return element._prototypeEventID = [++arguments.callee.id];
Chris@0 3939 }
Chris@0 3940
Chris@0 3941 function getDOMEventName(eventName) {
Chris@0 3942 if (eventName && eventName.include(':')) return "dataavailable";
Chris@0 3943 return eventName;
Chris@0 3944 }
Chris@0 3945
Chris@0 3946 function getCacheForID(id) {
Chris@0 3947 return cache[id] = cache[id] || { };
Chris@0 3948 }
Chris@0 3949
Chris@0 3950 function getWrappersForEventName(id, eventName) {
Chris@0 3951 var c = getCacheForID(id);
Chris@0 3952 return c[eventName] = c[eventName] || [];
Chris@0 3953 }
Chris@0 3954
Chris@0 3955 function createWrapper(element, eventName, handler) {
Chris@0 3956 var id = getEventID(element);
Chris@0 3957 var c = getWrappersForEventName(id, eventName);
Chris@0 3958 if (c.pluck("handler").include(handler)) return false;
Chris@0 3959
Chris@0 3960 var wrapper = function(event) {
Chris@0 3961 if (!Event || !Event.extend ||
Chris@0 3962 (event.eventName && event.eventName != eventName))
Chris@0 3963 return false;
Chris@0 3964
Chris@0 3965 Event.extend(event);
Chris@0 3966 handler.call(element, event);
Chris@0 3967 };
Chris@0 3968
Chris@0 3969 wrapper.handler = handler;
Chris@0 3970 c.push(wrapper);
Chris@0 3971 return wrapper;
Chris@0 3972 }
Chris@0 3973
Chris@0 3974 function findWrapper(id, eventName, handler) {
Chris@0 3975 var c = getWrappersForEventName(id, eventName);
Chris@0 3976 return c.find(function(wrapper) { return wrapper.handler == handler });
Chris@0 3977 }
Chris@0 3978
Chris@0 3979 function destroyWrapper(id, eventName, handler) {
Chris@0 3980 var c = getCacheForID(id);
Chris@0 3981 if (!c[eventName]) return false;
Chris@0 3982 c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
Chris@0 3983 }
Chris@0 3984
Chris@0 3985 function destroyCache() {
Chris@0 3986 for (var id in cache)
Chris@0 3987 for (var eventName in cache[id])
Chris@0 3988 cache[id][eventName] = null;
Chris@0 3989 }
Chris@0 3990
Chris@0 3991
Chris@0 3992 // Internet Explorer needs to remove event handlers on page unload
Chris@0 3993 // in order to avoid memory leaks.
Chris@0 3994 if (window.attachEvent) {
Chris@0 3995 window.attachEvent("onunload", destroyCache);
Chris@0 3996 }
Chris@0 3997
Chris@0 3998 // Safari has a dummy event handler on page unload so that it won't
Chris@0 3999 // use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
Chris@0 4000 // object when page is returned to via the back button using its bfcache.
Chris@0 4001 if (Prototype.Browser.WebKit) {
Chris@0 4002 window.addEventListener('unload', Prototype.emptyFunction, false);
Chris@0 4003 }
Chris@0 4004
Chris@0 4005 return {
Chris@0 4006 observe: function(element, eventName, handler) {
Chris@0 4007 element = $(element);
Chris@0 4008 var name = getDOMEventName(eventName);
Chris@0 4009
Chris@0 4010 var wrapper = createWrapper(element, eventName, handler);
Chris@0 4011 if (!wrapper) return element;
Chris@0 4012
Chris@0 4013 if (element.addEventListener) {
Chris@0 4014 element.addEventListener(name, wrapper, false);
Chris@0 4015 } else {
Chris@0 4016 element.attachEvent("on" + name, wrapper);
Chris@0 4017 }
Chris@0 4018
Chris@0 4019 return element;
Chris@0 4020 },
Chris@0 4021
Chris@0 4022 stopObserving: function(element, eventName, handler) {
Chris@0 4023 element = $(element);
Chris@0 4024 var id = getEventID(element), name = getDOMEventName(eventName);
Chris@0 4025
Chris@0 4026 if (!handler && eventName) {
Chris@0 4027 getWrappersForEventName(id, eventName).each(function(wrapper) {
Chris@0 4028 element.stopObserving(eventName, wrapper.handler);
Chris@0 4029 });
Chris@0 4030 return element;
Chris@0 4031
Chris@0 4032 } else if (!eventName) {
Chris@0 4033 Object.keys(getCacheForID(id)).each(function(eventName) {
Chris@0 4034 element.stopObserving(eventName);
Chris@0 4035 });
Chris@0 4036 return element;
Chris@0 4037 }
Chris@0 4038
Chris@0 4039 var wrapper = findWrapper(id, eventName, handler);
Chris@0 4040 if (!wrapper) return element;
Chris@0 4041
Chris@0 4042 if (element.removeEventListener) {
Chris@0 4043 element.removeEventListener(name, wrapper, false);
Chris@0 4044 } else {
Chris@0 4045 element.detachEvent("on" + name, wrapper);
Chris@0 4046 }
Chris@0 4047
Chris@0 4048 destroyWrapper(id, eventName, handler);
Chris@0 4049
Chris@0 4050 return element;
Chris@0 4051 },
Chris@0 4052
Chris@0 4053 fire: function(element, eventName, memo) {
Chris@0 4054 element = $(element);
Chris@0 4055 if (element == document && document.createEvent && !element.dispatchEvent)
Chris@0 4056 element = document.documentElement;
Chris@0 4057
Chris@0 4058 var event;
Chris@0 4059 if (document.createEvent) {
Chris@0 4060 event = document.createEvent("HTMLEvents");
Chris@0 4061 event.initEvent("dataavailable", true, true);
Chris@0 4062 } else {
Chris@0 4063 event = document.createEventObject();
Chris@0 4064 event.eventType = "ondataavailable";
Chris@0 4065 }
Chris@0 4066
Chris@0 4067 event.eventName = eventName;
Chris@0 4068 event.memo = memo || { };
Chris@0 4069
Chris@0 4070 if (document.createEvent) {
Chris@0 4071 element.dispatchEvent(event);
Chris@0 4072 } else {
Chris@0 4073 element.fireEvent(event.eventType, event);
Chris@0 4074 }
Chris@0 4075
Chris@0 4076 return Event.extend(event);
Chris@0 4077 }
Chris@0 4078 };
Chris@0 4079 })());
Chris@0 4080
Chris@0 4081 Object.extend(Event, Event.Methods);
Chris@0 4082
Chris@0 4083 Element.addMethods({
Chris@0 4084 fire: Event.fire,
Chris@0 4085 observe: Event.observe,
Chris@0 4086 stopObserving: Event.stopObserving
Chris@0 4087 });
Chris@0 4088
Chris@0 4089 Object.extend(document, {
Chris@0 4090 fire: Element.Methods.fire.methodize(),
Chris@0 4091 observe: Element.Methods.observe.methodize(),
Chris@0 4092 stopObserving: Element.Methods.stopObserving.methodize(),
Chris@0 4093 loaded: false
Chris@0 4094 });
Chris@0 4095
Chris@0 4096 (function() {
Chris@0 4097 /* Support for the DOMContentLoaded event is based on work by Dan Webb,
Chris@0 4098 Matthias Miller, Dean Edwards and John Resig. */
Chris@0 4099
Chris@0 4100 var timer;
Chris@0 4101
Chris@0 4102 function fireContentLoadedEvent() {
Chris@0 4103 if (document.loaded) return;
Chris@0 4104 if (timer) window.clearInterval(timer);
Chris@0 4105 document.fire("dom:loaded");
Chris@0 4106 document.loaded = true;
Chris@0 4107 }
Chris@0 4108
Chris@0 4109 if (document.addEventListener) {
Chris@0 4110 if (Prototype.Browser.WebKit) {
Chris@0 4111 timer = window.setInterval(function() {
Chris@0 4112 if (/loaded|complete/.test(document.readyState))
Chris@0 4113 fireContentLoadedEvent();
Chris@0 4114 }, 0);
Chris@0 4115
Chris@0 4116 Event.observe(window, "load", fireContentLoadedEvent);
Chris@0 4117
Chris@0 4118 } else {
Chris@0 4119 document.addEventListener("DOMContentLoaded",
Chris@0 4120 fireContentLoadedEvent, false);
Chris@0 4121 }
Chris@0 4122
Chris@0 4123 } else {
Chris@0 4124 document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
Chris@0 4125 $("__onDOMContentLoaded").onreadystatechange = function() {
Chris@0 4126 if (this.readyState == "complete") {
Chris@0 4127 this.onreadystatechange = null;
Chris@0 4128 fireContentLoadedEvent();
Chris@0 4129 }
Chris@0 4130 };
Chris@0 4131 }
Chris@0 4132 })();
Chris@0 4133 /*------------------------------- DEPRECATED -------------------------------*/
Chris@0 4134
Chris@0 4135 Hash.toQueryString = Object.toQueryString;
Chris@0 4136
Chris@0 4137 var Toggle = { display: Element.toggle };
Chris@0 4138
Chris@0 4139 Element.Methods.childOf = Element.Methods.descendantOf;
Chris@0 4140
Chris@0 4141 var Insertion = {
Chris@0 4142 Before: function(element, content) {
Chris@0 4143 return Element.insert(element, {before:content});
Chris@0 4144 },
Chris@0 4145
Chris@0 4146 Top: function(element, content) {
Chris@0 4147 return Element.insert(element, {top:content});
Chris@0 4148 },
Chris@0 4149
Chris@0 4150 Bottom: function(element, content) {
Chris@0 4151 return Element.insert(element, {bottom:content});
Chris@0 4152 },
Chris@0 4153
Chris@0 4154 After: function(element, content) {
Chris@0 4155 return Element.insert(element, {after:content});
Chris@0 4156 }
Chris@0 4157 };
Chris@0 4158
Chris@0 4159 var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
Chris@0 4160
Chris@0 4161 // This should be moved to script.aculo.us; notice the deprecated methods
Chris@0 4162 // further below, that map to the newer Element methods.
Chris@0 4163 var Position = {
Chris@0 4164 // set to true if needed, warning: firefox performance problems
Chris@0 4165 // NOT neeeded for page scrolling, only if draggable contained in
Chris@0 4166 // scrollable elements
Chris@0 4167 includeScrollOffsets: false,
Chris@0 4168
Chris@0 4169 // must be called before calling withinIncludingScrolloffset, every time the
Chris@0 4170 // page is scrolled
Chris@0 4171 prepare: function() {
Chris@0 4172 this.deltaX = window.pageXOffset
Chris@0 4173 || document.documentElement.scrollLeft
Chris@0 4174 || document.body.scrollLeft
Chris@0 4175 || 0;
Chris@0 4176 this.deltaY = window.pageYOffset
Chris@0 4177 || document.documentElement.scrollTop
Chris@0 4178 || document.body.scrollTop
Chris@0 4179 || 0;
Chris@0 4180 },
Chris@0 4181
Chris@0 4182 // caches x/y coordinate pair to use with overlap
Chris@0 4183 within: function(element, x, y) {
Chris@0 4184 if (this.includeScrollOffsets)
Chris@0 4185 return this.withinIncludingScrolloffsets(element, x, y);
Chris@0 4186 this.xcomp = x;
Chris@0 4187 this.ycomp = y;
Chris@0 4188 this.offset = Element.cumulativeOffset(element);
Chris@0 4189
Chris@0 4190 return (y >= this.offset[1] &&
Chris@0 4191 y < this.offset[1] + element.offsetHeight &&
Chris@0 4192 x >= this.offset[0] &&
Chris@0 4193 x < this.offset[0] + element.offsetWidth);
Chris@0 4194 },
Chris@0 4195
Chris@0 4196 withinIncludingScrolloffsets: function(element, x, y) {
Chris@0 4197 var offsetcache = Element.cumulativeScrollOffset(element);
Chris@0 4198
Chris@0 4199 this.xcomp = x + offsetcache[0] - this.deltaX;
Chris@0 4200 this.ycomp = y + offsetcache[1] - this.deltaY;
Chris@0 4201 this.offset = Element.cumulativeOffset(element);
Chris@0 4202
Chris@0 4203 return (this.ycomp >= this.offset[1] &&
Chris@0 4204 this.ycomp < this.offset[1] + element.offsetHeight &&
Chris@0 4205 this.xcomp >= this.offset[0] &&
Chris@0 4206 this.xcomp < this.offset[0] + element.offsetWidth);
Chris@0 4207 },
Chris@0 4208
Chris@0 4209 // within must be called directly before
Chris@0 4210 overlap: function(mode, element) {
Chris@0 4211 if (!mode) return 0;
Chris@0 4212 if (mode == 'vertical')
Chris@0 4213 return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
Chris@0 4214 element.offsetHeight;
Chris@0 4215 if (mode == 'horizontal')
Chris@0 4216 return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
Chris@0 4217 element.offsetWidth;
Chris@0 4218 },
Chris@0 4219
Chris@0 4220 // Deprecation layer -- use newer Element methods now (1.5.2).
Chris@0 4221
Chris@0 4222 cumulativeOffset: Element.Methods.cumulativeOffset,
Chris@0 4223
Chris@0 4224 positionedOffset: Element.Methods.positionedOffset,
Chris@0 4225
Chris@0 4226 absolutize: function(element) {
Chris@0 4227 Position.prepare();
Chris@0 4228 return Element.absolutize(element);
Chris@0 4229 },
Chris@0 4230
Chris@0 4231 relativize: function(element) {
Chris@0 4232 Position.prepare();
Chris@0 4233 return Element.relativize(element);
Chris@0 4234 },
Chris@0 4235
Chris@0 4236 realOffset: Element.Methods.cumulativeScrollOffset,
Chris@0 4237
Chris@0 4238 offsetParent: Element.Methods.getOffsetParent,
Chris@0 4239
Chris@0 4240 page: Element.Methods.viewportOffset,
Chris@0 4241
Chris@0 4242 clone: function(source, target, options) {
Chris@0 4243 options = options || { };
Chris@0 4244 return Element.clonePosition(target, source, options);
Chris@0 4245 }
Chris@0 4246 };
Chris@0 4247
Chris@0 4248 /*--------------------------------------------------------------------------*/
Chris@0 4249
Chris@0 4250 if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
Chris@0 4251 function iter(name) {
Chris@0 4252 return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
Chris@0 4253 }
Chris@0 4254
Chris@0 4255 instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
Chris@0 4256 function(element, className) {
Chris@0 4257 className = className.toString().strip();
Chris@0 4258 var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
Chris@0 4259 return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
Chris@0 4260 } : function(element, className) {
Chris@0 4261 className = className.toString().strip();
Chris@0 4262 var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
Chris@0 4263 if (!classNames && !className) return elements;
Chris@0 4264
Chris@0 4265 var nodes = $(element).getElementsByTagName('*');
Chris@0 4266 className = ' ' + className + ' ';
Chris@0 4267
Chris@0 4268 for (var i = 0, child, cn; child = nodes[i]; i++) {
Chris@0 4269 if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
Chris@0 4270 (classNames && classNames.all(function(name) {
Chris@0 4271 return !name.toString().blank() && cn.include(' ' + name + ' ');
Chris@0 4272 }))))
Chris@0 4273 elements.push(Element.extend(child));
Chris@0 4274 }
Chris@0 4275 return elements;
Chris@0 4276 };
Chris@0 4277
Chris@0 4278 return function(className, parentElement) {
Chris@0 4279 return $(parentElement || document.body).getElementsByClassName(className);
Chris@0 4280 };
Chris@0 4281 }(Element.Methods);
Chris@0 4282
Chris@0 4283 /*--------------------------------------------------------------------------*/
Chris@0 4284
Chris@0 4285 Element.ClassNames = Class.create();
Chris@0 4286 Element.ClassNames.prototype = {
Chris@0 4287 initialize: function(element) {
Chris@0 4288 this.element = $(element);
Chris@0 4289 },
Chris@0 4290
Chris@0 4291 _each: function(iterator) {
Chris@0 4292 this.element.className.split(/\s+/).select(function(name) {
Chris@0 4293 return name.length > 0;
Chris@0 4294 })._each(iterator);
Chris@0 4295 },
Chris@0 4296
Chris@0 4297 set: function(className) {
Chris@0 4298 this.element.className = className;
Chris@0 4299 },
Chris@0 4300
Chris@0 4301 add: function(classNameToAdd) {
Chris@0 4302 if (this.include(classNameToAdd)) return;
Chris@0 4303 this.set($A(this).concat(classNameToAdd).join(' '));
Chris@0 4304 },
Chris@0 4305
Chris@0 4306 remove: function(classNameToRemove) {
Chris@0 4307 if (!this.include(classNameToRemove)) return;
Chris@0 4308 this.set($A(this).without(classNameToRemove).join(' '));
Chris@0 4309 },
Chris@0 4310
Chris@0 4311 toString: function() {
Chris@0 4312 return $A(this).join(' ');
Chris@0 4313 }
Chris@0 4314 };
Chris@0 4315
Chris@0 4316 Object.extend(Element.ClassNames.prototype, Enumerable);
Chris@0 4317
Chris@0 4318 /*--------------------------------------------------------------------------*/
Chris@0 4319
Chris@0 4320 Element.addMethods();