1 /* Prototype JavaScript framework, version 1.6.0
2 * (c) 2005-2007 Sam Stephenson
4 * Prototype is freely distributable under the terms of an MIT-style license.
5 * For details, see the Prototype web site: http://www.prototypejs.org/
7 *--------------------------------------------------------------------------*/
13 IE
: !!(window
.attachEvent
&& !window
.opera
),
14 Opera
: !!window
.opera
,
15 WebKit
: navigator
.userAgent
.indexOf('AppleWebKit/') > -1,
16 Gecko
: navigator
.userAgent
.indexOf('Gecko') > -1 && navigator
.userAgent
.indexOf('KHTML') == -1,
17 MobileSafari
: !!navigator
.userAgent
.match(/Apple.*Mobile.*Safari/)
21 XPath
: !!document
.evaluate
,
22 ElementExtensions
: !!window
.HTMLElement
,
23 SpecificElementExtensions
:
24 document
.createElement('div').__proto__
&&
25 document
.createElement('div').__proto__
!==
26 document
.createElement('form').__proto__
29 ScriptFragment
: '<script[^>]*>([\\S\\s]*?)<\/script>',
30 JSONFilter
: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
32 emptyFunction: function() { },
33 K: function(x
) { return x
}
36 if (Prototype
.Browser
.MobileSafari
)
37 Prototype
.BrowserFeatures
.SpecificElementExtensions
= false;
39 if (Prototype
.Browser
.WebKit
)
40 Prototype
.BrowserFeatures
.XPath
= false;
42 /* Based on Alex Arnell's inheritance implementation. */
45 var parent
= null, properties
= $A(arguments
);
46 if (Object
.isFunction(properties
[0]))
47 parent
= properties
.shift();
50 this.initialize
.apply(this, arguments
);
53 Object
.extend(klass
, Class
.Methods
);
54 klass
.superclass
= parent
;
55 klass
.subclasses
= [];
58 var subclass = function() { };
59 subclass
.prototype = parent
.prototype;
60 klass
.prototype = new subclass
;
61 parent
.subclasses
.push(klass
);
64 for (var i
= 0; i
< properties
.length
; i
++)
65 klass
.addMethods(properties
[i
]);
67 if (!klass
.prototype.initialize
)
68 klass
.prototype.initialize
= Prototype
.emptyFunction
;
70 klass
.prototype.constructor = klass
;
77 addMethods: function(source
) {
78 var ancestor
= this.superclass
&& this.superclass
.prototype;
79 var properties
= Object
.keys(source
);
81 if (!Object
.keys({ toString
: true }).length
)
82 properties
.push("toString", "valueOf");
84 for (var i
= 0, length
= properties
.length
; i
< length
; i
++) {
85 var property
= properties
[i
], value
= source
[property
];
86 if (ancestor
&& Object
.isFunction(value
) &&
87 value
.argumentNames().first() == "$super") {
88 var method
= value
, value
= Object
.extend((function(m
) {
89 return function() { return ancestor
[m
].apply(this, arguments
) };
90 })(property
).wrap(method
), {
91 valueOf: function() { return method
},
92 toString: function() { return method
.toString() }
95 this.prototype[property
] = value
;
104 Object
.extend = function(destination
, source
) {
105 for (var property
in source
)
106 destination
[property
] = source
[property
];
110 Object
.extend(Object
, {
111 inspect: function(object
) {
113 if (object
=== undefined) return 'undefined';
114 if (object
=== null) return 'null';
115 return object
.inspect
? object
.inspect() : object
.toString();
117 if (e
instanceof RangeError
) return '...';
122 toJSON: function(object
) {
123 var type
= typeof object
;
127 case 'unknown': return;
128 case 'boolean': return object
.toString();
131 if (object
=== null) return 'null';
132 if (object
.toJSON
) return object
.toJSON();
133 if (Object
.isElement(object
)) return;
136 for (var property
in object
) {
137 var value
= Object
.toJSON(object
[property
]);
138 if (value
!== undefined)
139 results
.push(property
.toJSON() + ': ' + value
);
142 return '{' + results
.join(', ') + '}';
145 toQueryString: function(object
) {
146 return $H(object
).toQueryString();
149 toHTML: function(object
) {
150 return object
&& object
.toHTML
? object
.toHTML() : String
.interpret(object
);
153 keys: function(object
) {
155 for (var property
in object
)
160 values: function(object
) {
162 for (var property
in object
)
163 values
.push(object
[property
]);
167 clone: function(object
) {
168 return Object
.extend({ }, object
);
171 isElement: function(object
) {
172 return object
&& object
.nodeType
== 1;
175 isArray: function(object
) {
176 return object
&& object
.constructor === Array
;
179 isHash: function(object
) {
180 return object
instanceof Hash
;
183 isFunction: function(object
) {
184 return typeof object
== "function";
187 isString: function(object
) {
188 return typeof object
== "string";
191 isNumber: function(object
) {
192 return typeof object
== "number";
195 isUndefined: function(object
) {
196 return typeof object
== "undefined";
200 Object
.extend(Function
.prototype, {
201 argumentNames: function() {
202 var names
= this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
203 return names
.length
== 1 && !names
[0] ? [] : names
;
207 if (arguments
.length
< 2 && arguments
[0] === undefined) return this;
208 var __method
= this, args
= $A(arguments
), object
= args
.shift();
210 return __method
.apply(object
, args
.concat($A(arguments
)));
214 bindAsEventListener: function() {
215 var __method
= this, args
= $A(arguments
), object
= args
.shift();
216 return function(event
) {
217 return __method
.apply(object
, [event
|| window
.event
].concat(args
));
222 if (!arguments
.length
) return this;
223 var __method
= this, args
= $A(arguments
);
225 return __method
.apply(this, args
.concat($A(arguments
)));
230 var __method
= this, args
= $A(arguments
), timeout
= args
.shift() * 1000;
231 return window
.setTimeout(function() {
232 return __method
.apply(__method
, args
);
236 wrap: function(wrapper
) {
239 return wrapper
.apply(this, [__method
.bind(this)].concat($A(arguments
)));
243 methodize: function() {
244 if (this._methodized
) return this._methodized
;
246 return this._methodized = function() {
247 return __method
.apply(null, [this].concat($A(arguments
)));
252 Function
.prototype.defer
= Function
.prototype.delay
.curry(0.01);
254 Date
.prototype.toJSON = function() {
255 return '"' + this.getUTCFullYear() + '-' +
256 (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
257 this.getUTCDate().toPaddedString(2) + 'T' +
258 this.getUTCHours().toPaddedString(2) + ':' +
259 this.getUTCMinutes().toPaddedString(2) + ':' +
260 this.getUTCSeconds().toPaddedString(2) + 'Z"';
267 for (var i
= 0, length
= arguments
.length
; i
< length
; i
++) {
268 var lambda
= arguments
[i
];
270 returnValue
= lambda();
279 RegExp
.prototype.match
= RegExp
.prototype.test
;
281 RegExp
.escape = function(str
) {
282 return String(str
).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
285 /*--------------------------------------------------------------------------*/
287 var PeriodicalExecuter
= Class
.create({
288 initialize: function(callback
, frequency
) {
289 this.callback
= callback
;
290 this.frequency
= frequency
;
291 this.currentlyExecuting
= false;
293 this.registerCallback();
296 registerCallback: function() {
297 this.timer
= setInterval(this.onTimerEvent
.bind(this), this.frequency
* 1000);
300 execute: function() {
305 if (!this.timer
) return;
306 clearInterval(this.timer
);
310 onTimerEvent: function() {
311 if (!this.currentlyExecuting
) {
313 this.currentlyExecuting
= true;
316 this.currentlyExecuting
= false;
321 Object
.extend(String
, {
322 interpret: function(value
) {
323 return value
== null ? '' : String(value
);
335 Object
.extend(String
.prototype, {
336 gsub: function(pattern
, replacement
) {
337 var result
= '', source
= this, match
;
338 replacement
= arguments
.callee
.prepareReplacement(replacement
);
340 while (source
.length
> 0) {
341 if (match
= source
.match(pattern
)) {
342 result
+= source
.slice(0, match
.index
);
343 result
+= String
.interpret(replacement(match
));
344 source
= source
.slice(match
.index
+ match
[0].length
);
346 result
+= source
, source
= '';
352 sub: function(pattern
, replacement
, count
) {
353 replacement
= this.gsub
.prepareReplacement(replacement
);
354 count
= count
=== undefined ? 1 : count
;
356 return this.gsub(pattern
, function(match
) {
357 if (--count
< 0) return match
[0];
358 return replacement(match
);
362 scan: function(pattern
, iterator
) {
363 this.gsub(pattern
, iterator
);
367 truncate: function(length
, truncation
) {
368 length
= length
|| 30;
369 truncation
= truncation
=== undefined ? '...' : truncation
;
370 return this.length
> length
?
371 this.slice(0, length
- truncation
.length
) + truncation
: String(this);
375 return this.replace(/^\s+/, '').replace(/\s+$/, '');
378 stripTags: function() {
379 return this.replace(/<\/?[^>]+>/gi, '');
382 stripScripts: function() {
383 return this.replace(new RegExp(Prototype
.ScriptFragment
, 'img'), '');
386 extractScripts: function() {
387 var matchAll
= new RegExp(Prototype
.ScriptFragment
, 'img');
388 var matchOne
= new RegExp(Prototype
.ScriptFragment
, 'im');
389 return (this.match(matchAll
) || []).map(function(scriptTag
) {
390 return (scriptTag
.match(matchOne
) || ['', ''])[1];
394 evalScripts: function() {
395 return this.extractScripts().map(function(script
) { return eval(script
) });
398 escapeHTML: function() {
399 var self
= arguments
.callee
;
400 self
.text
.data
= this;
401 return self
.div
.innerHTML
;
404 unescapeHTML: function() {
405 var div
= new Element('div');
406 div
.innerHTML
= this.stripTags();
407 return div
.childNodes
[0] ? (div
.childNodes
.length
> 1 ?
408 $A(div
.childNodes
).inject('', function(memo
, node
) { return memo
+node
.nodeValue
}) :
409 div
.childNodes
[0].nodeValue
) : '';
412 toQueryParams: function(separator
) {
413 var match
= this.strip().match(/([^?#]*)(#.*)?$/);
414 if (!match
) return { };
416 return match
[1].split(separator
|| '&').inject({ }, function(hash
, pair
) {
417 if ((pair
= pair
.split('='))[0]) {
418 var key
= decodeURIComponent(pair
.shift());
419 var value
= pair
.length
> 1 ? pair
.join('=') : pair
[0];
420 if (value
!= undefined) value
= decodeURIComponent(value
);
423 if (!Object
.isArray(hash
[key
])) hash
[key
] = [hash
[key
]];
424 hash
[key
].push(value
);
426 else hash
[key
] = value
;
432 toArray: function() {
433 return this.split('');
437 return this.slice(0, this.length
- 1) +
438 String
.fromCharCode(this.charCodeAt(this.length
- 1) + 1);
441 times: function(count
) {
442 return count
< 1 ? '' : new Array(count
+ 1).join(this);
445 camelize: function() {
446 var parts
= this.split('-'), len
= parts
.length
;
447 if (len
== 1) return parts
[0];
449 var camelized
= this.charAt(0) == '-'
450 ? parts
[0].charAt(0).toUpperCase() + parts
[0].substring(1)
453 for (var i
= 1; i
< len
; i
++)
454 camelized
+= parts
[i
].charAt(0).toUpperCase() + parts
[i
].substring(1);
459 capitalize: function() {
460 return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
463 underscore: function() {
464 return this.gsub(/::/, '/').gsub(/([A
-Z
]+)([A
-Z
][a
-z
])/,'#{1}_#{2}').gsub(/([a
-z
\d
])([A
-Z
])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
467 dasherize: function() {
468 return this.gsub(/_
/,'-');
471 inspect: function(useDoubleQuotes
) {
472 var escapedString
= this.gsub(/[\x00-\x1f\\]/, function(match
) {
473 var character
= String
.specialChar
[match
[0]];
474 return character
? character
: '\\u00' + match
[0].charCodeAt().toPaddedString(2, 16);
476 if (useDoubleQuotes
) return '"' + escapedString
.replace(/"/g, '\\"') + '"';
477 return "'" + escapedString.replace(/'/g
, '\\\'') + "'";
481 return this.inspect(true);
484 unfilterJSON: function(filter
) {
485 return this.sub(filter
|| Prototype
.JSONFilter
, '#{1}');
489 var str
= this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
490 return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str
);
493 evalJSON: function(sanitize
) {
494 var json
= this.unfilterJSON();
496 if (!sanitize
|| json
.isJSON()) return eval('(' + json
+ ')');
498 throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
501 include: function(pattern
) {
502 return this.indexOf(pattern
) > -1;
505 startsWith: function(pattern
) {
506 return this.indexOf(pattern
) === 0;
509 endsWith: function(pattern
) {
510 var d
= this.length
- pattern
.length
;
511 return d
>= 0 && this.lastIndexOf(pattern
) === d
;
519 return /^\s*$/.test(this);
522 interpolate: function(object
, pattern
) {
523 return new Template(this, pattern
).evaluate(object
);
527 if (Prototype
.Browser
.WebKit
|| Prototype
.Browser
.IE
) Object
.extend(String
.prototype, {
528 escapeHTML: function() {
529 return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g
,'>');
531 unescapeHTML: function() {
532 return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
536 String
.prototype.gsub
.prepareReplacement = function(replacement
) {
537 if (Object
.isFunction(replacement
)) return replacement
;
538 var template
= new Template(replacement
);
539 return function(match
) { return template
.evaluate(match
) };
542 String
.prototype.parseQuery
= String
.prototype.toQueryParams
;
544 Object
.extend(String
.prototype.escapeHTML
, {
545 div
: document
.createElement('div'),
546 text
: document
.createTextNode('')
549 with (String
.prototype.escapeHTML
) div
.appendChild(text
);
551 var Template
= Class
.create({
552 initialize: function(template
, pattern
) {
553 this.template
= template
.toString();
554 this.pattern
= pattern
|| Template
.Pattern
;
557 evaluate: function(object
) {
558 if (Object
.isFunction(object
.toTemplateReplacements
))
559 object
= object
.toTemplateReplacements();
561 return this.template
.gsub(this.pattern
, function(match
) {
562 if (object
== null) return '';
564 var before
= match
[1] || '';
565 if (before
== '\\') return match
[2];
567 var ctx
= object
, expr
= match
[3];
568 var pattern
= /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/, match
= pattern
.exec(expr
);
569 if (match
== null) return before
;
571 while (match
!= null) {
572 var comp
= match
[1].startsWith('[') ? match
[2].gsub('\\\\]', ']') : match
[1];
574 if (null == ctx
|| '' == match
[3]) break;
575 expr
= expr
.substring('[' == match
[3] ? match
[1].length
: match
[0].length
);
576 match
= pattern
.exec(expr
);
579 return before
+ String
.interpret(ctx
);
583 Template
.Pattern
= /(^|.|\r|\n)(#\{(.*?)\})/;
588 each: function(iterator
, context
) {
590 iterator
= iterator
.bind(context
);
592 this._each(function(value
) {
593 iterator(value
, index
++);
596 if (e
!= $break) throw e
;
601 eachSlice: function(number
, iterator
, context
) {
602 iterator
= iterator
? iterator
.bind(context
) : Prototype
.K
;
603 var index
= -number
, slices
= [], array
= this.toArray();
604 while ((index
+= number
) < array
.length
)
605 slices
.push(array
.slice(index
, index
+number
));
606 return slices
.collect(iterator
, context
);
609 all: function(iterator
, context
) {
610 iterator
= iterator
? iterator
.bind(context
) : Prototype
.K
;
612 this.each(function(value
, index
) {
613 result
= result
&& !!iterator(value
, index
);
614 if (!result
) throw $break;
619 any: function(iterator
, context
) {
620 iterator
= iterator
? iterator
.bind(context
) : Prototype
.K
;
622 this.each(function(value
, index
) {
623 if (result
= !!iterator(value
, index
))
629 collect: function(iterator
, context
) {
630 iterator
= iterator
? iterator
.bind(context
) : Prototype
.K
;
632 this.each(function(value
, index
) {
633 results
.push(iterator(value
, index
));
638 detect: function(iterator
, context
) {
639 iterator
= iterator
.bind(context
);
641 this.each(function(value
, index
) {
642 if (iterator(value
, index
)) {
650 findAll: function(iterator
, context
) {
651 iterator
= iterator
.bind(context
);
653 this.each(function(value
, index
) {
654 if (iterator(value
, index
))
660 grep: function(filter
, iterator
, context
) {
661 iterator
= iterator
? iterator
.bind(context
) : Prototype
.K
;
664 if (Object
.isString(filter
))
665 filter
= new RegExp(filter
);
667 this.each(function(value
, index
) {
668 if (filter
.match(value
))
669 results
.push(iterator(value
, index
));
674 include: function(object
) {
675 if (Object
.isFunction(this.indexOf
))
676 if (this.indexOf(object
) != -1) return true;
679 this.each(function(value
) {
680 if (value
== object
) {
688 inGroupsOf: function(number
, fillWith
) {
689 fillWith
= fillWith
=== undefined ? null : fillWith
;
690 return this.eachSlice(number
, function(slice
) {
691 while(slice
.length
< number
) slice
.push(fillWith
);
696 inject: function(memo
, iterator
, context
) {
697 iterator
= iterator
.bind(context
);
698 this.each(function(value
, index
) {
699 memo
= iterator(memo
, value
, index
);
704 invoke: function(method
) {
705 var args
= $A(arguments
).slice(1);
706 return this.map(function(value
) {
707 return value
[method
].apply(value
, args
);
711 max: function(iterator
, context
) {
712 iterator
= iterator
? iterator
.bind(context
) : Prototype
.K
;
714 this.each(function(value
, index
) {
715 value
= iterator(value
, index
);
716 if (result
== undefined || value
>= result
)
722 min: function(iterator
, context
) {
723 iterator
= iterator
? iterator
.bind(context
) : Prototype
.K
;
725 this.each(function(value
, index
) {
726 value
= iterator(value
, index
);
727 if (result
== undefined || value
< result
)
733 partition: function(iterator
, context
) {
734 iterator
= iterator
? iterator
.bind(context
) : Prototype
.K
;
735 var trues
= [], falses
= [];
736 this.each(function(value
, index
) {
737 (iterator(value
, index
) ?
738 trues
: falses
).push(value
);
740 return [trues
, falses
];
743 pluck: function(property
) {
745 this.each(function(value
) {
746 results
.push(value
[property
]);
751 reject: function(iterator
, context
) {
752 iterator
= iterator
.bind(context
);
754 this.each(function(value
, index
) {
755 if (!iterator(value
, index
))
761 sortBy: function(iterator
, context
) {
762 iterator
= iterator
.bind(context
);
763 return this.map(function(value
, index
) {
764 return {value
: value
, criteria
: iterator(value
, index
)};
765 }).sort(function(left
, right
) {
766 var a
= left
.criteria
, b
= right
.criteria
;
767 return a
< b
? -1 : a
> b
? 1 : 0;
771 toArray: function() {
776 var iterator
= Prototype
.K
, args
= $A(arguments
);
777 if (Object
.isFunction(args
.last()))
778 iterator
= args
.pop();
780 var collections
= [this].concat(args
).map($A
);
781 return this.map(function(value
, index
) {
782 return iterator(collections
.pluck(index
));
787 return this.toArray().length
;
790 inspect: function() {
791 return '#<Enumerable:' + this.toArray().inspect() + '>';
795 Object
.extend(Enumerable
, {
796 map
: Enumerable
.collect
,
797 find
: Enumerable
.detect
,
798 select
: Enumerable
.findAll
,
799 filter
: Enumerable
.findAll
,
800 member
: Enumerable
.include
,
801 entries
: Enumerable
.toArray
,
802 every
: Enumerable
.all
,
805 function $A(iterable
) {
806 if (!iterable
) return [];
807 if (iterable
.toArray
) return iterable
.toArray();
808 var length
= iterable
.length
, results
= new Array(length
);
809 while (length
--) results
[length
] = iterable
[length
];
813 if (Prototype
.Browser
.WebKit
) {
814 function $A(iterable
) {
815 if (!iterable
) return [];
816 if (!(Object
.isFunction(iterable
) && iterable
== '[object NodeList]') &&
817 iterable
.toArray
) return iterable
.toArray();
818 var length
= iterable
.length
, results
= new Array(length
);
819 while (length
--) results
[length
] = iterable
[length
];
826 Object
.extend(Array
.prototype, Enumerable
);
828 if (!Array
.prototype._reverse
) Array
.prototype._reverse
= Array
.prototype.reverse
;
830 Object
.extend(Array
.prototype, {
831 _each: function(iterator
) {
832 for (var i
= 0, length
= this.length
; i
< length
; i
++)
846 return this[this.length
- 1];
849 compact: function() {
850 return this.select(function(value
) {
851 return value
!= null;
855 flatten: function() {
856 return this.inject([], function(array
, value
) {
857 return array
.concat(Object
.isArray(value
) ?
858 value
.flatten() : [value
]);
862 without: function() {
863 var values
= $A(arguments
);
864 return this.select(function(value
) {
865 return !values
.include(value
);
869 reverse: function(inline
) {
870 return (inline
!== false ? this : this.toArray())._reverse();
874 return this.length
> 1 ? this : this[0];
877 uniq: function(sorted
) {
878 return this.inject([], function(array
, value
, index
) {
879 if (0 == index
|| (sorted
? array
.last() != value
: !array
.include(value
)))
885 intersect: function(array
) {
886 return this.uniq().findAll(function(item
) {
887 return array
.detect(function(value
) { return item
=== value
});
892 return [].concat(this);
899 inspect: function() {
900 return '[' + this.map(Object
.inspect
).join(', ') + ']';
905 this.each(function(object
) {
906 var value
= Object
.toJSON(object
);
907 if (value
!== undefined) results
.push(value
);
909 return '[' + results
.join(', ') + ']';
913 // use native browser JS 1.6 implementation if available
914 if (Object
.isFunction(Array
.prototype.forEach
))
915 Array
.prototype._each
= Array
.prototype.forEach
;
917 if (!Array
.prototype.indexOf
) Array
.prototype.indexOf = function(item
, i
) {
919 var length
= this.length
;
920 if (i
< 0) i
= length
+ i
;
921 for (; i
< length
; i
++)
922 if (this[i
] === item
) return i
;
926 if (!Array
.prototype.lastIndexOf
) Array
.prototype.lastIndexOf = function(item
, i
) {
927 i
= isNaN(i
) ? this.length
: (i
< 0 ? this.length
+ i
: i
) + 1;
928 var n
= this.slice(0, i
).reverse().indexOf(item
);
929 return (n
< 0) ? n
: i
- n
- 1;
932 Array
.prototype.toArray
= Array
.prototype.clone
;
934 function $w(string
) {
935 if (!Object
.isString(string
)) return [];
936 string
= string
.strip();
937 return string
? string
.split(/\s+/) : [];
940 if (Prototype
.Browser
.Opera
){
941 Array
.prototype.concat = function() {
943 for (var i
= 0, length
= this.length
; i
< length
; i
++) array
.push(this[i
]);
944 for (var i
= 0, length
= arguments
.length
; i
< length
; i
++) {
945 if (Object
.isArray(arguments
[i
])) {
946 for (var j
= 0, arrayLength
= arguments
[i
].length
; j
< arrayLength
; j
++)
947 array
.push(arguments
[i
][j
]);
949 array
.push(arguments
[i
]);
955 Object
.extend(Number
.prototype, {
956 toColorPart: function() {
957 return this.toPaddedString(2, 16);
964 times: function(iterator
) {
965 $R(0, this, true).each(iterator
);
969 toPaddedString: function(length
, radix
) {
970 var string
= this.toString(radix
|| 10);
971 return '0'.times(length
- string
.length
) + string
;
975 return isFinite(this) ? this.toString() : 'null';
979 $w('abs round ceil floor').each(function(method
){
980 Number
.prototype[method
] = Math
[method
].methodize();
982 function $H(object
) {
983 return new Hash(object
);
986 var Hash
= Class
.create(Enumerable
, (function() {
988 var i
= 0, Test = function(value
) { this.key
= value
};
989 Test
.prototype.key
= 'foo';
990 for (var property
in new Test('bar')) i
++;
993 function each(iterator
) {
995 for (var key
in this._object
) {
996 var value
= this._object
[key
];
997 if (cache
.include(key
)) continue;
999 var pair
= [key
, value
];
1006 function each(iterator
) {
1007 for (var key
in this._object
) {
1008 var value
= this._object
[key
], pair
= [key
, value
];
1016 function toQueryPair(key
, value
) {
1017 if (Object
.isUndefined(value
)) return key
;
1018 return key
+ '=' + encodeURIComponent(String
.interpret(value
));
1022 initialize: function(object
) {
1023 this._object
= Object
.isHash(object
) ? object
.toObject() : Object
.clone(object
);
1028 set: function(key
, value
) {
1029 return this._object
[key
] = value
;
1032 get: function(key
) {
1033 return this._object
[key
];
1036 unset: function(key
) {
1037 var value
= this._object
[key
];
1038 delete this._object
[key
];
1042 toObject: function() {
1043 return Object
.clone(this._object
);
1047 return this.pluck('key');
1050 values: function() {
1051 return this.pluck('value');
1054 index: function(value
) {
1055 var match
= this.detect(function(pair
) {
1056 return pair
.value
=== value
;
1058 return match
&& match
.key
;
1061 merge: function(object
) {
1062 return this.clone().update(object
);
1065 update: function(object
) {
1066 return new Hash(object
).inject(this, function(result
, pair
) {
1067 result
.set(pair
.key
, pair
.value
);
1072 toQueryString: function() {
1073 return this.map(function(pair
) {
1074 var key
= encodeURIComponent(pair
.key
), values
= pair
.value
;
1076 if (values
&& typeof values
== 'object') {
1077 if (Object
.isArray(values
))
1078 return values
.map(toQueryPair
.curry(key
)).join('&');
1080 return toQueryPair(key
, values
);
1084 inspect: function() {
1085 return '#<Hash:{' + this.map(function(pair
) {
1086 return pair
.map(Object
.inspect
).join(': ');
1087 }).join(', ') + '}>';
1090 toJSON: function() {
1091 return Object
.toJSON(this.toObject());
1095 return new Hash(this);
1100 Hash
.prototype.toTemplateReplacements
= Hash
.prototype.toObject
;
1102 var ObjectRange
= Class
.create(Enumerable
, {
1103 initialize: function(start
, end
, exclusive
) {
1106 this.exclusive
= exclusive
;
1109 _each: function(iterator
) {
1110 var value
= this.start
;
1111 while (this.include(value
)) {
1113 value
= value
.succ();
1117 include: function(value
) {
1118 if (value
< this.start
)
1121 return value
< this.end
;
1122 return value
<= this.end
;
1126 var $R = function(start
, end
, exclusive
) {
1127 return new ObjectRange(start
, end
, exclusive
);
1131 getTransport: function() {
1133 function() {return new XMLHttpRequest()},
1134 function() {return new ActiveXObject('Msxml2.XMLHTTP')},
1135 function() {return new ActiveXObject('Microsoft.XMLHTTP')}
1139 activeRequestCount
: 0
1145 _each: function(iterator
) {
1146 this.responders
._each(iterator
);
1149 register: function(responder
) {
1150 if (!this.include(responder
))
1151 this.responders
.push(responder
);
1154 unregister: function(responder
) {
1155 this.responders
= this.responders
.without(responder
);
1158 dispatch: function(callback
, request
, transport
, json
) {
1159 this.each(function(responder
) {
1160 if (Object
.isFunction(responder
[callback
])) {
1162 responder
[callback
].apply(responder
, [request
, transport
, json
]);
1169 Object
.extend(Ajax
.Responders
, Enumerable
);
1171 Ajax
.Responders
.register({
1172 onCreate: function() { Ajax
.activeRequestCount
++ },
1173 onComplete: function() { Ajax
.activeRequestCount
-- }
1176 Ajax
.Base
= Class
.create({
1177 initialize: function(options
) {
1181 contentType
: 'application/x-www-form-urlencoded',
1187 Object
.extend(this.options
, options
|| { });
1189 this.options
.method
= this.options
.method
.toLowerCase();
1190 if (Object
.isString(this.options
.parameters
))
1191 this.options
.parameters
= this.options
.parameters
.toQueryParams();
1195 Ajax
.Request
= Class
.create(Ajax
.Base
, {
1198 initialize: function($super, url
, options
) {
1200 this.transport
= Ajax
.getTransport();
1204 request: function(url
) {
1206 this.method
= this.options
.method
;
1207 var params
= Object
.clone(this.options
.parameters
);
1209 if (!['get', 'post'].include(this.method
)) {
1210 // simulate other verbs over post
1211 params
['_method'] = this.method
;
1212 this.method
= 'post';
1215 this.parameters
= params
;
1217 if (params
= Object
.toQueryString(params
)) {
1218 // when GET, append parameters to URL
1219 if (this.method
== 'get')
1220 this.url
+= (this.url
.include('?') ? '&' : '?') + params
;
1221 else if (/Konqueror|Safari|KHTML/.test(navigator
.userAgent
))
1226 var response
= new Ajax
.Response(this);
1227 if (this.options
.onCreate
) this.options
.onCreate(response
);
1228 Ajax
.Responders
.dispatch('onCreate', this, response
);
1230 this.transport
.open(this.method
.toUpperCase(), this.url
,
1231 this.options
.asynchronous
);
1233 if (this.options
.asynchronous
) this.respondToReadyState
.bind(this).defer(1);
1235 this.transport
.onreadystatechange
= this.onStateChange
.bind(this);
1236 this.setRequestHeaders();
1238 this.body
= this.method
== 'post' ? (this.options
.postBody
|| params
) : null;
1239 this.transport
.send(this.body
);
1241 /* Force Firefox to handle ready state 4 for synchronous requests */
1242 if (!this.options
.asynchronous
&& this.transport
.overrideMimeType
)
1243 this.onStateChange();
1247 this.dispatchException(e
);
1251 onStateChange: function() {
1252 var readyState
= this.transport
.readyState
;
1253 if (readyState
> 1 && !((readyState
== 4) && this._complete
))
1254 this.respondToReadyState(this.transport
.readyState
);
1257 setRequestHeaders: function() {
1259 'X-Requested-With': 'XMLHttpRequest',
1260 'X-Prototype-Version': Prototype
.Version
,
1261 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
1264 if (this.method
== 'post') {
1265 headers
['Content-type'] = this.options
.contentType
+
1266 (this.options
.encoding
? '; charset=' + this.options
.encoding
: '');
1268 /* Force "Connection: close" for older Mozilla browsers to work
1269 * around a bug where XMLHttpRequest sends an incorrect
1270 * Content-length header. See Mozilla Bugzilla #246651.
1272 if (this.transport
.overrideMimeType
&&
1273 (navigator
.userAgent
.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
1274 headers
['Connection'] = 'close';
1277 // user-defined headers
1278 if (typeof this.options
.requestHeaders
== 'object') {
1279 var extras
= this.options
.requestHeaders
;
1281 if (Object
.isFunction(extras
.push
))
1282 for (var i
= 0, length
= extras
.length
; i
< length
; i
+= 2)
1283 headers
[extras
[i
]] = extras
[i
+1];
1285 $H(extras
).each(function(pair
) { headers
[pair
.key
] = pair
.value
});
1288 for (var name
in headers
)
1289 this.transport
.setRequestHeader(name
, headers
[name
]);
1292 success: function() {
1293 var status
= this.getStatus();
1294 return !status
|| (status
>= 200 && status
< 300);
1297 getStatus: function() {
1299 return this.transport
.status
|| 0;
1300 } catch (e
) { return 0 }
1303 respondToReadyState: function(readyState
) {
1304 var state
= Ajax
.Request
.Events
[readyState
], response
= new Ajax
.Response(this);
1306 if (state
== 'Complete') {
1308 this._complete
= true;
1309 (this.options
['on' + response
.status
]
1310 || this.options
['on' + (this.success() ? 'Success' : 'Failure')]
1311 || Prototype
.emptyFunction
)(response
, response
.headerJSON
);
1313 this.dispatchException(e
);
1316 var contentType
= response
.getHeader('Content-type');
1317 if (this.options
.evalJS
== 'force'
1318 || (this.options
.evalJS
&& contentType
1319 && contentType
.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
1320 this.evalResponse();
1324 (this.options
['on' + state
] || Prototype
.emptyFunction
)(response
, response
.headerJSON
);
1325 Ajax
.Responders
.dispatch('on' + state
, this, response
, response
.headerJSON
);
1327 this.dispatchException(e
);
1330 if (state
== 'Complete') {
1331 // avoid memory leak in MSIE: clean up
1332 this.transport
.onreadystatechange
= Prototype
.emptyFunction
;
1336 getHeader: function(name
) {
1338 return this.transport
.getResponseHeader(name
);
1339 } catch (e
) { return null }
1342 evalResponse: function() {
1344 return eval((this.transport
.responseText
|| '').unfilterJSON());
1346 this.dispatchException(e
);
1350 dispatchException: function(exception
) {
1351 (this.options
.onException
|| Prototype
.emptyFunction
)(this, exception
);
1352 Ajax
.Responders
.dispatch('onException', this, exception
);
1356 Ajax
.Request
.Events
=
1357 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
1359 Ajax
.Response
= Class
.create({
1360 initialize: function(request
){
1361 this.request
= request
;
1362 var transport
= this.transport
= request
.transport
,
1363 readyState
= this.readyState
= transport
.readyState
;
1365 if((readyState
> 2 && !Prototype
.Browser
.IE
) || readyState
== 4) {
1366 this.status
= this.getStatus();
1367 this.statusText
= this.getStatusText();
1368 this.responseText
= String
.interpret(transport
.responseText
);
1369 this.headerJSON
= this._getHeaderJSON();
1372 if(readyState
== 4) {
1373 var xml
= transport
.responseXML
;
1374 this.responseXML
= xml
=== undefined ? null : xml
;
1375 this.responseJSON
= this._getResponseJSON();
1382 getStatus
: Ajax
.Request
.prototype.getStatus
,
1384 getStatusText: function() {
1386 return this.transport
.statusText
|| '';
1387 } catch (e
) { return '' }
1390 getHeader
: Ajax
.Request
.prototype.getHeader
,
1392 getAllHeaders: function() {
1394 return this.getAllResponseHeaders();
1395 } catch (e
) { return null }
1398 getResponseHeader: function(name
) {
1399 return this.transport
.getResponseHeader(name
);
1402 getAllResponseHeaders: function() {
1403 return this.transport
.getAllResponseHeaders();
1406 _getHeaderJSON: function() {
1407 var json
= this.getHeader('X-JSON');
1408 if (!json
) return null;
1409 json
= decodeURIComponent(escape(json
));
1411 return json
.evalJSON(this.request
.options
.sanitizeJSON
);
1413 this.request
.dispatchException(e
);
1417 _getResponseJSON: function() {
1418 var options
= this.request
.options
;
1419 if (!options
.evalJSON
|| (options
.evalJSON
!= 'force' &&
1420 !(this.getHeader('Content-type') || '').include('application/json')))
1423 return this.transport
.responseText
.evalJSON(options
.sanitizeJSON
);
1425 this.request
.dispatchException(e
);
1430 Ajax
.Updater
= Class
.create(Ajax
.Request
, {
1431 initialize: function($super, container
, url
, options
) {
1433 success
: (container
.success
|| container
),
1434 failure
: (container
.failure
|| (container
.success
? null : container
))
1437 options
= options
|| { };
1438 var onComplete
= options
.onComplete
;
1439 options
.onComplete
= (function(response
, param
) {
1440 this.updateContent(response
.responseText
);
1441 if (Object
.isFunction(onComplete
)) onComplete(response
, param
);
1444 $super(url
, options
);
1447 updateContent: function(responseText
) {
1448 var receiver
= this.container
[this.success() ? 'success' : 'failure'],
1449 options
= this.options
;
1451 if (!options
.evalScripts
) responseText
= responseText
.stripScripts();
1453 if (receiver
= $(receiver
)) {
1454 if (options
.insertion
) {
1455 if (Object
.isString(options
.insertion
)) {
1456 var insertion
= { }; insertion
[options
.insertion
] = responseText
;
1457 receiver
.insert(insertion
);
1459 else options
.insertion(receiver
, responseText
);
1461 else receiver
.update(responseText
);
1464 if (this.success()) {
1465 if (this.onComplete
) this.onComplete
.bind(this).defer();
1470 Ajax
.PeriodicalUpdater
= Class
.create(Ajax
.Base
, {
1471 initialize: function($super, container
, url
, options
) {
1473 this.onComplete
= this.options
.onComplete
;
1475 this.frequency
= (this.options
.frequency
|| 2);
1476 this.decay
= (this.options
.decay
|| 1);
1479 this.container
= container
;
1486 this.options
.onComplete
= this.updateComplete
.bind(this);
1487 this.onTimerEvent();
1491 this.updater
.options
.onComplete
= undefined;
1492 clearTimeout(this.timer
);
1493 (this.onComplete
|| Prototype
.emptyFunction
).apply(this, arguments
);
1496 updateComplete: function(response
) {
1497 if (this.options
.decay
) {
1498 this.decay
= (response
.responseText
== this.lastText
?
1499 this.decay
* this.options
.decay
: 1);
1501 this.lastText
= response
.responseText
;
1503 this.timer
= this.onTimerEvent
.bind(this).delay(this.decay
* this.frequency
);
1506 onTimerEvent: function() {
1507 this.updater
= new Ajax
.Updater(this.container
, this.url
, this.options
);
1510 function $(element
) {
1511 if (arguments
.length
> 1) {
1512 for (var i
= 0, elements
= [], length
= arguments
.length
; i
< length
; i
++)
1513 elements
.push($(arguments
[i
]));
1516 if (Object
.isString(element
))
1517 element
= document
.getElementById(element
);
1518 return Element
.extend(element
);
1521 if (Prototype
.BrowserFeatures
.XPath
) {
1522 document
._getElementsByXPath = function(expression
, parentElement
) {
1524 var query
= document
.evaluate(expression
, $(parentElement
) || document
,
1525 null, XPathResult
.ORDERED_NODE_SNAPSHOT_TYPE
, null);
1526 for (var i
= 0, length
= query
.snapshotLength
; i
< length
; i
++)
1527 results
.push(Element
.extend(query
.snapshotItem(i
)));
1532 /*--------------------------------------------------------------------------*/
1534 if (!window
.Node
) var Node
= { };
1536 if (!Node
.ELEMENT_NODE
) {
1537 // DOM level 2 ECMAScript Language Binding
1538 Object
.extend(Node
, {
1542 CDATA_SECTION_NODE
: 4,
1543 ENTITY_REFERENCE_NODE
: 5,
1545 PROCESSING_INSTRUCTION_NODE
: 7,
1548 DOCUMENT_TYPE_NODE
: 10,
1549 DOCUMENT_FRAGMENT_NODE
: 11,
1555 var element
= this.Element
;
1556 this.Element = function(tagName
, attributes
) {
1557 attributes
= attributes
|| { };
1558 tagName
= tagName
.toLowerCase();
1559 var cache
= Element
.cache
;
1560 if (Prototype
.Browser
.IE
&& attributes
.name
) {
1561 tagName
= '<' + tagName
+ ' name="' + attributes
.name
+ '">';
1562 delete attributes
.name
;
1563 return Element
.writeAttribute(document
.createElement(tagName
), attributes
);
1565 if (!cache
[tagName
]) cache
[tagName
] = Element
.extend(document
.createElement(tagName
));
1566 return Element
.writeAttribute(cache
[tagName
].cloneNode(false), attributes
);
1568 Object
.extend(this.Element
, element
|| { });
1571 Element
.cache
= { };
1574 visible: function(element
) {
1575 return $(element
).style
.display
!= 'none';
1578 toggle: function(element
) {
1579 element
= $(element
);
1580 Element
[Element
.visible(element
) ? 'hide' : 'show'](element
);
1584 hide: function(element
) {
1585 $(element
).style
.display
= 'none';
1589 show: function(element
) {
1590 $(element
).style
.display
= '';
1594 remove: function(element
) {
1595 element
= $(element
);
1596 element
.parentNode
.removeChild(element
);
1600 update: function(element
, content
) {
1601 element
= $(element
);
1602 if (content
&& content
.toElement
) content
= content
.toElement();
1603 if (Object
.isElement(content
)) return element
.update().insert(content
);
1604 content
= Object
.toHTML(content
);
1605 element
.innerHTML
= content
.stripScripts();
1606 content
.evalScripts
.bind(content
).defer();
1610 replace: function(element
, content
) {
1611 element
= $(element
);
1612 if (content
&& content
.toElement
) content
= content
.toElement();
1613 else if (!Object
.isElement(content
)) {
1614 content
= Object
.toHTML(content
);
1615 var range
= element
.ownerDocument
.createRange();
1616 range
.selectNode(element
);
1617 content
.evalScripts
.bind(content
).defer();
1618 content
= range
.createContextualFragment(content
.stripScripts());
1620 element
.parentNode
.replaceChild(content
, element
);
1624 insert: function(element
, insertions
) {
1625 element
= $(element
);
1627 if (Object
.isString(insertions
) || Object
.isNumber(insertions
) ||
1628 Object
.isElement(insertions
) || (insertions
&& (insertions
.toElement
|| insertions
.toHTML
)))
1629 insertions
= {bottom
:insertions
};
1631 var content
, t
, range
;
1633 for (position
in insertions
) {
1634 content
= insertions
[position
];
1635 position
= position
.toLowerCase();
1636 t
= Element
._insertionTranslations
[position
];
1638 if (content
&& content
.toElement
) content
= content
.toElement();
1639 if (Object
.isElement(content
)) {
1640 t
.insert(element
, content
);
1644 content
= Object
.toHTML(content
);
1646 range
= element
.ownerDocument
.createRange();
1647 t
.initializeRange(element
, range
);
1648 t
.insert(element
, range
.createContextualFragment(content
.stripScripts()));
1650 content
.evalScripts
.bind(content
).defer();
1656 wrap: function(element
, wrapper
, attributes
) {
1657 element
= $(element
);
1658 if (Object
.isElement(wrapper
))
1659 $(wrapper
).writeAttribute(attributes
|| { });
1660 else if (Object
.isString(wrapper
)) wrapper
= new Element(wrapper
, attributes
);
1661 else wrapper
= new Element('div', wrapper
);
1662 if (element
.parentNode
)
1663 element
.parentNode
.replaceChild(wrapper
, element
);
1664 wrapper
.appendChild(element
);
1668 inspect: function(element
) {
1669 element
= $(element
);
1670 var result
= '<' + element
.tagName
.toLowerCase();
1671 $H({'id': 'id', 'className': 'class'}).each(function(pair
) {
1672 var property
= pair
.first(), attribute
= pair
.last();
1673 var value
= (element
[property
] || '').toString();
1674 if (value
) result
+= ' ' + attribute
+ '=' + value
.inspect(true);
1676 return result
+ '>';
1679 recursivelyCollect: function(element
, property
) {
1680 element
= $(element
);
1682 while (element
= element
[property
])
1683 if (element
.nodeType
== 1)
1684 elements
.push(Element
.extend(element
));
1688 ancestors: function(element
) {
1689 return $(element
).recursivelyCollect('parentNode');
1692 descendants: function(element
) {
1693 return $A($(element
).getElementsByTagName('*')).each(Element
.extend
);
1696 firstDescendant: function(element
) {
1697 element
= $(element
).firstChild
;
1698 while (element
&& element
.nodeType
!= 1) element
= element
.nextSibling
;
1702 immediateDescendants: function(element
) {
1703 if (!(element
= $(element
).firstChild
)) return [];
1704 while (element
&& element
.nodeType
!= 1) element
= element
.nextSibling
;
1705 if (element
) return [element
].concat($(element
).nextSiblings());
1709 previousSiblings: function(element
) {
1710 return $(element
).recursivelyCollect('previousSibling');
1713 nextSiblings: function(element
) {
1714 return $(element
).recursivelyCollect('nextSibling');
1717 siblings: function(element
) {
1718 element
= $(element
);
1719 return element
.previousSiblings().reverse().concat(element
.nextSiblings());
1722 match: function(element
, selector
) {
1723 if (Object
.isString(selector
))
1724 selector
= new Selector(selector
);
1725 return selector
.match($(element
));
1728 up: function(element
, expression
, index
) {
1729 element
= $(element
);
1730 if (arguments
.length
== 1) return $(element
.parentNode
);
1731 var ancestors
= element
.ancestors();
1732 return expression
? Selector
.findElement(ancestors
, expression
, index
) :
1733 ancestors
[index
|| 0];
1736 down: function(element
, expression
, index
) {
1737 element
= $(element
);
1738 if (arguments
.length
== 1) return element
.firstDescendant();
1739 var descendants
= element
.descendants();
1740 return expression
? Selector
.findElement(descendants
, expression
, index
) :
1741 descendants
[index
|| 0];
1744 previous: function(element
, expression
, index
) {
1745 element
= $(element
);
1746 if (arguments
.length
== 1) return $(Selector
.handlers
.previousElementSibling(element
));
1747 var previousSiblings
= element
.previousSiblings();
1748 return expression
? Selector
.findElement(previousSiblings
, expression
, index
) :
1749 previousSiblings
[index
|| 0];
1752 next: function(element
, expression
, index
) {
1753 element
= $(element
);
1754 if (arguments
.length
== 1) return $(Selector
.handlers
.nextElementSibling(element
));
1755 var nextSiblings
= element
.nextSiblings();
1756 return expression
? Selector
.findElement(nextSiblings
, expression
, index
) :
1757 nextSiblings
[index
|| 0];
1760 select: function() {
1761 var args
= $A(arguments
), element
= $(args
.shift());
1762 return Selector
.findChildElements(element
, args
);
1765 adjacent: function() {
1766 var args
= $A(arguments
), element
= $(args
.shift());
1767 return Selector
.findChildElements(element
.parentNode
, args
).without(element
);
1770 identify: function(element
) {
1771 element
= $(element
);
1772 var id
= element
.readAttribute('id'), self
= arguments
.callee
;
1774 do { id
= 'anonymous_element_' + self
.counter
++ } while ($(id
));
1775 element
.writeAttribute('id', id
);
1779 readAttribute: function(element
, name
) {
1780 element
= $(element
);
1781 if (Prototype
.Browser
.IE
) {
1782 var t
= Element
._attributeTranslations
.read
;
1783 if (t
.values
[name
]) return t
.values
[name
](element
, name
);
1784 if (t
.names
[name
]) name
= t
.names
[name
];
1785 if (name
.include(':')) {
1786 return (!element
.attributes
|| !element
.attributes
[name
]) ? null :
1787 element
.attributes
[name
].value
;
1790 return element
.getAttribute(name
);
1793 writeAttribute: function(element
, name
, value
) {
1794 element
= $(element
);
1795 var attributes
= { }, t
= Element
._attributeTranslations
.write
;
1797 if (typeof name
== 'object') attributes
= name
;
1798 else attributes
[name
] = value
=== undefined ? true : value
;
1800 for (var attr
in attributes
) {
1801 var name
= t
.names
[attr
] || attr
, value
= attributes
[attr
];
1802 if (t
.values
[attr
]) name
= t
.values
[attr
](element
, value
);
1803 if (value
=== false || value
=== null)
1804 element
.removeAttribute(name
);
1805 else if (value
=== true)
1806 element
.setAttribute(name
, name
);
1807 else element
.setAttribute(name
, value
);
1812 getHeight: function(element
) {
1813 return $(element
).getDimensions().height
;
1816 getWidth: function(element
) {
1817 return $(element
).getDimensions().width
;
1820 classNames: function(element
) {
1821 return new Element
.ClassNames(element
);
1824 hasClassName: function(element
, className
) {
1825 if (!(element
= $(element
))) return;
1826 var elementClassName
= element
.className
;
1827 return (elementClassName
.length
> 0 && (elementClassName
== className
||
1828 new RegExp("(^|\\s)" + className
+ "(\\s|$)").test(elementClassName
)));
1831 addClassName: function(element
, className
) {
1832 if (!(element
= $(element
))) return;
1833 if (!element
.hasClassName(className
))
1834 element
.className
+= (element
.className
? ' ' : '') + className
;
1838 removeClassName: function(element
, className
) {
1839 if (!(element
= $(element
))) return;
1840 element
.className
= element
.className
.replace(
1841 new RegExp("(^|\\s+)" + className
+ "(\\s+|$)"), ' ').strip();
1845 toggleClassName: function(element
, className
) {
1846 if (!(element
= $(element
))) return;
1847 return element
[element
.hasClassName(className
) ?
1848 'removeClassName' : 'addClassName'](className
);
1851 // removes whitespace-only text node children
1852 cleanWhitespace: function(element
) {
1853 element
= $(element
);
1854 var node
= element
.firstChild
;
1856 var nextNode
= node
.nextSibling
;
1857 if (node
.nodeType
== 3 && !/\S/.test(node
.nodeValue
))
1858 element
.removeChild(node
);
1864 empty: function(element
) {
1865 return $(element
).innerHTML
.blank();
1868 descendantOf: function(element
, ancestor
) {
1869 element
= $(element
), ancestor
= $(ancestor
);
1871 if (element
.compareDocumentPosition
)
1872 return (element
.compareDocumentPosition(ancestor
) & 8) === 8;
1874 if (element
.sourceIndex
&& !Prototype
.Browser
.Opera
) {
1875 var e
= element
.sourceIndex
, a
= ancestor
.sourceIndex
,
1876 nextAncestor
= ancestor
.nextSibling
;
1877 if (!nextAncestor
) {
1878 do { ancestor
= ancestor
.parentNode
; }
1879 while (!(nextAncestor
= ancestor
.nextSibling
) && ancestor
.parentNode
);
1881 if (nextAncestor
) return (e
> a
&& e
< nextAncestor
.sourceIndex
);
1884 while (element
= element
.parentNode
)
1885 if (element
== ancestor
) return true;
1889 scrollTo: function(element
) {
1890 element
= $(element
);
1891 var pos
= element
.cumulativeOffset();
1892 window
.scrollTo(pos
[0], pos
[1]);
1896 getStyle: function(element
, style
) {
1897 element
= $(element
);
1898 style
= style
== 'float' ? 'cssFloat' : style
.camelize();
1899 var value
= element
.style
[style
];
1901 var css
= document
.defaultView
.getComputedStyle(element
, null);
1902 value
= css
? css
[style
] : null;
1904 if (style
== 'opacity') return value
? parseFloat(value
) : 1.0;
1905 return value
== 'auto' ? null : value
;
1908 getOpacity: function(element
) {
1909 return $(element
).getStyle('opacity');
1912 setStyle: function(element
, styles
) {
1913 element
= $(element
);
1914 var elementStyle
= element
.style
, match
;
1915 if (Object
.isString(styles
)) {
1916 element
.style
.cssText
+= ';' + styles
;
1917 return styles
.include('opacity') ?
1918 element
.setOpacity(styles
.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element
;
1920 for (var property
in styles
)
1921 if (property
== 'opacity') element
.setOpacity(styles
[property
]);
1923 elementStyle
[(property
== 'float' || property
== 'cssFloat') ?
1924 (elementStyle
.styleFloat
=== undefined ? 'cssFloat' : 'styleFloat') :
1925 property
] = styles
[property
];
1930 setOpacity: function(element
, value
) {
1931 element
= $(element
);
1932 element
.style
.opacity
= (value
== 1 || value
=== '') ? '' :
1933 (value
< 0.00001) ? 0 : value
;
1937 getDimensions: function(element
) {
1938 element
= $(element
);
1939 var display
= $(element
).getStyle('display');
1940 if (display
!= 'none' && display
!= null) // Safari bug
1941 return {width
: element
.offsetWidth
, height
: element
.offsetHeight
};
1943 // All *Width and *Height properties give 0 on elements with display none,
1944 // so enable the element temporarily
1945 var els
= element
.style
;
1946 var originalVisibility
= els
.visibility
;
1947 var originalPosition
= els
.position
;
1948 var originalDisplay
= els
.display
;
1949 els
.visibility
= 'hidden';
1950 els
.position
= 'absolute';
1951 els
.display
= 'block';
1952 var originalWidth
= element
.clientWidth
;
1953 var originalHeight
= element
.clientHeight
;
1954 els
.display
= originalDisplay
;
1955 els
.position
= originalPosition
;
1956 els
.visibility
= originalVisibility
;
1957 return {width
: originalWidth
, height
: originalHeight
};
1960 makePositioned: function(element
) {
1961 element
= $(element
);
1962 var pos
= Element
.getStyle(element
, 'position');
1963 if (pos
== 'static' || !pos
) {
1964 element
._madePositioned
= true;
1965 element
.style
.position
= 'relative';
1966 // Opera returns the offset relative to the positioning context, when an
1967 // element is position relative but top and left have not been defined
1969 element
.style
.top
= 0;
1970 element
.style
.left
= 0;
1976 undoPositioned: function(element
) {
1977 element
= $(element
);
1978 if (element
._madePositioned
) {
1979 element
._madePositioned
= undefined;
1980 element
.style
.position
=
1982 element
.style
.left
=
1983 element
.style
.bottom
=
1984 element
.style
.right
= '';
1989 makeClipping: function(element
) {
1990 element
= $(element
);
1991 if (element
._overflow
) return element
;
1992 element
._overflow
= Element
.getStyle(element
, 'overflow') || 'auto';
1993 if (element
._overflow
!== 'hidden')
1994 element
.style
.overflow
= 'hidden';
1998 undoClipping: function(element
) {
1999 element
= $(element
);
2000 if (!element
._overflow
) return element
;
2001 element
.style
.overflow
= element
._overflow
== 'auto' ? '' : element
._overflow
;
2002 element
._overflow
= null;
2006 cumulativeOffset: function(element
) {
2007 var valueT
= 0, valueL
= 0;
2009 valueT
+= element
.offsetTop
|| 0;
2010 valueL
+= element
.offsetLeft
|| 0;
2011 element
= element
.offsetParent
;
2013 return Element
._returnOffset(valueL
, valueT
);
2016 positionedOffset: function(element
) {
2017 var valueT
= 0, valueL
= 0;
2019 valueT
+= element
.offsetTop
|| 0;
2020 valueL
+= element
.offsetLeft
|| 0;
2021 element
= element
.offsetParent
;
2023 if (element
.tagName
== 'BODY') break;
2024 var p
= Element
.getStyle(element
, 'position');
2025 if (p
== 'relative' || p
== 'absolute') break;
2028 return Element
._returnOffset(valueL
, valueT
);
2031 absolutize: function(element
) {
2032 element
= $(element
);
2033 if (element
.getStyle('position') == 'absolute') return;
2034 // Position.prepare(); // To be done manually by Scripty when it needs it.
2036 var offsets
= element
.positionedOffset();
2037 var top
= offsets
[1];
2038 var left
= offsets
[0];
2039 var width
= element
.clientWidth
;
2040 var height
= element
.clientHeight
;
2042 element
._originalLeft
= left
- parseFloat(element
.style
.left
|| 0);
2043 element
._originalTop
= top
- parseFloat(element
.style
.top
|| 0);
2044 element
._originalWidth
= element
.style
.width
;
2045 element
._originalHeight
= element
.style
.height
;
2047 element
.style
.position
= 'absolute';
2048 element
.style
.top
= top
+ 'px';
2049 element
.style
.left
= left
+ 'px';
2050 element
.style
.width
= width
+ 'px';
2051 element
.style
.height
= height
+ 'px';
2055 relativize: function(element
) {
2056 element
= $(element
);
2057 if (element
.getStyle('position') == 'relative') return;
2058 // Position.prepare(); // To be done manually by Scripty when it needs it.
2060 element
.style
.position
= 'relative';
2061 var top
= parseFloat(element
.style
.top
|| 0) - (element
._originalTop
|| 0);
2062 var left
= parseFloat(element
.style
.left
|| 0) - (element
._originalLeft
|| 0);
2064 element
.style
.top
= top
+ 'px';
2065 element
.style
.left
= left
+ 'px';
2066 element
.style
.height
= element
._originalHeight
;
2067 element
.style
.width
= element
._originalWidth
;
2071 cumulativeScrollOffset: function(element
) {
2072 var valueT
= 0, valueL
= 0;
2074 valueT
+= element
.scrollTop
|| 0;
2075 valueL
+= element
.scrollLeft
|| 0;
2076 element
= element
.parentNode
;
2078 return Element
._returnOffset(valueL
, valueT
);
2081 getOffsetParent: function(element
) {
2082 if (element
.offsetParent
) return $(element
.offsetParent
);
2083 if (element
== document
.body
) return $(element
);
2085 while ((element
= element
.parentNode
) && element
!= document
.body
)
2086 if (Element
.getStyle(element
, 'position') != 'static')
2089 return $(document
.body
);
2092 viewportOffset: function(forElement
) {
2093 var valueT
= 0, valueL
= 0;
2095 var element
= forElement
;
2097 valueT
+= element
.offsetTop
|| 0;
2098 valueL
+= element
.offsetLeft
|| 0;
2101 if (element
.offsetParent
== document
.body
&&
2102 Element
.getStyle(element
, 'position') == 'absolute') break;
2104 } while (element
= element
.offsetParent
);
2106 element
= forElement
;
2108 if (!Prototype
.Browser
.Opera
|| element
.tagName
== 'BODY') {
2109 valueT
-= element
.scrollTop
|| 0;
2110 valueL
-= element
.scrollLeft
|| 0;
2112 } while (element
= element
.parentNode
);
2114 return Element
._returnOffset(valueL
, valueT
);
2117 clonePosition: function(element
, source
) {
2118 var options
= Object
.extend({
2125 }, arguments
[2] || { });
2127 // find page position of source
2129 var p
= source
.viewportOffset();
2131 // find coordinate system to use
2132 element
= $(element
);
2135 // delta [0,0] will do fine with position: fixed elements,
2136 // position:absolute needs offsetParent deltas
2137 if (Element
.getStyle(element
, 'position') == 'absolute') {
2138 parent
= element
.getOffsetParent();
2139 delta
= parent
.viewportOffset();
2142 // correct by body offsets (fixes Safari)
2143 if (parent
== document
.body
) {
2144 delta
[0] -= document
.body
.offsetLeft
;
2145 delta
[1] -= document
.body
.offsetTop
;
2149 if (options
.setLeft
) element
.style
.left
= (p
[0] - delta
[0] + options
.offsetLeft
) + 'px';
2150 if (options
.setTop
) element
.style
.top
= (p
[1] - delta
[1] + options
.offsetTop
) + 'px';
2151 if (options
.setWidth
) element
.style
.width
= source
.offsetWidth
+ 'px';
2152 if (options
.setHeight
) element
.style
.height
= source
.offsetHeight
+ 'px';
2157 Element
.Methods
.identify
.counter
= 1;
2159 Object
.extend(Element
.Methods
, {
2160 getElementsBySelector
: Element
.Methods
.select
,
2161 childElements
: Element
.Methods
.immediateDescendants
2164 Element
._attributeTranslations
= {
2175 if (!document
.createRange
|| Prototype
.Browser
.Opera
) {
2176 Element
.Methods
.insert = function(element
, insertions
) {
2177 element
= $(element
);
2179 if (Object
.isString(insertions
) || Object
.isNumber(insertions
) ||
2180 Object
.isElement(insertions
) || (insertions
&& (insertions
.toElement
|| insertions
.toHTML
)))
2181 insertions
= { bottom
: insertions
};
2183 var t
= Element
._insertionTranslations
, content
, position
, pos
, tagName
;
2185 for (position
in insertions
) {
2186 content
= insertions
[position
];
2187 position
= position
.toLowerCase();
2190 if (content
&& content
.toElement
) content
= content
.toElement();
2191 if (Object
.isElement(content
)) {
2192 pos
.insert(element
, content
);
2196 content
= Object
.toHTML(content
);
2197 tagName
= ((position
== 'before' || position
== 'after')
2198 ? element
.parentNode
: element
).tagName
.toUpperCase();
2200 if (t
.tags
[tagName
]) {
2201 var fragments
= Element
._getContentFromAnonymousElement(tagName
, content
.stripScripts());
2202 if (position
== 'top' || position
== 'after') fragments
.reverse();
2203 fragments
.each(pos
.insert
.curry(element
));
2205 else element
.insertAdjacentHTML(pos
.adjacency
, content
.stripScripts());
2207 content
.evalScripts
.bind(content
).defer();
2214 if (Prototype
.Browser
.Opera
) {
2215 Element
.Methods
._getStyle
= Element
.Methods
.getStyle
;
2216 Element
.Methods
.getStyle = function(element
, style
) {
2222 if (Element
._getStyle(element
, 'position') == 'static') return null;
2223 default: return Element
._getStyle(element
, style
);
2226 Element
.Methods
._readAttribute
= Element
.Methods
.readAttribute
;
2227 Element
.Methods
.readAttribute = function(element
, attribute
) {
2228 if (attribute
== 'title') return element
.title
;
2229 return Element
._readAttribute(element
, attribute
);
2233 else if (Prototype
.Browser
.IE
) {
2234 $w('positionedOffset getOffsetParent viewportOffset').each(function(method
) {
2235 Element
.Methods
[method
] = Element
.Methods
[method
].wrap(
2236 function(proceed
, element
) {
2237 element
= $(element
);
2238 var position
= element
.getStyle('position');
2239 if (position
!= 'static') return proceed(element
);
2240 element
.setStyle({ position
: 'relative' });
2241 var value
= proceed(element
);
2242 element
.setStyle({ position
: position
});
2248 Element
.Methods
.getStyle = function(element
, style
) {
2249 element
= $(element
);
2250 style
= (style
== 'float' || style
== 'cssFloat') ? 'styleFloat' : style
.camelize();
2251 var value
= element
.style
[style
];
2252 if (!value
&& element
.currentStyle
) value
= element
.currentStyle
[style
];
2254 if (style
== 'opacity') {
2255 if (value
= (element
.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
2256 if (value
[1]) return parseFloat(value
[1]) / 100;
2260 if (value
== 'auto') {
2261 if ((style
== 'width' || style
== 'height') && (element
.getStyle('display') != 'none'))
2262 return element
['offset' + style
.capitalize()] + 'px';
2268 Element
.Methods
.setOpacity = function(element
, value
) {
2269 function stripAlpha(filter
){
2270 return filter
.replace(/alpha\([^\)]*\)/gi,'');
2272 element
= $(element
);
2273 var currentStyle
= element
.currentStyle
;
2274 if ((currentStyle
&& !currentStyle
.hasLayout
) ||
2275 (!currentStyle
&& element
.style
.zoom
== 'normal'))
2276 element
.style
.zoom
= 1;
2278 var filter
= element
.getStyle('filter'), style
= element
.style
;
2279 if (value
== 1 || value
=== '') {
2280 (filter
= stripAlpha(filter
)) ?
2281 style
.filter
= filter
: style
.removeAttribute('filter');
2283 } else if (value
< 0.00001) value
= 0;
2284 style
.filter
= stripAlpha(filter
) +
2285 'alpha(opacity=' + (value
* 100) + ')';
2289 Element
._attributeTranslations
= {
2292 'class': 'className',
2296 _getAttr: function(element
, attribute
) {
2297 return element
.getAttribute(attribute
, 2);
2299 _getAttrNode: function(element
, attribute
) {
2300 var node
= element
.getAttributeNode(attribute
);
2301 return node
? node
.value
: "";
2303 _getEv: function(element
, attribute
) {
2304 var attribute
= element
.getAttribute(attribute
);
2305 return attribute
? attribute
.toString().slice(23, -2) : null;
2307 _flag: function(element
, attribute
) {
2308 return $(element
).hasAttribute(attribute
) ? attribute
: null;
2310 style: function(element
) {
2311 return element
.style
.cssText
.toLowerCase();
2313 title: function(element
) {
2314 return element
.title
;
2320 Element
._attributeTranslations
.write
= {
2321 names
: Object
.clone(Element
._attributeTranslations
.read
.names
),
2323 checked: function(element
, value
) {
2324 element
.checked
= !!value
;
2327 style: function(element
, value
) {
2328 element
.style
.cssText
= value
? value
: '';
2333 Element
._attributeTranslations
.has
= {};
2335 $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
2336 'encType maxLength readOnly longDesc').each(function(attr
) {
2337 Element
._attributeTranslations
.write
.names
[attr
.toLowerCase()] = attr
;
2338 Element
._attributeTranslations
.has
[attr
.toLowerCase()] = attr
;
2346 action
: v
._getAttrNode
,
2354 ondblclick
: v
._getEv
,
2355 onmousedown
: v
._getEv
,
2356 onmouseup
: v
._getEv
,
2357 onmouseover
: v
._getEv
,
2358 onmousemove
: v
._getEv
,
2359 onmouseout
: v
._getEv
,
2362 onkeypress
: v
._getEv
,
2363 onkeydown
: v
._getEv
,
2370 })(Element
._attributeTranslations
.read
.values
);
2373 else if (Prototype
.Browser
.Gecko
&& /rv:1\.8\.0/.test(navigator
.userAgent
)) {
2374 Element
.Methods
.setOpacity = function(element
, value
) {
2375 element
= $(element
);
2376 element
.style
.opacity
= (value
== 1) ? 0.999999 :
2377 (value
=== '') ? '' : (value
< 0.00001) ? 0 : value
;
2382 else if (Prototype
.Browser
.WebKit
) {
2383 Element
.Methods
.setOpacity = function(element
, value
) {
2384 element
= $(element
);
2385 element
.style
.opacity
= (value
== 1 || value
=== '') ? '' :
2386 (value
< 0.00001) ? 0 : value
;
2389 if(element
.tagName
== 'IMG' && element
.width
) {
2390 element
.width
++; element
.width
--;
2392 var n
= document
.createTextNode(' ');
2393 element
.appendChild(n
);
2394 element
.removeChild(n
);
2400 // Safari returns margins on body which is incorrect if the child is absolutely
2401 // positioned. For performance reasons, redefine Position.cumulativeOffset for
2402 // KHTML/WebKit only.
2403 Element
.Methods
.cumulativeOffset = function(element
) {
2404 var valueT
= 0, valueL
= 0;
2406 valueT
+= element
.offsetTop
|| 0;
2407 valueL
+= element
.offsetLeft
|| 0;
2408 if (element
.offsetParent
== document
.body
)
2409 if (Element
.getStyle(element
, 'position') == 'absolute') break;
2411 element
= element
.offsetParent
;
2414 return Element
._returnOffset(valueL
, valueT
);
2418 if (Prototype
.Browser
.IE
|| Prototype
.Browser
.Opera
) {
2419 // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
2420 Element
.Methods
.update = function(element
, content
) {
2421 element
= $(element
);
2423 if (content
&& content
.toElement
) content
= content
.toElement();
2424 if (Object
.isElement(content
)) return element
.update().insert(content
);
2426 content
= Object
.toHTML(content
);
2427 var tagName
= element
.tagName
.toUpperCase();
2429 if (tagName
in Element
._insertionTranslations
.tags
) {
2430 $A(element
.childNodes
).each(function(node
) { element
.removeChild(node
) });
2431 Element
._getContentFromAnonymousElement(tagName
, content
.stripScripts())
2432 .each(function(node
) { element
.appendChild(node
) });
2434 else element
.innerHTML
= content
.stripScripts();
2436 content
.evalScripts
.bind(content
).defer();
2441 if (document
.createElement('div').outerHTML
) {
2442 Element
.Methods
.replace = function(element
, content
) {
2443 element
= $(element
);
2445 if (content
&& content
.toElement
) content
= content
.toElement();
2446 if (Object
.isElement(content
)) {
2447 element
.parentNode
.replaceChild(content
, element
);
2451 content
= Object
.toHTML(content
);
2452 var parent
= element
.parentNode
, tagName
= parent
.tagName
.toUpperCase();
2454 if (Element
._insertionTranslations
.tags
[tagName
]) {
2455 var nextSibling
= element
.next();
2456 var fragments
= Element
._getContentFromAnonymousElement(tagName
, content
.stripScripts());
2457 parent
.removeChild(element
);
2459 fragments
.each(function(node
) { parent
.insertBefore(node
, nextSibling
) });
2461 fragments
.each(function(node
) { parent
.appendChild(node
) });
2463 else element
.outerHTML
= content
.stripScripts();
2465 content
.evalScripts
.bind(content
).defer();
2470 Element
._returnOffset = function(l
, t
) {
2471 var result
= [l
, t
];
2477 Element
._getContentFromAnonymousElement = function(tagName
, html
) {
2478 var div
= new Element('div'), t
= Element
._insertionTranslations
.tags
[tagName
];
2479 div
.innerHTML
= t
[0] + html
+ t
[1];
2480 t
[2].times(function() { div
= div
.firstChild
});
2481 return $A(div
.childNodes
);
2484 Element
._insertionTranslations
= {
2486 adjacency
: 'beforeBegin',
2487 insert: function(element
, node
) {
2488 element
.parentNode
.insertBefore(node
, element
);
2490 initializeRange: function(element
, range
) {
2491 range
.setStartBefore(element
);
2495 adjacency
: 'afterBegin',
2496 insert: function(element
, node
) {
2497 element
.insertBefore(node
, element
.firstChild
);
2499 initializeRange: function(element
, range
) {
2500 range
.selectNodeContents(element
);
2501 range
.collapse(true);
2505 adjacency
: 'beforeEnd',
2506 insert: function(element
, node
) {
2507 element
.appendChild(node
);
2511 adjacency
: 'afterEnd',
2512 insert: function(element
, node
) {
2513 element
.parentNode
.insertBefore(node
, element
.nextSibling
);
2515 initializeRange: function(element
, range
) {
2516 range
.setStartAfter(element
);
2520 TABLE
: ['<table>', '</table>', 1],
2521 TBODY
: ['<table><tbody>', '</tbody></table>', 2],
2522 TR
: ['<table><tbody><tr>', '</tr></tbody></table>', 3],
2523 TD
: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
2524 SELECT
: ['<select>', '</select>', 1]
2529 this.bottom
.initializeRange
= this.top
.initializeRange
;
2530 Object
.extend(this.tags
, {
2531 THEAD
: this.tags
.TBODY
,
2532 TFOOT
: this.tags
.TBODY
,
2535 }).call(Element
._insertionTranslations
);
2537 Element
.Methods
.Simulated
= {
2538 hasAttribute: function(element
, attribute
) {
2539 attribute
= Element
._attributeTranslations
.has
[attribute
] || attribute
;
2540 var node
= $(element
).getAttributeNode(attribute
);
2541 return node
&& node
.specified
;
2545 Element
.Methods
.ByTag
= { };
2547 Object
.extend(Element
, Element
.Methods
);
2549 if (!Prototype
.BrowserFeatures
.ElementExtensions
&&
2550 document
.createElement('div').__proto__
) {
2551 window
.HTMLElement
= { };
2552 window
.HTMLElement
.prototype = document
.createElement('div').__proto__
;
2553 Prototype
.BrowserFeatures
.ElementExtensions
= true;
2556 Element
.extend
= (function() {
2557 if (Prototype
.BrowserFeatures
.SpecificElementExtensions
)
2560 var Methods
= { }, ByTag
= Element
.Methods
.ByTag
;
2562 var extend
= Object
.extend(function(element
) {
2563 if (!element
|| element
._extendedByPrototype
||
2564 element
.nodeType
!= 1 || element
== window
) return element
;
2566 var methods
= Object
.clone(Methods
),
2567 tagName
= element
.tagName
, property
, value
;
2569 // extend methods for specific tags
2570 if (ByTag
[tagName
]) Object
.extend(methods
, ByTag
[tagName
]);
2572 for (property
in methods
) {
2573 value
= methods
[property
];
2574 if (Object
.isFunction(value
) && !(property
in element
))
2575 element
[property
] = value
.methodize();
2578 element
._extendedByPrototype
= Prototype
.emptyFunction
;
2582 refresh: function() {
2583 // extend methods for all tags (Safari doesn't need this)
2584 if (!Prototype
.BrowserFeatures
.ElementExtensions
) {
2585 Object
.extend(Methods
, Element
.Methods
);
2586 Object
.extend(Methods
, Element
.Methods
.Simulated
);
2595 Element
.hasAttribute = function(element
, attribute
) {
2596 if (element
.hasAttribute
) return element
.hasAttribute(attribute
);
2597 return Element
.Methods
.Simulated
.hasAttribute(element
, attribute
);
2600 Element
.addMethods = function(methods
) {
2601 var F
= Prototype
.BrowserFeatures
, T
= Element
.Methods
.ByTag
;
2604 Object
.extend(Form
, Form
.Methods
);
2605 Object
.extend(Form
.Element
, Form
.Element
.Methods
);
2606 Object
.extend(Element
.Methods
.ByTag
, {
2607 "FORM": Object
.clone(Form
.Methods
),
2608 "INPUT": Object
.clone(Form
.Element
.Methods
),
2609 "SELECT": Object
.clone(Form
.Element
.Methods
),
2610 "TEXTAREA": Object
.clone(Form
.Element
.Methods
)
2614 if (arguments
.length
== 2) {
2615 var tagName
= methods
;
2616 methods
= arguments
[1];
2619 if (!tagName
) Object
.extend(Element
.Methods
, methods
|| { });
2621 if (Object
.isArray(tagName
)) tagName
.each(extend
);
2622 else extend(tagName
);
2625 function extend(tagName
) {
2626 tagName
= tagName
.toUpperCase();
2627 if (!Element
.Methods
.ByTag
[tagName
])
2628 Element
.Methods
.ByTag
[tagName
] = { };
2629 Object
.extend(Element
.Methods
.ByTag
[tagName
], methods
);
2632 function copy(methods
, destination
, onlyIfAbsent
) {
2633 onlyIfAbsent
= onlyIfAbsent
|| false;
2634 for (var property
in methods
) {
2635 var value
= methods
[property
];
2636 if (!Object
.isFunction(value
)) continue;
2637 if (!onlyIfAbsent
|| !(property
in destination
))
2638 destination
[property
] = value
.methodize();
2642 function findDOMClass(tagName
) {
2645 "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
2646 "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
2647 "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
2648 "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
2649 "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
2650 "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
2651 "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
2652 "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
2653 "FrameSet", "IFRAME": "IFrame"
2655 if (trans
[tagName
]) klass
= 'HTML' + trans
[tagName
] + 'Element';
2656 if (window
[klass
]) return window
[klass
];
2657 klass
= 'HTML' + tagName
+ 'Element';
2658 if (window
[klass
]) return window
[klass
];
2659 klass
= 'HTML' + tagName
.capitalize() + 'Element';
2660 if (window
[klass
]) return window
[klass
];
2662 window
[klass
] = { };
2663 window
[klass
].prototype = document
.createElement(tagName
).__proto__
;
2664 return window
[klass
];
2667 if (F
.ElementExtensions
) {
2668 copy(Element
.Methods
, HTMLElement
.prototype);
2669 copy(Element
.Methods
.Simulated
, HTMLElement
.prototype, true);
2672 if (F
.SpecificElementExtensions
) {
2673 for (var tag
in Element
.Methods
.ByTag
) {
2674 var klass
= findDOMClass(tag
);
2675 if (Object
.isUndefined(klass
)) continue;
2676 copy(T
[tag
], klass
.prototype);
2680 Object
.extend(Element
, Element
.Methods
);
2681 delete Element
.ByTag
;
2683 if (Element
.extend
.refresh
) Element
.extend
.refresh();
2684 Element
.cache
= { };
2687 document
.viewport
= {
2688 getDimensions: function() {
2689 var dimensions
= { };
2690 $w('width height').each(function(d
) {
2691 var D
= d
.capitalize();
2692 dimensions
[d
] = self
['inner' + D
] ||
2693 (document
.documentElement
['client' + D
] || document
.body
['client' + D
]);
2698 getWidth: function() {
2699 return this.getDimensions().width
;
2702 getHeight: function() {
2703 return this.getDimensions().height
;
2706 getScrollOffsets: function() {
2707 return Element
._returnOffset(
2708 window
.pageXOffset
|| document
.documentElement
.scrollLeft
|| document
.body
.scrollLeft
,
2709 window
.pageYOffset
|| document
.documentElement
.scrollTop
|| document
.body
.scrollTop
);
2712 /* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
2713 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
2714 * license. Please see http://www.yui-ext.com/ for more information. */
2716 var Selector
= Class
.create({
2717 initialize: function(expression
) {
2718 this.expression
= expression
.strip();
2719 this.compileMatcher();
2722 compileMatcher: function() {
2723 // Selectors with namespaced attributes can't use the XPath version
2724 if (Prototype
.BrowserFeatures
.XPath
&& !(/(\[[\w-]*?:|:checked)/).test(this.expression
))
2725 return this.compileXPathMatcher();
2727 var e
= this.expression
, ps
= Selector
.patterns
, h
= Selector
.handlers
,
2728 c
= Selector
.criteria
, le
, p
, m
;
2730 if (Selector
._cache
[e
]) {
2731 this.matcher
= Selector
._cache
[e
];
2735 this.matcher
= ["this.matcher = function(root) {",
2736 "var r = root, h = Selector.handlers, c = false, n;"];
2738 while (e
&& le
!= e
&& (/\S/).test(e
)) {
2742 if (m
= e
.match(p
)) {
2743 this.matcher
.push(Object
.isFunction(c
[i
]) ? c
[i
](m
) :
2744 new Template(c
[i
]).evaluate(m
));
2745 e
= e
.replace(m
[0], '');
2751 this.matcher
.push("return h.unique(n);\n}");
2752 eval(this.matcher
.join('\n'));
2753 Selector
._cache
[this.expression
] = this.matcher
;
2756 compileXPathMatcher: function() {
2757 var e
= this.expression
, ps
= Selector
.patterns
,
2758 x
= Selector
.xpath
, le
, m
;
2760 if (Selector
._cache
[e
]) {
2761 this.xpath
= Selector
._cache
[e
]; return;
2764 this.matcher
= ['.//*'];
2765 while (e
&& le
!= e
&& (/\S/).test(e
)) {
2768 if (m
= e
.match(ps
[i
])) {
2769 this.matcher
.push(Object
.isFunction(x
[i
]) ? x
[i
](m
) :
2770 new Template(x
[i
]).evaluate(m
));
2771 e
= e
.replace(m
[0], '');
2777 this.xpath
= this.matcher
.join('');
2778 Selector
._cache
[this.expression
] = this.xpath
;
2781 findElements: function(root
) {
2782 root
= root
|| document
;
2783 if (this.xpath
) return document
._getElementsByXPath(this.xpath
, root
);
2784 return this.matcher(root
);
2787 match: function(element
) {
2790 var e
= this.expression
, ps
= Selector
.patterns
, as
= Selector
.assertions
;
2793 while (e
&& le
!== e
&& (/\S/).test(e
)) {
2797 if (m
= e
.match(p
)) {
2798 // use the Selector.assertions methods unless the selector
2801 this.tokens
.push([i
, Object
.clone(m
)]);
2802 e
= e
.replace(m
[0], '');
2804 // reluctantly do a document-wide search
2805 // and look for a match in the array
2806 return this.findElements(document
).include(element
);
2812 var match
= true, name
, matches
;
2813 for (var i
= 0, token
; token
= this.tokens
[i
]; i
++) {
2814 name
= token
[0], matches
= token
[1];
2815 if (!Selector
.assertions
[name
](element
, matches
)) {
2816 match
= false; break;
2823 toString: function() {
2824 return this.expression
;
2827 inspect: function() {
2828 return "#<Selector:" + this.expression
.inspect() + ">";
2832 Object
.extend(Selector
, {
2838 adjacent
: "/following-sibling::*[1]",
2839 laterSibling
: '/following-sibling::*',
2840 tagName: function(m
) {
2841 if (m
[1] == '*') return '';
2842 return "[local-name()='" + m
[1].toLowerCase() +
2843 "' or local-name()='" + m
[1].toUpperCase() + "']";
2845 className
: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
2847 attrPresence
: "[@#{1}]",
2849 m
[3] = m
[5] || m
[6];
2850 return new Template(Selector
.xpath
.operators
[m
[2]]).evaluate(m
);
2852 pseudo: function(m
) {
2853 var h
= Selector
.xpath
.pseudos
[m
[1]];
2855 if (Object
.isFunction(h
)) return h(m
);
2856 return new Template(Selector
.xpath
.pseudos
[m
[1]]).evaluate(m
);
2859 '=': "[@#{1}='#{3}']",
2860 '!=': "[@#{1}!='#{3}']",
2861 '^=': "[starts-with(@#{1}, '#{3}')]",
2862 '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
2863 '*=': "[contains(@#{1}, '#{3}')]",
2864 '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
2865 '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
2868 'first-child': '[not(preceding-sibling::*)]',
2869 'last-child': '[not(following-sibling::*)]',
2870 'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
2871 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
2872 'checked': "[@checked]",
2873 'disabled': "[@disabled]",
2874 'enabled': "[not(@disabled)]",
2875 'not': function(m
) {
2876 var e
= m
[6], p
= Selector
.patterns
,
2877 x
= Selector
.xpath
, le
, m
, v
;
2880 while (e
&& le
!= e
&& (/\S/).test(e
)) {
2883 if (m
= e
.match(p
[i
])) {
2884 v
= Object
.isFunction(x
[i
]) ? x
[i
](m
) : new Template(x
[i
]).evaluate(m
);
2885 exclusion
.push("(" + v
.substring(1, v
.length
- 1) + ")");
2886 e
= e
.replace(m
[0], '');
2891 return "[not(" + exclusion
.join(" and ") + ")]";
2893 'nth-child': function(m
) {
2894 return Selector
.xpath
.pseudos
.nth("(count(./preceding-sibling::*) + 1) ", m
);
2896 'nth-last-child': function(m
) {
2897 return Selector
.xpath
.pseudos
.nth("(count(./following-sibling::*) + 1) ", m
);
2899 'nth-of-type': function(m
) {
2900 return Selector
.xpath
.pseudos
.nth("position() ", m
);
2902 'nth-last-of-type': function(m
) {
2903 return Selector
.xpath
.pseudos
.nth("(last() + 1 - position()) ", m
);
2905 'first-of-type': function(m
) {
2906 m
[6] = "1"; return Selector
.xpath
.pseudos
['nth-of-type'](m
);
2908 'last-of-type': function(m
) {
2909 m
[6] = "1"; return Selector
.xpath
.pseudos
['nth-last-of-type'](m
);
2911 'only-of-type': function(m
) {
2912 var p
= Selector
.xpath
.pseudos
; return p
['first-of-type'](m
) + p
['last-of-type'](m
);
2914 nth: function(fragment
, m
) {
2915 var mm
, formula
= m
[6], predicate
;
2916 if (formula
== 'even') formula
= '2n+0';
2917 if (formula
== 'odd') formula
= '2n+1';
2918 if (mm
= formula
.match(/^(\d+)$/)) // digit only
2919 return '[' + fragment
+ "= " + mm
[1] + ']';
2920 if (mm
= formula
.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
2921 if (mm
[1] == "-") mm
[1] = -1;
2922 var a
= mm
[1] ? Number(mm
[1]) : 1;
2923 var b
= mm
[2] ? Number(mm
[2]) : 0;
2924 predicate
= "[((#{fragment} - #{b}) mod #{a} = 0) and " +
2925 "((#{fragment} - #{b}) div #{a} >= 0)]";
2926 return new Template(predicate
).evaluate({
2927 fragment
: fragment
, a
: a
, b
: b
});
2934 tagName
: 'n = h.tagName(n, r, "#{1}", c); c = false;',
2935 className
: 'n = h.className(n, r, "#{1}", c); c = false;',
2936 id
: 'n = h.id(n, r, "#{1}", c); c = false;',
2937 attrPresence
: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
2939 m
[3] = (m
[5] || m
[6]);
2940 return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m
);
2942 pseudo: function(m
) {
2943 if (m
[6]) m
[6] = m
[6].replace(/"/g, '\\"');
2944 return new Template('n
= h
.pseudo(n
, "#{1}", "#{6}", r
, c
); c
= false;').evaluate(m);
2946 descendant: 'c
= "descendant";',
2947 child: 'c
= "child";',
2948 adjacent: 'c
= "adjacent";',
2949 laterSibling: 'c
= "laterSibling";'
2953 // combinators must be listed first
2954 // (and descendant needs to be last combinator)
2955 laterSibling: /^\s*~\s*/,
2957 adjacent: /^\s*\+\s*/,
2961 tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
2962 id: /^#([\w\-\*]+)(\b|$)/,
2963 className: /^\.([\w\-\*]+)(\b|$)/,
2964 pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/,
2965 attrPresence: /^\[([\w]+)\]/,
2966 attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
2969 // for Selector.match and Element#match
2971 tagName: function(element
, matches
) {
2972 return matches
[1].toUpperCase() == element
.tagName
.toUpperCase();
2975 className: function(element
, matches
) {
2976 return Element
.hasClassName(element
, matches
[1]);
2979 id: function(element
, matches
) {
2980 return element
.id
=== matches
[1];
2983 attrPresence: function(element
, matches
) {
2984 return Element
.hasAttribute(element
, matches
[1]);
2987 attr: function(element
, matches
) {
2988 var nodeValue
= Element
.readAttribute(element
, matches
[1]);
2989 return Selector
.operators
[matches
[2]](nodeValue
, matches
[3]);
2994 // UTILITY FUNCTIONS
2995 // joins two collections
2996 concat: function(a
, b
) {
2997 for (var i
= 0, node
; node
= b
[i
]; i
++)
3002 // marks an array of nodes for counting
3003 mark: function(nodes
) {
3004 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3005 node
._counted
= true;
3009 unmark: function(nodes
) {
3010 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3011 node
._counted
= undefined;
3015 // mark each child node with its position (for nth calls)
3016 // "ofType" flag indicates whether we're indexing for nth-of-type
3017 // rather than nth-child
3018 index: function(parentNode
, reverse
, ofType
) {
3019 parentNode
._counted
= true;
3021 for (var nodes
= parentNode
.childNodes
, i
= nodes
.length
- 1, j
= 1; i
>= 0; i
--) {
3022 var node
= nodes
[i
];
3023 if (node
.nodeType
== 1 && (!ofType
|| node
._counted
)) node
.nodeIndex
= j
++;
3026 for (var i
= 0, j
= 1, nodes
= parentNode
.childNodes
; node
= nodes
[i
]; i
++)
3027 if (node
.nodeType
== 1 && (!ofType
|| node
._counted
)) node
.nodeIndex
= j
++;
3031 // filters out duplicates and extends all nodes
3032 unique: function(nodes
) {
3033 if (nodes
.length
== 0) return nodes
;
3034 var results
= [], n
;
3035 for (var i
= 0, l
= nodes
.length
; i
< l
; i
++)
3036 if (!(n
= nodes
[i
])._counted
) {
3038 results
.push(Element
.extend(n
));
3040 return Selector
.handlers
.unmark(results
);
3043 // COMBINATOR FUNCTIONS
3044 descendant: function(nodes
) {
3045 var h
= Selector
.handlers
;
3046 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3047 h
.concat(results
, node
.getElementsByTagName('*'));
3051 child: function(nodes
) {
3052 var h
= Selector
.handlers
;
3053 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++) {
3054 for (var j
= 0, children
= [], child
; child
= node
.childNodes
[j
]; j
++)
3055 if (child
.nodeType
== 1 && child
.tagName
!= '!') results
.push(child
);
3060 adjacent: function(nodes
) {
3061 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++) {
3062 var next
= this.nextElementSibling(node
);
3063 if (next
) results
.push(next
);
3068 laterSibling: function(nodes
) {
3069 var h
= Selector
.handlers
;
3070 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3071 h
.concat(results
, Element
.nextSiblings(node
));
3075 nextElementSibling: function(node
) {
3076 while (node
= node
.nextSibling
)
3077 if (node
.nodeType
== 1) return node
;
3081 previousElementSibling: function(node
) {
3082 while (node
= node
.previousSibling
)
3083 if (node
.nodeType
== 1) return node
;
3088 tagName: function(nodes
, root
, tagName
, combinator
) {
3089 tagName
= tagName
.toUpperCase();
3090 var results
= [], h
= Selector
.handlers
;
3093 // fastlane for ordinary descendant combinators
3094 if (combinator
== "descendant") {
3095 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3096 h
.concat(results
, node
.getElementsByTagName(tagName
));
3098 } else nodes
= this[combinator
](nodes
);
3099 if (tagName
== "*") return nodes
;
3101 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3102 if (node
.tagName
.toUpperCase() == tagName
) results
.push(node
);
3104 } else return root
.getElementsByTagName(tagName
);
3107 id: function(nodes
, root
, id
, combinator
) {
3108 var targetNode
= $(id
), h
= Selector
.handlers
;
3109 if (!targetNode
) return [];
3110 if (!nodes
&& root
== document
) return [targetNode
];
3113 if (combinator
== 'child') {
3114 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3115 if (targetNode
.parentNode
== node
) return [targetNode
];
3116 } else if (combinator
== 'descendant') {
3117 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3118 if (Element
.descendantOf(targetNode
, node
)) return [targetNode
];
3119 } else if (combinator
== 'adjacent') {
3120 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3121 if (Selector
.handlers
.previousElementSibling(targetNode
) == node
)
3122 return [targetNode
];
3123 } else nodes
= h
[combinator
](nodes
);
3125 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3126 if (node
== targetNode
) return [targetNode
];
3129 return (targetNode
&& Element
.descendantOf(targetNode
, root
)) ? [targetNode
] : [];
3132 className: function(nodes
, root
, className
, combinator
) {
3133 if (nodes
&& combinator
) nodes
= this[combinator
](nodes
);
3134 return Selector
.handlers
.byClassName(nodes
, root
, className
);
3137 byClassName: function(nodes
, root
, className
) {
3138 if (!nodes
) nodes
= Selector
.handlers
.descendant([root
]);
3139 var needle
= ' ' + className
+ ' ';
3140 for (var i
= 0, results
= [], node
, nodeClassName
; node
= nodes
[i
]; i
++) {
3141 nodeClassName
= node
.className
;
3142 if (nodeClassName
.length
== 0) continue;
3143 if (nodeClassName
== className
|| (' ' + nodeClassName
+ ' ').include(needle
))
3149 attrPresence: function(nodes
, root
, attr
) {
3150 if (!nodes
) nodes
= root
.getElementsByTagName("*");
3152 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3153 if (Element
.hasAttribute(node
, attr
)) results
.push(node
);
3157 attr: function(nodes
, root
, attr
, value
, operator
) {
3158 if (!nodes
) nodes
= root
.getElementsByTagName("*");
3159 var handler
= Selector
.operators
[operator
], results
= [];
3160 for (var i
= 0, node
; node
= nodes
[i
]; i
++) {
3161 var nodeValue
= Element
.readAttribute(node
, attr
);
3162 if (nodeValue
=== null) continue;
3163 if (handler(nodeValue
, value
)) results
.push(node
);
3168 pseudo: function(nodes
, name
, value
, root
, combinator
) {
3169 if (nodes
&& combinator
) nodes
= this[combinator
](nodes
);
3170 if (!nodes
) nodes
= root
.getElementsByTagName("*");
3171 return Selector
.pseudos
[name
](nodes
, value
, root
);
3176 'first-child': function(nodes
, value
, root
) {
3177 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++) {
3178 if (Selector
.handlers
.previousElementSibling(node
)) continue;
3183 'last-child': function(nodes
, value
, root
) {
3184 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++) {
3185 if (Selector
.handlers
.nextElementSibling(node
)) continue;
3190 'only-child': function(nodes
, value
, root
) {
3191 var h
= Selector
.handlers
;
3192 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3193 if (!h
.previousElementSibling(node
) && !h
.nextElementSibling(node
))
3197 'nth-child': function(nodes
, formula
, root
) {
3198 return Selector
.pseudos
.nth(nodes
, formula
, root
);
3200 'nth-last-child': function(nodes
, formula
, root
) {
3201 return Selector
.pseudos
.nth(nodes
, formula
, root
, true);
3203 'nth-of-type': function(nodes
, formula
, root
) {
3204 return Selector
.pseudos
.nth(nodes
, formula
, root
, false, true);
3206 'nth-last-of-type': function(nodes
, formula
, root
) {
3207 return Selector
.pseudos
.nth(nodes
, formula
, root
, true, true);
3209 'first-of-type': function(nodes
, formula
, root
) {
3210 return Selector
.pseudos
.nth(nodes
, "1", root
, false, true);
3212 'last-of-type': function(nodes
, formula
, root
) {
3213 return Selector
.pseudos
.nth(nodes
, "1", root
, true, true);
3215 'only-of-type': function(nodes
, formula
, root
) {
3216 var p
= Selector
.pseudos
;
3217 return p
['last-of-type'](p
['first-of-type'](nodes
, formula
, root
), formula
, root
);
3220 // handles the an+b logic
3221 getIndices: function(a
, b
, total
) {
3222 if (a
== 0) return b
> 0 ? [b
] : [];
3223 return $R(1, total
).inject([], function(memo
, i
) {
3224 if (0 == (i
- b
) % a
&& (i
- b
) / a
>= 0) memo
.push(i
);
3229 // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
3230 nth: function(nodes
, formula
, root
, reverse
, ofType
) {
3231 if (nodes
.length
== 0) return [];
3232 if (formula
== 'even') formula
= '2n+0';
3233 if (formula
== 'odd') formula
= '2n+1';
3234 var h
= Selector
.handlers
, results
= [], indexed
= [], m
;
3236 for (var i
= 0, node
; node
= nodes
[i
]; i
++) {
3237 if (!node
.parentNode
._counted
) {
3238 h
.index(node
.parentNode
, reverse
, ofType
);
3239 indexed
.push(node
.parentNode
);
3242 if (formula
.match(/^\d+$/)) { // just a number
3243 formula
= Number(formula
);
3244 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3245 if (node
.nodeIndex
== formula
) results
.push(node
);
3246 } else if (m
= formula
.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
3247 if (m
[1] == "-") m
[1] = -1;
3248 var a
= m
[1] ? Number(m
[1]) : 1;
3249 var b
= m
[2] ? Number(m
[2]) : 0;
3250 var indices
= Selector
.pseudos
.getIndices(a
, b
, nodes
.length
);
3251 for (var i
= 0, node
, l
= indices
.length
; node
= nodes
[i
]; i
++) {
3252 for (var j
= 0; j
< l
; j
++)
3253 if (node
.nodeIndex
== indices
[j
]) results
.push(node
);
3261 'empty': function(nodes
, value
, root
) {
3262 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++) {
3263 // IE treats comments as element nodes
3264 if (node
.tagName
== '!' || (node
.firstChild
&& !node
.innerHTML
.match(/^\s*$/))) continue;
3270 'not': function(nodes
, selector
, root
) {
3271 var h
= Selector
.handlers
, selectorType
, m
;
3272 var exclusions
= new Selector(selector
).findElements(root
);
3274 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3275 if (!node
._counted
) results
.push(node
);
3276 h
.unmark(exclusions
);
3280 'enabled': function(nodes
, value
, root
) {
3281 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3282 if (!node
.disabled
) results
.push(node
);
3286 'disabled': function(nodes
, value
, root
) {
3287 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3288 if (node
.disabled
) results
.push(node
);
3292 'checked': function(nodes
, value
, root
) {
3293 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3294 if (node
.checked
) results
.push(node
);
3300 '=': function(nv
, v
) { return nv
== v
; },
3301 '!=': function(nv
, v
) { return nv
!= v
; },
3302 '^=': function(nv
, v
) { return nv
.startsWith(v
); },
3303 '$=': function(nv
, v
) { return nv
.endsWith(v
); },
3304 '*=': function(nv
, v
) { return nv
.include(v
); },
3305 '~=': function(nv
, v
) { return (' ' + nv
+ ' ').include(' ' + v
+ ' '); },
3306 '|=': function(nv
, v
) { return ('-' + nv
.toUpperCase() + '-').include('-' + v
.toUpperCase() + '-'); }
3309 matchElements: function(elements
, expression
) {
3310 var matches
= new Selector(expression
).findElements(), h
= Selector
.handlers
;
3312 for (var i
= 0, results
= [], element
; element
= elements
[i
]; i
++)
3313 if (element
._counted
) results
.push(element
);
3318 findElement: function(elements
, expression
, index
) {
3319 if (Object
.isNumber(expression
)) {
3320 index
= expression
; expression
= false;
3322 return Selector
.matchElements(elements
, expression
|| '*')[index
|| 0];
3325 findChildElements: function(element
, expressions
) {
3326 var exprs
= expressions
.join(','), expressions
= [];
3327 exprs
.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m
) {
3328 expressions
.push(m
[1].strip());
3330 var results
= [], h
= Selector
.handlers
;
3331 for (var i
= 0, l
= expressions
.length
, selector
; i
< l
; i
++) {
3332 selector
= new Selector(expressions
[i
].strip());
3333 h
.concat(results
, selector
.findElements(element
));
3335 return (l
> 1) ? h
.unique(results
) : results
;
3340 return Selector
.findChildElements(document
, $A(arguments
));
3343 reset: function(form
) {
3348 serializeElements: function(elements
, options
) {
3349 if (typeof options
!= 'object') options
= { hash
: !!options
};
3350 else if (options
.hash
=== undefined) options
.hash
= true;
3351 var key
, value
, submitted
= false, submit
= options
.submit
;
3353 var data
= elements
.inject({ }, function(result
, element
) {
3354 if (!element
.disabled
&& element
.name
) {
3355 key
= element
.name
; value
= $(element
).getValue();
3356 if (value
!= null && (element
.type
!= 'submit' || (!submitted
&&
3357 submit
!== false && (!submit
|| key
== submit
) && (submitted
= true)))) {
3358 if (key
in result
) {
3359 // a key is already present; construct an array of values
3360 if (!Object
.isArray(result
[key
])) result
[key
] = [result
[key
]];
3361 result
[key
].push(value
);
3363 else result
[key
] = value
;
3369 return options
.hash
? data
: Object
.toQueryString(data
);
3374 serialize: function(form
, options
) {
3375 return Form
.serializeElements(Form
.getElements(form
), options
);
3378 getElements: function(form
) {
3379 return $A($(form
).getElementsByTagName('*')).inject([],
3380 function(elements
, child
) {
3381 if (Form
.Element
.Serializers
[child
.tagName
.toLowerCase()])
3382 elements
.push(Element
.extend(child
));
3388 getInputs: function(form
, typeName
, name
) {
3390 var inputs
= form
.getElementsByTagName('input');
3392 if (!typeName
&& !name
) return $A(inputs
).map(Element
.extend
);
3394 for (var i
= 0, matchingInputs
= [], length
= inputs
.length
; i
< length
; i
++) {
3395 var input
= inputs
[i
];
3396 if ((typeName
&& input
.type
!= typeName
) || (name
&& input
.name
!= name
))
3398 matchingInputs
.push(Element
.extend(input
));
3401 return matchingInputs
;
3404 disable: function(form
) {
3406 Form
.getElements(form
).invoke('disable');
3410 enable: function(form
) {
3412 Form
.getElements(form
).invoke('enable');
3416 findFirstElement: function(form
) {
3417 var elements
= $(form
).getElements().findAll(function(element
) {
3418 return 'hidden' != element
.type
&& !element
.disabled
;
3420 var firstByIndex
= elements
.findAll(function(element
) {
3421 return element
.hasAttribute('tabIndex') && element
.tabIndex
>= 0;
3422 }).sortBy(function(element
) { return element
.tabIndex
}).first();
3424 return firstByIndex
? firstByIndex
: elements
.find(function(element
) {
3425 return ['input', 'select', 'textarea'].include(element
.tagName
.toLowerCase());
3429 focusFirstElement: function(form
) {
3431 form
.findFirstElement().activate();
3435 request: function(form
, options
) {
3436 form
= $(form
), options
= Object
.clone(options
|| { });
3438 var params
= options
.parameters
, action
= form
.readAttribute('action') || '';
3439 if (action
.blank()) action
= window
.location
.href
;
3440 options
.parameters
= form
.serialize(true);
3443 if (Object
.isString(params
)) params
= params
.toQueryParams();
3444 Object
.extend(options
.parameters
, params
);
3447 if (form
.hasAttribute('method') && !options
.method
)
3448 options
.method
= form
.method
;
3450 return new Ajax
.Request(action
, options
);
3454 /*--------------------------------------------------------------------------*/
3457 focus: function(element
) {
3462 select: function(element
) {
3463 $(element
).select();
3468 Form
.Element
.Methods
= {
3469 serialize: function(element
) {
3470 element
= $(element
);
3471 if (!element
.disabled
&& element
.name
) {
3472 var value
= element
.getValue();
3473 if (value
!= undefined) {
3475 pair
[element
.name
] = value
;
3476 return Object
.toQueryString(pair
);
3482 getValue: function(element
) {
3483 element
= $(element
);
3484 var method
= element
.tagName
.toLowerCase();
3485 return Form
.Element
.Serializers
[method
](element
);
3488 setValue: function(element
, value
) {
3489 element
= $(element
);
3490 var method
= element
.tagName
.toLowerCase();
3491 Form
.Element
.Serializers
[method
](element
, value
);
3495 clear: function(element
) {
3496 $(element
).value
= '';
3500 present: function(element
) {
3501 return $(element
).value
!= '';
3504 activate: function(element
) {
3505 element
= $(element
);
3508 if (element
.select
&& (element
.tagName
.toLowerCase() != 'input' ||
3509 !['button', 'reset', 'submit'].include(element
.type
)))
3515 disable: function(element
) {
3516 element
= $(element
);
3518 element
.disabled
= true;
3522 enable: function(element
) {
3523 element
= $(element
);
3524 element
.disabled
= false;
3529 /*--------------------------------------------------------------------------*/
3531 var Field
= Form
.Element
;
3532 var $F
= Form
.Element
.Methods
.getValue
;
3534 /*--------------------------------------------------------------------------*/
3536 Form
.Element
.Serializers
= {
3537 input: function(element
, value
) {
3538 switch (element
.type
.toLowerCase()) {
3541 return Form
.Element
.Serializers
.inputSelector(element
, value
);
3543 return Form
.Element
.Serializers
.textarea(element
, value
);
3547 inputSelector: function(element
, value
) {
3548 if (value
=== undefined) return element
.checked
? element
.value
: null;
3549 else element
.checked
= !!value
;
3552 textarea: function(element
, value
) {
3553 if (value
=== undefined) return element
.value
;
3554 else element
.value
= value
;
3557 select: function(element
, index
) {
3558 if (index
=== undefined)
3559 return this[element
.type
== 'select-one' ?
3560 'selectOne' : 'selectMany'](element
);
3562 var opt
, value
, single
= !Object
.isArray(index
);
3563 for (var i
= 0, length
= element
.length
; i
< length
; i
++) {
3564 opt
= element
.options
[i
];
3565 value
= this.optionValue(opt
);
3567 if (value
== index
) {
3568 opt
.selected
= true;
3572 else opt
.selected
= index
.include(value
);
3577 selectOne: function(element
) {
3578 var index
= element
.selectedIndex
;
3579 return index
>= 0 ? this.optionValue(element
.options
[index
]) : null;
3582 selectMany: function(element
) {
3583 var values
, length
= element
.length
;
3584 if (!length
) return null;
3586 for (var i
= 0, values
= []; i
< length
; i
++) {
3587 var opt
= element
.options
[i
];
3588 if (opt
.selected
) values
.push(this.optionValue(opt
));
3593 optionValue: function(opt
) {
3594 // extend element because hasAttribute may not be native
3595 return Element
.extend(opt
).hasAttribute('value') ? opt
.value
: opt
.text
;
3599 /*--------------------------------------------------------------------------*/
3601 Abstract
.TimedObserver
= Class
.create(PeriodicalExecuter
, {
3602 initialize: function($super, element
, frequency
, callback
) {
3603 $super(callback
, frequency
);
3604 this.element
= $(element
);
3605 this.lastValue
= this.getValue();
3608 execute: function() {
3609 var value
= this.getValue();
3610 if (Object
.isString(this.lastValue
) && Object
.isString(value
) ?
3611 this.lastValue
!= value
: String(this.lastValue
) != String(value
)) {
3612 this.callback(this.element
, value
);
3613 this.lastValue
= value
;
3618 Form
.Element
.Observer
= Class
.create(Abstract
.TimedObserver
, {
3619 getValue: function() {
3620 return Form
.Element
.getValue(this.element
);
3624 Form
.Observer
= Class
.create(Abstract
.TimedObserver
, {
3625 getValue: function() {
3626 return Form
.serialize(this.element
);
3630 /*--------------------------------------------------------------------------*/
3632 Abstract
.EventObserver
= Class
.create({
3633 initialize: function(element
, callback
) {
3634 this.element
= $(element
);
3635 this.callback
= callback
;
3637 this.lastValue
= this.getValue();
3638 if (this.element
.tagName
.toLowerCase() == 'form')
3639 this.registerFormCallbacks();
3641 this.registerCallback(this.element
);
3644 onElementEvent: function() {
3645 var value
= this.getValue();
3646 if (this.lastValue
!= value
) {
3647 this.callback(this.element
, value
);
3648 this.lastValue
= value
;
3652 registerFormCallbacks: function() {
3653 Form
.getElements(this.element
).each(this.registerCallback
, this);
3656 registerCallback: function(element
) {
3658 switch (element
.type
.toLowerCase()) {
3661 Event
.observe(element
, 'click', this.onElementEvent
.bind(this));
3664 Event
.observe(element
, 'change', this.onElementEvent
.bind(this));
3671 Form
.Element
.EventObserver
= Class
.create(Abstract
.EventObserver
, {
3672 getValue: function() {
3673 return Form
.Element
.getValue(this.element
);
3677 Form
.EventObserver
= Class
.create(Abstract
.EventObserver
, {
3678 getValue: function() {
3679 return Form
.serialize(this.element
);
3682 if (!window
.Event
) var Event
= { };
3684 Object
.extend(Event
, {
3702 relatedTarget: function(event
) {
3704 switch(event
.type
) {
3705 case 'mouseover': element
= event
.fromElement
; break;
3706 case 'mouseout': element
= event
.toElement
; break;
3707 default: return null;
3709 return Element
.extend(element
);
3713 Event
.Methods
= (function() {
3716 if (Prototype
.Browser
.IE
) {
3717 var buttonMap
= { 0: 1, 1: 4, 2: 2 };
3718 isButton = function(event
, code
) {
3719 return event
.button
== buttonMap
[code
];
3722 } else if (Prototype
.Browser
.WebKit
) {
3723 isButton = function(event
, code
) {
3725 case 0: return event
.which
== 1 && !event
.metaKey
;
3726 case 1: return event
.which
== 1 && event
.metaKey
;
3727 default: return false;
3732 isButton = function(event
, code
) {
3733 return event
.which
? (event
.which
=== code
+ 1) : (event
.button
=== code
);
3738 isLeftClick: function(event
) { return isButton(event
, 0) },
3739 isMiddleClick: function(event
) { return isButton(event
, 1) },
3740 isRightClick: function(event
) { return isButton(event
, 2) },
3742 element: function(event
) {
3743 var node
= Event
.extend(event
).target
;
3744 return Element
.extend(node
.nodeType
== Node
.TEXT_NODE
? node
.parentNode
: node
);
3747 findElement: function(event
, expression
) {
3748 var element
= Event
.element(event
);
3749 return element
.match(expression
) ? element
: element
.up(expression
);
3752 pointer: function(event
) {
3754 x
: event
.pageX
|| (event
.clientX
+
3755 (document
.documentElement
.scrollLeft
|| document
.body
.scrollLeft
)),
3756 y
: event
.pageY
|| (event
.clientY
+
3757 (document
.documentElement
.scrollTop
|| document
.body
.scrollTop
))
3761 pointerX: function(event
) { return Event
.pointer(event
).x
},
3762 pointerY: function(event
) { return Event
.pointer(event
).y
},
3764 stop: function(event
) {
3765 Event
.extend(event
);
3766 event
.preventDefault();
3767 event
.stopPropagation();
3768 event
.stopped
= true;
3773 Event
.extend
= (function() {
3774 var methods
= Object
.keys(Event
.Methods
).inject({ }, function(m
, name
) {
3775 m
[name
] = Event
.Methods
[name
].methodize();
3779 if (Prototype
.Browser
.IE
) {
3780 Object
.extend(methods
, {
3781 stopPropagation: function() { this.cancelBubble
= true },
3782 preventDefault: function() { this.returnValue
= false },
3783 inspect: function() { return "[object Event]" }
3786 return function(event
) {
3787 if (!event
) return false;
3788 if (event
._extendedByPrototype
) return event
;
3790 event
._extendedByPrototype
= Prototype
.emptyFunction
;
3791 var pointer
= Event
.pointer(event
);
3792 Object
.extend(event
, {
3793 target
: event
.srcElement
,
3794 relatedTarget
: Event
.relatedTarget(event
),
3798 return Object
.extend(event
, methods
);
3802 Event
.prototype = Event
.prototype || document
.createEvent("HTMLEvents").__proto__
;
3803 Object
.extend(Event
.prototype, methods
);
3808 Object
.extend(Event
, (function() {
3809 var cache
= Event
.cache
;
3811 function getEventID(element
) {
3812 if (element
._eventID
) return element
._eventID
;
3813 arguments
.callee
.id
= arguments
.callee
.id
|| 1;
3814 return element
._eventID
= ++arguments
.callee
.id
;
3817 function getDOMEventName(eventName
) {
3818 if (eventName
&& eventName
.include(':')) return "dataavailable";
3822 function getCacheForID(id
) {
3823 return cache
[id
] = cache
[id
] || { };
3826 function getWrappersForEventName(id
, eventName
) {
3827 var c
= getCacheForID(id
);
3828 return c
[eventName
] = c
[eventName
] || [];
3831 function createWrapper(element
, eventName
, handler
) {
3832 var id
= getEventID(element
);
3833 var c
= getWrappersForEventName(id
, eventName
);
3834 if (c
.pluck("handler").include(handler
)) return false;
3836 var wrapper = function(event
) {
3837 if (!Event
|| !Event
.extend
||
3838 (event
.eventName
&& event
.eventName
!= eventName
))
3841 Event
.extend(event
);
3842 handler
.call(element
, event
)
3845 wrapper
.handler
= handler
;
3850 function findWrapper(id
, eventName
, handler
) {
3851 var c
= getWrappersForEventName(id
, eventName
);
3852 return c
.find(function(wrapper
) { return wrapper
.handler
== handler
});
3855 function destroyWrapper(id
, eventName
, handler
) {
3856 var c
= getCacheForID(id
);
3857 if (!c
[eventName
]) return false;
3858 c
[eventName
] = c
[eventName
].without(findWrapper(id
, eventName
, handler
));
3861 function destroyCache() {
3862 for (var id
in cache
)
3863 for (var eventName
in cache
[id
])
3864 cache
[id
][eventName
] = null;
3867 if (window
.attachEvent
) {
3868 window
.attachEvent("onunload", destroyCache
);
3872 observe: function(element
, eventName
, handler
) {
3873 element
= $(element
);
3874 var name
= getDOMEventName(eventName
);
3876 var wrapper
= createWrapper(element
, eventName
, handler
);
3877 if (!wrapper
) return element
;
3879 if (element
.addEventListener
) {
3880 element
.addEventListener(name
, wrapper
, false);
3882 element
.attachEvent("on" + name
, wrapper
);
3888 stopObserving: function(element
, eventName
, handler
) {
3889 element
= $(element
);
3890 var id
= getEventID(element
), name
= getDOMEventName(eventName
);
3892 if (!handler
&& eventName
) {
3893 getWrappersForEventName(id
, eventName
).each(function(wrapper
) {
3894 element
.stopObserving(eventName
, wrapper
.handler
);
3898 } else if (!eventName
) {
3899 Object
.keys(getCacheForID(id
)).each(function(eventName
) {
3900 element
.stopObserving(eventName
);
3905 var wrapper
= findWrapper(id
, eventName
, handler
);
3906 if (!wrapper
) return element
;
3908 if (element
.removeEventListener
) {
3909 element
.removeEventListener(name
, wrapper
, false);
3911 element
.detachEvent("on" + name
, wrapper
);
3914 destroyWrapper(id
, eventName
, handler
);
3919 fire: function(element
, eventName
, memo
) {
3920 element
= $(element
);
3921 if (element
== document
&& document
.createEvent
&& !element
.dispatchEvent
)
3922 element
= document
.documentElement
;
3924 if (document
.createEvent
) {
3925 var event
= document
.createEvent("HTMLEvents");
3926 event
.initEvent("dataavailable", true, true);
3928 var event
= document
.createEventObject();
3929 event
.eventType
= "ondataavailable";
3932 event
.eventName
= eventName
;
3933 event
.memo
= memo
|| { };
3935 if (document
.createEvent
) {
3936 element
.dispatchEvent(event
);
3938 element
.fireEvent(event
.eventType
, event
);
3946 Object
.extend(Event
, Event
.Methods
);
3948 Element
.addMethods({
3950 observe
: Event
.observe
,
3951 stopObserving
: Event
.stopObserving
3954 Object
.extend(document
, {
3955 fire
: Element
.Methods
.fire
.methodize(),
3956 observe
: Element
.Methods
.observe
.methodize(),
3957 stopObserving
: Element
.Methods
.stopObserving
.methodize()
3961 /* Support for the DOMContentLoaded event is based on work by Dan Webb,
3962 Matthias Miller, Dean Edwards and John Resig. */
3964 var timer
, fired
= false;
3966 function fireContentLoadedEvent() {
3968 if (timer
) window
.clearInterval(timer
);
3969 document
.fire("dom:loaded");
3973 if (document
.addEventListener
) {
3974 if (Prototype
.Browser
.WebKit
) {
3975 timer
= window
.setInterval(function() {
3976 if (/loaded|complete/.test(document
.readyState
))
3977 fireContentLoadedEvent();
3980 Event
.observe(window
, "load", fireContentLoadedEvent
);
3983 document
.addEventListener("DOMContentLoaded",
3984 fireContentLoadedEvent
, false);
3988 document
.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
3989 $("__onDOMContentLoaded").onreadystatechange = function() {
3990 if (this.readyState
== "complete") {
3991 this.onreadystatechange
= null;
3992 fireContentLoadedEvent();
3997 /*------------------------------- DEPRECATED -------------------------------*/
3999 Hash
.toQueryString
= Object
.toQueryString
;
4001 var Toggle
= { display
: Element
.toggle
};
4003 Element
.Methods
.childOf
= Element
.Methods
.descendantOf
;
4006 Before: function(element
, content
) {
4007 return Element
.insert(element
, {before
:content
});
4010 Top: function(element
, content
) {
4011 return Element
.insert(element
, {top
:content
});
4014 Bottom: function(element
, content
) {
4015 return Element
.insert(element
, {bottom
:content
});
4018 After: function(element
, content
) {
4019 return Element
.insert(element
, {after
:content
});
4023 var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
4025 // This should be moved to script.aculo.us; notice the deprecated methods
4026 // further below, that map to the newer Element methods.
4028 // set to true if needed, warning: firefox performance problems
4029 // NOT neeeded for page scrolling, only if draggable contained in
4030 // scrollable elements
4031 includeScrollOffsets
: false,
4033 // must be called before calling withinIncludingScrolloffset, every time the
4035 prepare: function() {
4036 this.deltaX
= window
.pageXOffset
4037 || document
.documentElement
.scrollLeft
4038 || document
.body
.scrollLeft
4040 this.deltaY
= window
.pageYOffset
4041 || document
.documentElement
.scrollTop
4042 || document
.body
.scrollTop
4046 // caches x/y coordinate pair to use with overlap
4047 within: function(element
, x
, y
) {
4048 if (this.includeScrollOffsets
)
4049 return this.withinIncludingScrolloffsets(element
, x
, y
);
4052 this.offset
= Element
.cumulativeOffset(element
);
4054 return (y
>= this.offset
[1] &&
4055 y
< this.offset
[1] + element
.offsetHeight
&&
4056 x
>= this.offset
[0] &&
4057 x
< this.offset
[0] + element
.offsetWidth
);
4060 withinIncludingScrolloffsets: function(element
, x
, y
) {
4061 var offsetcache
= Element
.cumulativeScrollOffset(element
);
4063 this.xcomp
= x
+ offsetcache
[0] - this.deltaX
;
4064 this.ycomp
= y
+ offsetcache
[1] - this.deltaY
;
4065 this.offset
= Element
.cumulativeOffset(element
);
4067 return (this.ycomp
>= this.offset
[1] &&
4068 this.ycomp
< this.offset
[1] + element
.offsetHeight
&&
4069 this.xcomp
>= this.offset
[0] &&
4070 this.xcomp
< this.offset
[0] + element
.offsetWidth
);
4073 // within must be called directly before
4074 overlap: function(mode
, element
) {
4075 if (!mode
) return 0;
4076 if (mode
== 'vertical')
4077 return ((this.offset
[1] + element
.offsetHeight
) - this.ycomp
) /
4078 element
.offsetHeight
;
4079 if (mode
== 'horizontal')
4080 return ((this.offset
[0] + element
.offsetWidth
) - this.xcomp
) /
4081 element
.offsetWidth
;
4084 // Deprecation layer -- use newer Element methods now (1.5.2).
4086 cumulativeOffset
: Element
.Methods
.cumulativeOffset
,
4088 positionedOffset
: Element
.Methods
.positionedOffset
,
4090 absolutize: function(element
) {
4092 return Element
.absolutize(element
);
4095 relativize: function(element
) {
4097 return Element
.relativize(element
);
4100 realOffset
: Element
.Methods
.cumulativeScrollOffset
,
4102 offsetParent
: Element
.Methods
.getOffsetParent
,
4104 page
: Element
.Methods
.viewportOffset
,
4106 clone: function(source
, target
, options
) {
4107 options
= options
|| { };
4108 return Element
.clonePosition(target
, source
, options
);
4112 /*--------------------------------------------------------------------------*/
4114 if (!document
.getElementsByClassName
) document
.getElementsByClassName = function(instanceMethods
){
4115 function iter(name
) {
4116 return name
.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name
+ " ')]";
4119 instanceMethods
.getElementsByClassName
= Prototype
.BrowserFeatures
.XPath
?
4120 function(element
, className
) {
4121 className
= className
.toString().strip();
4122 var cond
= /\s/.test(className
) ? $w(className
).map(iter
).join('') : iter(className
);
4123 return cond
? document
._getElementsByXPath('.//*' + cond
, element
) : [];
4124 } : function(element
, className
) {
4125 className
= className
.toString().strip();
4126 var elements
= [], classNames
= (/\s/.test(className
) ? $w(className
) : null);
4127 if (!classNames
&& !className
) return elements
;
4129 var nodes
= $(element
).getElementsByTagName('*');
4130 className
= ' ' + className
+ ' ';
4132 for (var i
= 0, child
, cn
; child
= nodes
[i
]; i
++) {
4133 if (child
.className
&& (cn
= ' ' + child
.className
+ ' ') && (cn
.include(className
) ||
4134 (classNames
&& classNames
.all(function(name
) {
4135 return !name
.toString().blank() && cn
.include(' ' + name
+ ' ');
4137 elements
.push(Element
.extend(child
));
4142 return function(className
, parentElement
) {
4143 return $(parentElement
|| document
.body
).getElementsByClassName(className
);
4147 /*--------------------------------------------------------------------------*/
4149 Element
.ClassNames
= Class
.create();
4150 Element
.ClassNames
.prototype = {
4151 initialize: function(element
) {
4152 this.element
= $(element
);
4155 _each: function(iterator
) {
4156 this.element
.className
.split(/\s+/).select(function(name
) {
4157 return name
.length
> 0;
4161 set: function(className
) {
4162 this.element
.className
= className
;
4165 add: function(classNameToAdd
) {
4166 if (this.include(classNameToAdd
)) return;
4167 this.set($A(this).concat(classNameToAdd
).join(' '));
4170 remove: function(classNameToRemove
) {
4171 if (!this.include(classNameToRemove
)) return;
4172 this.set($A(this).without(classNameToRemove
).join(' '));
4175 toString: function() {
4176 return $A(this).join(' ');
4180 Object
.extend(Element
.ClassNames
.prototype, Enumerable
);
4182 /*--------------------------------------------------------------------------*/
4184 Element
.addMethods();