annotate public/javascripts/prototype.js @ 8:0c83d98252d9 yuya

* Add custom repo prefix and proper auth realm, remove auth cache (seems like an unwise feature), pass DB handle around, various other bits of tidying
author Chris Cannam
date Thu, 12 Aug 2010 15:31:37 +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();