1 /* Prototype JavaScript framework, version 1.6.0_rc1
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('form').__proto__
28 ScriptFragment
: '<script[^>]*>([\\S\\s]*?)<\/script>',
29 JSONFilter
: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
31 emptyFunction: function() { },
32 K: function(x
) { return x
}
35 if (Prototype
.Browser
.MobileSafari
)
36 Prototype
.BrowserFeatures
.SpecificElementExtensions
= false;
38 if (Prototype
.Browser
.WebKit
)
39 Prototype
.BrowserFeatures
.XPath
= false;
41 /* Based on Alex Arnell's inheritance implementation. */
44 var parent
= null, properties
= $A(arguments
);
45 if (Object
.isFunction(properties
[0]))
46 parent
= properties
.shift();
49 this.initialize
.apply(this, arguments
);
52 Object
.extend(klass
, Class
.Methods
);
53 klass
.superclass
= parent
;
54 klass
.subclasses
= [];
57 var subclass = function() { };
58 subclass
.prototype = parent
.prototype;
59 klass
.prototype = new subclass
;
60 parent
.subclasses
.push(klass
);
63 for (var i
= 0; i
< properties
.length
; i
++)
64 klass
.addMethods(properties
[i
]);
66 if (!klass
.prototype.initialize
)
67 klass
.prototype.initialize
= Prototype
.emptyFunction
;
69 klass
.prototype.constructor = klass
;
76 addMethods: function(source
) {
77 var ancestor
= this.superclass
&& this.superclass
.prototype;
79 for (var property
in source
) {
80 var value
= source
[property
];
81 if (ancestor
&& Object
.isFunction(value
) &&
82 value
.argumentNames().first() == "$super") {
83 var method
= value
, value
= Object
.extend((function(m
) {
84 return function() { return ancestor
[m
].apply(this, arguments
) };
85 })(property
).wrap(method
), {
86 valueOf: function() { return method
},
87 toString: function() { return method
.toString() }
90 this.prototype[property
] = value
;
99 Object
.extend = function(destination
, source
) {
100 for (var property
in source
)
101 destination
[property
] = source
[property
];
105 Object
.extend(Object
, {
106 inspect: function(object
) {
108 if (object
=== undefined) return 'undefined';
109 if (object
=== null) return 'null';
110 return object
.inspect
? object
.inspect() : object
.toString();
112 if (e
instanceof RangeError
) return '...';
117 toJSON: function(object
) {
118 var type
= typeof object
;
122 case 'unknown': return;
123 case 'boolean': return object
.toString();
126 if (object
=== null) return 'null';
127 if (object
.toJSON
) return object
.toJSON();
128 if (Object
.isElement(object
)) return;
131 for (var property
in object
) {
132 var value
= Object
.toJSON(object
[property
]);
133 if (value
!== undefined)
134 results
.push(property
.toJSON() + ': ' + value
);
137 return '{' + results
.join(', ') + '}';
140 toQueryString: function(object
) {
141 return $H(object
).toQueryString();
144 toHTML: function(object
) {
145 return object
&& object
.toHTML
? object
.toHTML() : String
.interpret(object
);
148 keys: function(object
) {
150 for (var property
in object
)
155 values: function(object
) {
157 for (var property
in object
)
158 values
.push(object
[property
]);
162 clone: function(object
) {
163 return Object
.extend({ }, object
);
166 isElement: function(object
) {
167 return object
&& object
.nodeType
== 1;
170 isArray: function(object
) {
171 return object
&& object
.constructor === Array
;
174 isHash: function(object
) {
175 return object
instanceof Hash
;
178 isFunction: function(object
) {
179 return typeof object
== "function";
182 isString: function(object
) {
183 return typeof object
== "string";
186 isNumber: function(object
) {
187 return typeof object
== "number";
190 isUndefined: function(object
) {
191 return typeof object
== "undefined";
195 Object
.extend(Function
.prototype, {
196 argumentNames: function() {
197 var names
= this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
198 return names
.length
== 1 && !names
[0] ? [] : names
;
202 if (arguments
.length
< 2 && arguments
[0] === undefined) return this;
203 var __method
= this, args
= $A(arguments
), object
= args
.shift();
205 return __method
.apply(object
, args
.concat($A(arguments
)));
209 bindAsEventListener: function() {
210 var __method
= this, args
= $A(arguments
), object
= args
.shift();
211 return function(event
) {
212 return __method
.apply(object
, [event
|| window
.event
].concat(args
));
217 if (!arguments
.length
) return this;
218 var __method
= this, args
= $A(arguments
);
220 return __method
.apply(this, args
.concat($A(arguments
)));
225 var __method
= this, args
= $A(arguments
), timeout
= args
.shift() * 1000;
226 return window
.setTimeout(function() {
227 return __method
.apply(__method
, args
);
231 wrap: function(wrapper
) {
234 return wrapper
.apply(this, [__method
.bind(this)].concat($A(arguments
)));
238 methodize: function() {
239 if (this._methodized
) return this._methodized
;
241 return this._methodized = function() {
242 return __method
.apply(null, [this].concat($A(arguments
)));
247 Function
.prototype.defer
= Function
.prototype.delay
.curry(0.01);
249 Date
.prototype.toJSON = function() {
250 return '"' + this.getUTCFullYear() + '-' +
251 (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
252 this.getUTCDate().toPaddedString(2) + 'T' +
253 this.getUTCHours().toPaddedString(2) + ':' +
254 this.getUTCMinutes().toPaddedString(2) + ':' +
255 this.getUTCSeconds().toPaddedString(2) + 'Z"';
262 for (var i
= 0, length
= arguments
.length
; i
< length
; i
++) {
263 var lambda
= arguments
[i
];
265 returnValue
= lambda();
274 RegExp
.prototype.match
= RegExp
.prototype.test
;
276 RegExp
.escape = function(str
) {
277 return String(str
).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
280 /*--------------------------------------------------------------------------*/
282 var PeriodicalExecuter
= Class
.create({
283 initialize: function(callback
, frequency
) {
284 this.callback
= callback
;
285 this.frequency
= frequency
;
286 this.currentlyExecuting
= false;
288 this.registerCallback();
291 registerCallback: function() {
292 this.timer
= setInterval(this.onTimerEvent
.bind(this), this.frequency
* 1000);
295 execute: function() {
300 if (!this.timer
) return;
301 clearInterval(this.timer
);
305 onTimerEvent: function() {
306 if (!this.currentlyExecuting
) {
308 this.currentlyExecuting
= true;
311 this.currentlyExecuting
= false;
316 Object
.extend(String
, {
317 interpret: function(value
) {
318 return value
== null ? '' : String(value
);
330 Object
.extend(String
.prototype, {
331 gsub: function(pattern
, replacement
) {
332 var result
= '', source
= this, match
;
333 replacement
= arguments
.callee
.prepareReplacement(replacement
);
335 while (source
.length
> 0) {
336 if (match
= source
.match(pattern
)) {
337 result
+= source
.slice(0, match
.index
);
338 result
+= String
.interpret(replacement(match
));
339 source
= source
.slice(match
.index
+ match
[0].length
);
341 result
+= source
, source
= '';
347 sub: function(pattern
, replacement
, count
) {
348 replacement
= this.gsub
.prepareReplacement(replacement
);
349 count
= count
=== undefined ? 1 : count
;
351 return this.gsub(pattern
, function(match
) {
352 if (--count
< 0) return match
[0];
353 return replacement(match
);
357 scan: function(pattern
, iterator
) {
358 this.gsub(pattern
, iterator
);
362 truncate: function(length
, truncation
) {
363 length
= length
|| 30;
364 truncation
= truncation
=== undefined ? '...' : truncation
;
365 return this.length
> length
?
366 this.slice(0, length
- truncation
.length
) + truncation
: String(this);
370 return this.replace(/^\s+/, '').replace(/\s+$/, '');
373 stripTags: function() {
374 return this.replace(/<\/?[^>]+>/gi, '');
377 stripScripts: function() {
378 return this.replace(new RegExp(Prototype
.ScriptFragment
, 'img'), '');
381 extractScripts: function() {
382 var matchAll
= new RegExp(Prototype
.ScriptFragment
, 'img');
383 var matchOne
= new RegExp(Prototype
.ScriptFragment
, 'im');
384 return (this.match(matchAll
) || []).map(function(scriptTag
) {
385 return (scriptTag
.match(matchOne
) || ['', ''])[1];
389 evalScripts: function() {
390 return this.extractScripts().map(function(script
) { return eval(script
) });
393 escapeHTML: function() {
394 var self
= arguments
.callee
;
395 self
.text
.data
= this;
396 return self
.div
.innerHTML
;
399 unescapeHTML: function() {
400 var div
= new Element('div');
401 div
.innerHTML
= this.stripTags();
402 return div
.childNodes
[0] ? (div
.childNodes
.length
> 1 ?
403 $A(div
.childNodes
).inject('', function(memo
, node
) { return memo
+node
.nodeValue
}) :
404 div
.childNodes
[0].nodeValue
) : '';
407 toQueryParams: function(separator
) {
408 var match
= this.strip().match(/([^?#]*)(#.*)?$/);
409 if (!match
) return { };
411 return match
[1].split(separator
|| '&').inject({ }, function(hash
, pair
) {
412 if ((pair
= pair
.split('='))[0]) {
413 var key
= decodeURIComponent(pair
.shift());
414 var value
= pair
.length
> 1 ? pair
.join('=') : pair
[0];
415 if (value
!= undefined) value
= decodeURIComponent(value
);
418 if (!Object
.isArray(hash
[key
])) hash
[key
] = [hash
[key
]];
419 hash
[key
].push(value
);
421 else hash
[key
] = value
;
427 toArray: function() {
428 return this.split('');
432 return this.slice(0, this.length
- 1) +
433 String
.fromCharCode(this.charCodeAt(this.length
- 1) + 1);
436 times: function(count
) {
437 return count
< 1 ? '' : new Array(count
+ 1).join(this);
440 camelize: function() {
441 var parts
= this.split('-'), len
= parts
.length
;
442 if (len
== 1) return parts
[0];
444 var camelized
= this.charAt(0) == '-'
445 ? parts
[0].charAt(0).toUpperCase() + parts
[0].substring(1)
448 for (var i
= 1; i
< len
; i
++)
449 camelized
+= parts
[i
].charAt(0).toUpperCase() + parts
[i
].substring(1);
454 capitalize: function() {
455 return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
458 underscore: function() {
459 return this.gsub(/::/, '/').gsub(/([A
-Z
]+)([A
-Z
][a
-z
])/,'#{1}_#{2}').gsub(/([a
-z
\d
])([A
-Z
])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
462 dasherize: function() {
463 return this.gsub(/_
/,'-');
466 inspect: function(useDoubleQuotes
) {
467 var escapedString
= this.gsub(/[\x00-\x1f\\]/, function(match
) {
468 var character
= String
.specialChar
[match
[0]];
469 return character
? character
: '\\u00' + match
[0].charCodeAt().toPaddedString(2, 16);
471 if (useDoubleQuotes
) return '"' + escapedString
.replace(/"/g, '\\"') + '"';
472 return "'" + escapedString.replace(/'/g
, '\\\'') + "'";
476 return this.inspect(true);
479 unfilterJSON: function(filter
) {
480 return this.sub(filter
|| Prototype
.JSONFilter
, '#{1}');
484 var str
= this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
485 return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str
);
488 evalJSON: function(sanitize
) {
489 var json
= this.unfilterJSON();
491 if (!sanitize
|| json
.isJSON()) return eval('(' + json
+ ')');
493 throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
496 include: function(pattern
) {
497 return this.indexOf(pattern
) > -1;
500 startsWith: function(pattern
) {
501 return this.indexOf(pattern
) === 0;
504 endsWith: function(pattern
) {
505 var d
= this.length
- pattern
.length
;
506 return d
>= 0 && this.lastIndexOf(pattern
) === d
;
514 return /^\s*$/.test(this);
517 interpolate: function(object
, pattern
) {
518 return new Template(this, pattern
).evaluate(object
);
522 if (Prototype
.Browser
.WebKit
|| Prototype
.Browser
.IE
) Object
.extend(String
.prototype, {
523 escapeHTML: function() {
524 return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g
,'>');
526 unescapeHTML: function() {
527 return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
531 String
.prototype.gsub
.prepareReplacement = function(replacement
) {
532 if (Object
.isFunction(replacement
)) return replacement
;
533 var template
= new Template(replacement
);
534 return function(match
) { return template
.evaluate(match
) };
537 String
.prototype.parseQuery
= String
.prototype.toQueryParams
;
539 Object
.extend(String
.prototype.escapeHTML
, {
540 div
: document
.createElement('div'),
541 text
: document
.createTextNode('')
544 with (String
.prototype.escapeHTML
) div
.appendChild(text
);
546 var Template
= Class
.create({
547 initialize: function(template
, pattern
) {
548 this.template
= template
.toString();
549 this.pattern
= pattern
|| Template
.Pattern
;
552 evaluate: function(object
) {
553 if (Object
.isFunction(object
.toTemplateReplacements
))
554 object
= object
.toTemplateReplacements();
556 return this.template
.gsub(this.pattern
, function(match
) {
557 if (object
== null) return '';
559 var before
= match
[1] || '';
560 if (before
== '\\') return match
[2];
562 var ctx
= object
, expr
= match
[3];
563 var pattern
= /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/, match
= pattern
.exec(expr
);
564 if (match
== null) return '';
566 while (match
!= null) {
567 var comp
= match
[1].startsWith('[') ? match
[2].gsub('\\\\]', ']') : match
[1];
569 if (null == ctx
|| '' == match
[3]) break;
570 expr
= expr
.substring('[' == match
[3] ? match
[1].length
: match
[0].length
);
571 match
= pattern
.exec(expr
);
574 return before
+ String
.interpret(ctx
);
578 Template
.Pattern
= /(^|.|\r|\n)(#\{(.*?)\})/;
583 each: function(iterator
, context
) {
585 iterator
= iterator
.bind(context
);
587 this._each(function(value
) {
588 iterator(value
, index
++);
591 if (e
!= $break) throw e
;
596 eachSlice: function(number
, iterator
, context
) {
597 iterator
= iterator
? iterator
.bind(context
) : Prototype
.K
;
598 var index
= -number
, slices
= [], array
= this.toArray();
599 while ((index
+= number
) < array
.length
)
600 slices
.push(array
.slice(index
, index
+number
));
601 return slices
.collect(iterator
, context
);
604 all: function(iterator
, context
) {
605 iterator
= iterator
? iterator
.bind(context
) : Prototype
.K
;
607 this.each(function(value
, index
) {
608 result
= result
&& !!iterator(value
, index
);
609 if (!result
) throw $break;
614 any: function(iterator
, context
) {
615 iterator
= iterator
? iterator
.bind(context
) : Prototype
.K
;
617 this.each(function(value
, index
) {
618 if (result
= !!iterator(value
, index
))
624 collect: function(iterator
, context
) {
625 iterator
= iterator
? iterator
.bind(context
) : Prototype
.K
;
627 this.each(function(value
, index
) {
628 results
.push(iterator(value
, index
));
633 detect: function(iterator
, context
) {
634 iterator
= iterator
.bind(context
);
636 this.each(function(value
, index
) {
637 if (iterator(value
, index
)) {
645 findAll: function(iterator
, context
) {
646 iterator
= iterator
.bind(context
);
648 this.each(function(value
, index
) {
649 if (iterator(value
, index
))
655 grep: function(filter
, iterator
, context
) {
656 iterator
= iterator
? iterator
.bind(context
) : Prototype
.K
;
659 if (Object
.isString(filter
))
660 filter
= new RegExp(filter
);
662 this.each(function(value
, index
) {
663 if (filter
.match(value
))
664 results
.push(iterator(value
, index
));
669 include: function(object
) {
670 if (Object
.isFunction(this.indexOf
))
671 if (this.indexOf(object
) != -1) return true;
674 this.each(function(value
) {
675 if (value
== object
) {
683 inGroupsOf: function(number
, fillWith
) {
684 fillWith
= fillWith
=== undefined ? null : fillWith
;
685 return this.eachSlice(number
, function(slice
) {
686 while(slice
.length
< number
) slice
.push(fillWith
);
691 inject: function(memo
, iterator
, context
) {
692 iterator
= iterator
.bind(context
);
693 this.each(function(value
, index
) {
694 memo
= iterator(memo
, value
, index
);
699 invoke: function(method
) {
700 var args
= $A(arguments
).slice(1);
701 return this.map(function(value
) {
702 return value
[method
].apply(value
, args
);
706 max: function(iterator
, context
) {
707 iterator
= iterator
? iterator
.bind(context
) : Prototype
.K
;
709 this.each(function(value
, index
) {
710 value
= iterator(value
, index
);
711 if (result
== undefined || value
>= result
)
717 min: function(iterator
, context
) {
718 iterator
= iterator
? iterator
.bind(context
) : Prototype
.K
;
720 this.each(function(value
, index
) {
721 value
= iterator(value
, index
);
722 if (result
== undefined || value
< result
)
728 partition: function(iterator
, context
) {
729 iterator
= iterator
? iterator
.bind(context
) : Prototype
.K
;
730 var trues
= [], falses
= [];
731 this.each(function(value
, index
) {
732 (iterator(value
, index
) ?
733 trues
: falses
).push(value
);
735 return [trues
, falses
];
738 pluck: function(property
) {
740 this.each(function(value
) {
741 results
.push(value
[property
]);
746 reject: function(iterator
, context
) {
747 iterator
= iterator
.bind(context
);
749 this.each(function(value
, index
) {
750 if (!iterator(value
, index
))
756 sortBy: function(iterator
, context
) {
757 iterator
= iterator
.bind(context
);
758 return this.map(function(value
, index
) {
759 return {value
: value
, criteria
: iterator(value
, index
)};
760 }).sort(function(left
, right
) {
761 var a
= left
.criteria
, b
= right
.criteria
;
762 return a
< b
? -1 : a
> b
? 1 : 0;
766 toArray: function() {
771 var iterator
= Prototype
.K
, args
= $A(arguments
);
772 if (Object
.isFunction(args
.last()))
773 iterator
= args
.pop();
775 var collections
= [this].concat(args
).map($A
);
776 return this.map(function(value
, index
) {
777 return iterator(collections
.pluck(index
));
782 return this.toArray().length
;
785 inspect: function() {
786 return '#<Enumerable:' + this.toArray().inspect() + '>';
790 Object
.extend(Enumerable
, {
791 map
: Enumerable
.collect
,
792 find
: Enumerable
.detect
,
793 select
: Enumerable
.findAll
,
794 filter
: Enumerable
.findAll
,
795 member
: Enumerable
.include
,
796 entries
: Enumerable
.toArray
,
797 every
: Enumerable
.all
,
800 function $A(iterable
) {
801 if (!iterable
) return [];
802 if (iterable
.toArray
) return iterable
.toArray();
803 var length
= iterable
.length
, results
= new Array(length
);
804 while (length
--) results
[length
] = iterable
[length
];
808 if (Prototype
.Browser
.WebKit
) {
809 function $A(iterable
) {
810 if (!iterable
) return [];
811 if (!(Object
.isFunction(iterable
) && iterable
== '[object NodeList]') &&
812 iterable
.toArray
) return iterable
.toArray();
813 var length
= iterable
.length
, results
= new Array(length
);
814 while (length
--) results
[length
] = iterable
[length
];
821 Object
.extend(Array
.prototype, Enumerable
);
823 if (!Array
.prototype._reverse
) Array
.prototype._reverse
= Array
.prototype.reverse
;
825 Object
.extend(Array
.prototype, {
826 _each: function(iterator
) {
827 for (var i
= 0, length
= this.length
; i
< length
; i
++)
841 return this[this.length
- 1];
844 compact: function() {
845 return this.select(function(value
) {
846 return value
!= null;
850 flatten: function() {
851 return this.inject([], function(array
, value
) {
852 return array
.concat(Object
.isArray(value
) ?
853 value
.flatten() : [value
]);
857 without: function() {
858 var values
= $A(arguments
);
859 return this.select(function(value
) {
860 return !values
.include(value
);
864 reverse: function(inline
) {
865 return (inline
!== false ? this : this.toArray())._reverse();
869 return this.length
> 1 ? this : this[0];
872 uniq: function(sorted
) {
873 return this.inject([], function(array
, value
, index
) {
874 if (0 == index
|| (sorted
? array
.last() != value
: !array
.include(value
)))
880 intersect: function(array
) {
881 return this.uniq().findAll(function(item
) {
882 return array
.detect(function(value
) { return item
=== value
});
887 return [].concat(this);
894 inspect: function() {
895 return '[' + this.map(Object
.inspect
).join(', ') + ']';
900 this.each(function(object
) {
901 var value
= Object
.toJSON(object
);
902 if (value
!== undefined) results
.push(value
);
904 return '[' + results
.join(', ') + ']';
908 // use native browser JS 1.6 implementation if available
909 if (Object
.isFunction(Array
.prototype.forEach
))
910 Array
.prototype._each
= Array
.prototype.forEach
;
912 if (!Array
.prototype.indexOf
) Array
.prototype.indexOf = function(item
, i
) {
914 var length
= this.length
;
915 if (i
< 0) i
= length
+ i
;
916 for (; i
< length
; i
++)
917 if (this[i
] === item
) return i
;
921 if (!Array
.prototype.lastIndexOf
) Array
.prototype.lastIndexOf = function(item
, i
) {
922 i
= isNaN(i
) ? this.length
: (i
< 0 ? this.length
+ i
: i
) + 1;
923 var n
= this.slice(0, i
).reverse().indexOf(item
);
924 return (n
< 0) ? n
: i
- n
- 1;
927 Array
.prototype.toArray
= Array
.prototype.clone
;
929 function $w(string
) {
930 if (!Object
.isString(string
)) return [];
931 string
= string
.strip();
932 return string
? string
.split(/\s+/) : [];
935 if (Prototype
.Browser
.Opera
){
936 Array
.prototype.concat = function() {
938 for (var i
= 0, length
= this.length
; i
< length
; i
++) array
.push(this[i
]);
939 for (var i
= 0, length
= arguments
.length
; i
< length
; i
++) {
940 if (Object
.isArray(arguments
[i
])) {
941 for (var j
= 0, arrayLength
= arguments
[i
].length
; j
< arrayLength
; j
++)
942 array
.push(arguments
[i
][j
]);
944 array
.push(arguments
[i
]);
950 Object
.extend(Number
.prototype, {
951 toColorPart: function() {
952 return this.toPaddedString(2, 16);
959 times: function(iterator
) {
960 $R(0, this, true).each(iterator
);
964 toPaddedString: function(length
, radix
) {
965 var string
= this.toString(radix
|| 10);
966 return '0'.times(length
- string
.length
) + string
;
970 return isFinite(this) ? this.toString() : 'null';
974 $w('abs round ceil floor').each(function(method
){
975 Number
.prototype[method
] = Math
[method
].methodize();
977 function $H(object
) {
978 return new Hash(object
);
981 var Hash
= Class
.create(Enumerable
, (function() {
983 var i
= 0, Test = function(value
) { this.key
= value
};
984 Test
.prototype.key
= 'foo';
985 for (var property
in new Test('bar')) i
++;
988 function each(iterator
) {
990 for (var key
in this._object
) {
991 var value
= this._object
[key
];
992 if (cache
.include(key
)) continue;
994 var pair
= [key
, value
];
1001 function each(iterator
) {
1002 for (var key
in this._object
) {
1003 var value
= this._object
[key
], pair
= [key
, value
];
1011 function toQueryPair(key
, value
) {
1012 if (Object
.isUndefined(value
)) return key
;
1013 return key
+ '=' + encodeURIComponent(String
.interpret(value
));
1017 initialize: function(object
) {
1018 this._object
= Object
.isHash(object
) ? object
.toObject() : Object
.clone(object
);
1023 set: function(key
, value
) {
1024 return this._object
[key
] = value
;
1027 get: function(key
) {
1028 return this._object
[key
];
1031 unset: function(key
) {
1032 var value
= this._object
[key
];
1033 delete this._object
[key
];
1037 toObject: function() {
1038 return Object
.clone(this._object
);
1042 return this.pluck('key');
1045 values: function() {
1046 return this.pluck('value');
1049 index: function(value
) {
1050 var match
= this.detect(function(pair
) {
1051 return pair
.value
=== value
;
1053 return match
&& match
.key
;
1056 merge: function(object
) {
1057 return this.clone().update(object
);
1060 update: function(object
) {
1061 return new Hash(object
).inject(this, function(result
, pair
) {
1062 result
.set(pair
.key
, pair
.value
);
1067 toQueryString: function() {
1068 return this.map(function(pair
) {
1069 var key
= encodeURIComponent(pair
.key
), values
= pair
.value
;
1071 if (values
&& typeof values
== 'object') {
1072 if (Object
.isArray(values
))
1073 return values
.map(toQueryPair
.curry(key
)).join('&');
1075 return toQueryPair(key
, values
);
1079 inspect: function() {
1080 return '#<Hash:{' + this.map(function(pair
) {
1081 return pair
.map(Object
.inspect
).join(': ');
1082 }).join(', ') + '}>';
1085 toJSON: function() {
1086 return Object
.toJSON(this.toObject());
1090 return new Hash(this);
1096 var ObjectRange
= Class
.create(Enumerable
, {
1097 initialize: function(start
, end
, exclusive
) {
1100 this.exclusive
= exclusive
;
1103 _each: function(iterator
) {
1104 var value
= this.start
;
1105 while (this.include(value
)) {
1107 value
= value
.succ();
1111 include: function(value
) {
1112 if (value
< this.start
)
1115 return value
< this.end
;
1116 return value
<= this.end
;
1120 var $R = function(start
, end
, exclusive
) {
1121 return new ObjectRange(start
, end
, exclusive
);
1125 getTransport: function() {
1127 function() {return new XMLHttpRequest()},
1128 function() {return new ActiveXObject('Msxml2.XMLHTTP')},
1129 function() {return new ActiveXObject('Microsoft.XMLHTTP')}
1133 activeRequestCount
: 0
1139 _each: function(iterator
) {
1140 this.responders
._each(iterator
);
1143 register: function(responder
) {
1144 if (!this.include(responder
))
1145 this.responders
.push(responder
);
1148 unregister: function(responder
) {
1149 this.responders
= this.responders
.without(responder
);
1152 dispatch: function(callback
, request
, transport
, json
) {
1153 this.each(function(responder
) {
1154 if (Object
.isFunction(responder
[callback
])) {
1156 responder
[callback
].apply(responder
, [request
, transport
, json
]);
1163 Object
.extend(Ajax
.Responders
, Enumerable
);
1165 Ajax
.Responders
.register({
1166 onCreate: function() { Ajax
.activeRequestCount
++ },
1167 onComplete: function() { Ajax
.activeRequestCount
-- }
1170 Ajax
.Base
= Class
.create({
1171 initialize: function(options
) {
1175 contentType
: 'application/x-www-form-urlencoded',
1181 Object
.extend(this.options
, options
|| { });
1183 this.options
.method
= this.options
.method
.toLowerCase();
1184 if (Object
.isString(this.options
.parameters
))
1185 this.options
.parameters
= this.options
.parameters
.toQueryParams();
1189 Ajax
.Request
= Class
.create(Ajax
.Base
, {
1192 initialize: function($super, url
, options
) {
1194 this.transport
= Ajax
.getTransport();
1198 request: function(url
) {
1200 this.method
= this.options
.method
;
1201 var params
= Object
.clone(this.options
.parameters
);
1203 if (!['get', 'post'].include(this.method
)) {
1204 // simulate other verbs over post
1205 params
['_method'] = this.method
;
1206 this.method
= 'post';
1209 this.parameters
= params
;
1211 if (params
= Object
.toQueryString(params
)) {
1212 // when GET, append parameters to URL
1213 if (this.method
== 'get')
1214 this.url
+= (this.url
.include('?') ? '&' : '?') + params
;
1215 else if (/Konqueror|Safari|KHTML/.test(navigator
.userAgent
))
1220 var response
= new Ajax
.Response(this);
1221 if (this.options
.onCreate
) this.options
.onCreate(response
);
1222 Ajax
.Responders
.dispatch('onCreate', this, response
);
1224 this.transport
.open(this.method
.toUpperCase(), this.url
,
1225 this.options
.asynchronous
);
1227 if (this.options
.asynchronous
) this.respondToReadyState
.bind(this).defer(1);
1229 this.transport
.onreadystatechange
= this.onStateChange
.bind(this);
1230 this.setRequestHeaders();
1232 this.body
= this.method
== 'post' ? (this.options
.postBody
|| params
) : null;
1233 this.transport
.send(this.body
);
1235 /* Force Firefox to handle ready state 4 for synchronous requests */
1236 if (!this.options
.asynchronous
&& this.transport
.overrideMimeType
)
1237 this.onStateChange();
1241 this.dispatchException(e
);
1245 onStateChange: function() {
1246 var readyState
= this.transport
.readyState
;
1247 if (readyState
> 1 && !((readyState
== 4) && this._complete
))
1248 this.respondToReadyState(this.transport
.readyState
);
1251 setRequestHeaders: function() {
1253 'X-Requested-With': 'XMLHttpRequest',
1254 'X-Prototype-Version': Prototype
.Version
,
1255 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
1258 if (this.method
== 'post') {
1259 headers
['Content-type'] = this.options
.contentType
+
1260 (this.options
.encoding
? '; charset=' + this.options
.encoding
: '');
1262 /* Force "Connection: close" for older Mozilla browsers to work
1263 * around a bug where XMLHttpRequest sends an incorrect
1264 * Content-length header. See Mozilla Bugzilla #246651.
1266 if (this.transport
.overrideMimeType
&&
1267 (navigator
.userAgent
.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
1268 headers
['Connection'] = 'close';
1271 // user-defined headers
1272 if (typeof this.options
.requestHeaders
== 'object') {
1273 var extras
= this.options
.requestHeaders
;
1275 if (Object
.isFunction(extras
.push
))
1276 for (var i
= 0, length
= extras
.length
; i
< length
; i
+= 2)
1277 headers
[extras
[i
]] = extras
[i
+1];
1279 $H(extras
).each(function(pair
) { headers
[pair
.key
] = pair
.value
});
1282 for (var name
in headers
)
1283 this.transport
.setRequestHeader(name
, headers
[name
]);
1286 success: function() {
1287 var status
= this.getStatus();
1288 return !status
|| (status
>= 200 && status
< 300);
1291 getStatus: function() {
1293 return this.transport
.status
|| 0;
1294 } catch (e
) { return 0 }
1297 respondToReadyState: function(readyState
) {
1298 var state
= Ajax
.Request
.Events
[readyState
], response
= new Ajax
.Response(this);
1300 if (state
== 'Complete') {
1302 this._complete
= true;
1303 (this.options
['on' + response
.status
]
1304 || this.options
['on' + (this.success() ? 'Success' : 'Failure')]
1305 || Prototype
.emptyFunction
)(response
, response
.headerJSON
);
1307 this.dispatchException(e
);
1310 var contentType
= response
.getHeader('Content-type');
1311 if (this.options
.evalJS
== 'force'
1312 || (this.options
.evalJS
&& contentType
1313 && contentType
.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
1314 this.evalResponse();
1318 (this.options
['on' + state
] || Prototype
.emptyFunction
)(response
, response
.headerJSON
);
1319 Ajax
.Responders
.dispatch('on' + state
, this, response
, response
.headerJSON
);
1321 this.dispatchException(e
);
1324 if (state
== 'Complete') {
1325 // avoid memory leak in MSIE: clean up
1326 this.transport
.onreadystatechange
= Prototype
.emptyFunction
;
1330 getHeader: function(name
) {
1332 return this.transport
.getResponseHeader(name
);
1333 } catch (e
) { return null }
1336 evalResponse: function() {
1338 return eval((this.transport
.responseText
|| '').unfilterJSON());
1340 this.dispatchException(e
);
1344 dispatchException: function(exception
) {
1345 (this.options
.onException
|| Prototype
.emptyFunction
)(this, exception
);
1346 Ajax
.Responders
.dispatch('onException', this, exception
);
1350 Ajax
.Request
.Events
=
1351 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
1353 Ajax
.Response
= Class
.create({
1354 initialize: function(request
){
1355 this.request
= request
;
1356 var transport
= this.transport
= request
.transport
,
1357 readyState
= this.readyState
= transport
.readyState
;
1359 if((readyState
> 2 && !Prototype
.Browser
.IE
) || readyState
== 4) {
1360 this.status
= this.getStatus();
1361 this.statusText
= this.getStatusText();
1362 this.responseText
= String
.interpret(transport
.responseText
);
1363 this.headerJSON
= this._getHeaderJSON();
1366 if(readyState
== 4) {
1367 var xml
= transport
.responseXML
;
1368 this.responseXML
= xml
=== undefined ? null : xml
;
1369 this.responseJSON
= this._getResponseJSON();
1376 getStatus
: Ajax
.Request
.prototype.getStatus
,
1378 getStatusText: function() {
1380 return this.transport
.statusText
|| '';
1381 } catch (e
) { return '' }
1384 getHeader
: Ajax
.Request
.prototype.getHeader
,
1386 getAllHeaders: function() {
1388 return this.getAllResponseHeaders();
1389 } catch (e
) { return null }
1392 getResponseHeader: function(name
) {
1393 return this.transport
.getResponseHeader(name
);
1396 getAllResponseHeaders: function() {
1397 return this.transport
.getAllResponseHeaders();
1400 _getHeaderJSON: function() {
1401 var json
= this.getHeader('X-JSON');
1403 return json
? json
.evalJSON(this.request
.options
.sanitizeJSON
) : null;
1405 this.request
.dispatchException(e
);
1409 _getResponseJSON: function() {
1410 var options
= this.request
.options
;
1412 if (options
.evalJSON
== 'force' || (options
.evalJSON
&&
1413 (this.getHeader('Content-type') || '').include('application/json')))
1414 return this.transport
.responseText
.evalJSON(options
.sanitizeJSON
);
1417 this.request
.dispatchException(e
);
1422 Ajax
.Updater
= Class
.create(Ajax
.Request
, {
1423 initialize: function($super, container
, url
, options
) {
1425 success
: (container
.success
|| container
),
1426 failure
: (container
.failure
|| (container
.success
? null : container
))
1429 options
= options
|| { };
1430 var onComplete
= options
.onComplete
;
1431 options
.onComplete
= (function(response
, param
) {
1432 this.updateContent(response
.responseText
);
1433 if (Object
.isFunction(onComplete
)) onComplete(response
, param
);
1436 $super(url
, options
);
1439 updateContent: function(responseText
) {
1440 var receiver
= this.container
[this.success() ? 'success' : 'failure'],
1441 options
= this.options
;
1443 if (!options
.evalScripts
) responseText
= responseText
.stripScripts();
1445 if (receiver
= $(receiver
)) {
1446 if (options
.insertion
) {
1447 if (Object
.isString(options
.insertion
)) {
1448 var insertion
= { }; insertion
[options
.insertion
] = responseText
;
1449 receiver
.insert(insertion
);
1451 else options
.insertion(receiver
, responseText
);
1453 else receiver
.update(responseText
);
1456 if (this.success()) {
1457 if (this.onComplete
) this.onComplete
.bind(this).defer();
1462 Ajax
.PeriodicalUpdater
= Class
.create(Ajax
.Base
, {
1463 initialize: function($super, container
, url
, options
) {
1465 this.onComplete
= this.options
.onComplete
;
1467 this.frequency
= (this.options
.frequency
|| 2);
1468 this.decay
= (this.options
.decay
|| 1);
1471 this.container
= container
;
1478 this.options
.onComplete
= this.updateComplete
.bind(this);
1479 this.onTimerEvent();
1483 this.updater
.options
.onComplete
= undefined;
1484 clearTimeout(this.timer
);
1485 (this.onComplete
|| Prototype
.emptyFunction
).apply(this, arguments
);
1488 updateComplete: function(response
) {
1489 if (this.options
.decay
) {
1490 this.decay
= (response
.responseText
== this.lastText
?
1491 this.decay
* this.options
.decay
: 1);
1493 this.lastText
= response
.responseText
;
1495 this.timer
= this.onTimerEvent
.bind(this).delay(this.decay
* this.frequency
);
1498 onTimerEvent: function() {
1499 this.updater
= new Ajax
.Updater(this.container
, this.url
, this.options
);
1502 function $(element
) {
1503 if (arguments
.length
> 1) {
1504 for (var i
= 0, elements
= [], length
= arguments
.length
; i
< length
; i
++)
1505 elements
.push($(arguments
[i
]));
1508 if (Object
.isString(element
))
1509 element
= document
.getElementById(element
);
1510 return Element
.extend(element
);
1513 if (Prototype
.BrowserFeatures
.XPath
) {
1514 document
._getElementsByXPath = function(expression
, parentElement
) {
1516 var query
= document
.evaluate(expression
, $(parentElement
) || document
,
1517 null, XPathResult
.ORDERED_NODE_SNAPSHOT_TYPE
, null);
1518 for (var i
= 0, length
= query
.snapshotLength
; i
< length
; i
++)
1519 results
.push(Element
.extend(query
.snapshotItem(i
)));
1524 /*--------------------------------------------------------------------------*/
1526 if (!window
.Node
) var Node
= { };
1528 if (!Node
.ELEMENT_NODE
) {
1529 // DOM level 2 ECMAScript Language Binding
1530 Object
.extend(Node
, {
1534 CDATA_SECTION_NODE
: 4,
1535 ENTITY_REFERENCE_NODE
: 5,
1537 PROCESSING_INSTRUCTION_NODE
: 7,
1540 DOCUMENT_TYPE_NODE
: 10,
1541 DOCUMENT_FRAGMENT_NODE
: 11,
1547 var element
= this.Element
;
1548 this.Element = function(tagName
, attributes
) {
1549 attributes
= attributes
|| { };
1550 tagName
= tagName
.toLowerCase();
1551 var cache
= Element
.cache
;
1552 if (Prototype
.Browser
.IE
&& attributes
.name
) {
1553 tagName
= '<' + tagName
+ ' name="' + attributes
.name
+ '">';
1554 delete attributes
.name
;
1555 return Element
.writeAttribute(document
.createElement(tagName
), attributes
);
1557 if (!cache
[tagName
]) cache
[tagName
] = Element
.extend(document
.createElement(tagName
));
1558 return Element
.writeAttribute(cache
[tagName
].cloneNode(false), attributes
);
1560 Object
.extend(this.Element
, element
|| { });
1563 Element
.cache
= { };
1566 visible: function(element
) {
1567 return $(element
).style
.display
!= 'none';
1570 toggle: function(element
) {
1571 element
= $(element
);
1572 Element
[Element
.visible(element
) ? 'hide' : 'show'](element
);
1576 hide: function(element
) {
1577 $(element
).style
.display
= 'none';
1581 show: function(element
) {
1582 $(element
).style
.display
= '';
1586 remove: function(element
) {
1587 element
= $(element
);
1588 element
.parentNode
.removeChild(element
);
1592 update: function(element
, content
) {
1593 element
= $(element
);
1594 if (content
&& content
.toElement
) content
= content
.toElement();
1595 if (Object
.isElement(content
)) return element
.update().insert(content
);
1596 content
= Object
.toHTML(content
);
1597 element
.innerHTML
= content
.stripScripts();
1598 content
.evalScripts
.bind(content
).defer();
1602 replace: function(element
, content
) {
1603 element
= $(element
);
1604 if (content
&& content
.toElement
) content
= content
.toElement();
1605 else if (!Object
.isElement(content
)) {
1606 content
= Object
.toHTML(content
);
1607 var range
= element
.ownerDocument
.createRange();
1608 range
.selectNode(element
);
1609 content
.evalScripts
.bind(content
).defer();
1610 content
= range
.createContextualFragment(content
.stripScripts());
1612 element
.parentNode
.replaceChild(content
, element
);
1616 insert: function(element
, insertions
) {
1617 element
= $(element
);
1619 if (Object
.isString(insertions
) || Object
.isNumber(insertions
) ||
1620 Object
.isElement(insertions
) || (insertions
&& (insertions
.toElement
|| insertions
.toHTML
)))
1621 insertions
= {bottom
:insertions
};
1623 var content
, t
, range
;
1625 for (position
in insertions
) {
1626 content
= insertions
[position
];
1627 position
= position
.toLowerCase();
1628 t
= Element
._insertionTranslations
[position
];
1630 if (content
&& content
.toElement
) content
= content
.toElement();
1631 if (Object
.isElement(content
)) {
1632 t
.insert(element
, content
);
1636 content
= Object
.toHTML(content
);
1638 range
= element
.ownerDocument
.createRange();
1639 t
.initializeRange(element
, range
);
1640 t
.insert(element
, range
.createContextualFragment(content
.stripScripts()));
1642 content
.evalScripts
.bind(content
).defer();
1648 wrap: function(element
, wrapper
, attributes
) {
1649 element
= $(element
);
1650 if (Object
.isElement(wrapper
))
1651 $(wrapper
).writeAttribute(attributes
|| { });
1652 else if (Object
.isString(wrapper
)) wrapper
= new Element(wrapper
, attributes
);
1653 else wrapper
= new Element('div', wrapper
);
1654 if (element
.parentNode
)
1655 element
.parentNode
.replaceChild(wrapper
, element
);
1656 wrapper
.appendChild(element
);
1660 inspect: function(element
) {
1661 element
= $(element
);
1662 var result
= '<' + element
.tagName
.toLowerCase();
1663 $H({'id': 'id', 'className': 'class'}).each(function(pair
) {
1664 var property
= pair
.first(), attribute
= pair
.last();
1665 var value
= (element
[property
] || '').toString();
1666 if (value
) result
+= ' ' + attribute
+ '=' + value
.inspect(true);
1668 return result
+ '>';
1671 recursivelyCollect: function(element
, property
) {
1672 element
= $(element
);
1674 while (element
= element
[property
])
1675 if (element
.nodeType
== 1)
1676 elements
.push(Element
.extend(element
));
1680 ancestors: function(element
) {
1681 return $(element
).recursivelyCollect('parentNode');
1684 descendants: function(element
) {
1685 return $A($(element
).getElementsByTagName('*')).each(Element
.extend
);
1688 firstDescendant: function(element
) {
1689 element
= $(element
).firstChild
;
1690 while (element
&& element
.nodeType
!= 1) element
= element
.nextSibling
;
1694 immediateDescendants: function(element
) {
1695 if (!(element
= $(element
).firstChild
)) return [];
1696 while (element
&& element
.nodeType
!= 1) element
= element
.nextSibling
;
1697 if (element
) return [element
].concat($(element
).nextSiblings());
1701 previousSiblings: function(element
) {
1702 return $(element
).recursivelyCollect('previousSibling');
1705 nextSiblings: function(element
) {
1706 return $(element
).recursivelyCollect('nextSibling');
1709 siblings: function(element
) {
1710 element
= $(element
);
1711 return element
.previousSiblings().reverse().concat(element
.nextSiblings());
1714 match: function(element
, selector
) {
1715 if (Object
.isString(selector
))
1716 selector
= new Selector(selector
);
1717 return selector
.match($(element
));
1720 up: function(element
, expression
, index
) {
1721 element
= $(element
);
1722 if (arguments
.length
== 1) return $(element
.parentNode
);
1723 var ancestors
= element
.ancestors();
1724 return expression
? Selector
.findElement(ancestors
, expression
, index
) :
1725 ancestors
[index
|| 0];
1728 down: function(element
, expression
, index
) {
1729 element
= $(element
);
1730 if (arguments
.length
== 1) return element
.firstDescendant();
1731 var descendants
= element
.descendants();
1732 return expression
? Selector
.findElement(descendants
, expression
, index
) :
1733 descendants
[index
|| 0];
1736 previous: function(element
, expression
, index
) {
1737 element
= $(element
);
1738 if (arguments
.length
== 1) return $(Selector
.handlers
.previousElementSibling(element
));
1739 var previousSiblings
= element
.previousSiblings();
1740 return expression
? Selector
.findElement(previousSiblings
, expression
, index
) :
1741 previousSiblings
[index
|| 0];
1744 next: function(element
, expression
, index
) {
1745 element
= $(element
);
1746 if (arguments
.length
== 1) return $(Selector
.handlers
.nextElementSibling(element
));
1747 var nextSiblings
= element
.nextSiblings();
1748 return expression
? Selector
.findElement(nextSiblings
, expression
, index
) :
1749 nextSiblings
[index
|| 0];
1752 select: function() {
1753 var args
= $A(arguments
), element
= $(args
.shift());
1754 return Selector
.findChildElements(element
, args
);
1757 adjacent: function() {
1758 var args
= $A(arguments
), element
= $(args
.shift());
1759 return Selector
.findChildElements(element
.parentNode
, args
).without(element
);
1762 identify: function(element
) {
1763 element
= $(element
);
1764 var id
= element
.readAttribute('id'), self
= arguments
.callee
;
1766 do { id
= 'anonymous_element_' + self
.counter
++ } while ($(id
));
1767 element
.writeAttribute('id', id
);
1771 readAttribute: function(element
, name
) {
1772 element
= $(element
);
1773 if (Prototype
.Browser
.IE
) {
1774 var t
= Element
._attributeTranslations
.read
;
1775 if (t
.values
[name
]) return t
.values
[name
](element
, name
);
1776 if (t
.names
[name
]) name
= t
.names
[name
];
1777 if (name
.include(':')) {
1778 return (!element
.attributes
|| !element
.attributes
[name
]) ? null :
1779 element
.attributes
[name
].value
;
1782 return element
.getAttribute(name
);
1785 writeAttribute: function(element
, name
, value
) {
1786 element
= $(element
);
1787 var attributes
= { }, t
= Element
._attributeTranslations
.write
;
1789 if (typeof name
== 'object') attributes
= name
;
1790 else attributes
[name
] = value
=== undefined ? true : value
;
1792 for (var attr
in attributes
) {
1793 var name
= t
.names
[attr
] || attr
, value
= attributes
[attr
];
1794 if (t
.values
[attr
]) name
= t
.values
[attr
](element
, value
);
1795 if (value
=== false || value
=== null)
1796 element
.removeAttribute(name
);
1797 else if (value
=== true)
1798 element
.setAttribute(name
, name
);
1799 else element
.setAttribute(name
, value
);
1804 getHeight: function(element
) {
1805 return $(element
).getDimensions().height
;
1808 getWidth: function(element
) {
1809 return $(element
).getDimensions().width
;
1812 classNames: function(element
) {
1813 return new Element
.ClassNames(element
);
1816 hasClassName: function(element
, className
) {
1817 if (!(element
= $(element
))) return;
1818 var elementClassName
= element
.className
;
1819 return (elementClassName
.length
> 0 && (elementClassName
== className
||
1820 elementClassName
.match(new RegExp("(^|\\s)" + className
+ "(\\s|$)"))));
1823 addClassName: function(element
, className
) {
1824 if (!(element
= $(element
))) return;
1825 if (!element
.hasClassName(className
))
1826 element
.className
+= (element
.className
? ' ' : '') + className
;
1830 removeClassName: function(element
, className
) {
1831 if (!(element
= $(element
))) return;
1832 element
.className
= element
.className
.replace(
1833 new RegExp("(^|\\s+)" + className
+ "(\\s+|$)"), ' ').strip();
1837 toggleClassName: function(element
, className
) {
1838 if (!(element
= $(element
))) return;
1839 return element
[element
.hasClassName(className
) ?
1840 'removeClassName' : 'addClassName'](className
);
1843 // removes whitespace-only text node children
1844 cleanWhitespace: function(element
) {
1845 element
= $(element
);
1846 var node
= element
.firstChild
;
1848 var nextNode
= node
.nextSibling
;
1849 if (node
.nodeType
== 3 && !/\S/.test(node
.nodeValue
))
1850 element
.removeChild(node
);
1856 empty: function(element
) {
1857 return $(element
).innerHTML
.blank();
1860 descendantOf: function(element
, ancestor
) {
1861 element
= $(element
), ancestor
= $(ancestor
);
1862 while (element
= element
.parentNode
)
1863 if (element
== ancestor
) return true;
1867 scrollTo: function(element
) {
1868 element
= $(element
);
1869 var pos
= element
.cumulativeOffset();
1870 window
.scrollTo(pos
[0], pos
[1]);
1874 getStyle: function(element
, style
) {
1875 element
= $(element
);
1876 style
= style
== 'float' ? 'cssFloat' : style
.camelize();
1877 var value
= element
.style
[style
];
1879 var css
= document
.defaultView
.getComputedStyle(element
, null);
1880 value
= css
? css
[style
] : null;
1882 if (style
== 'opacity') return value
? parseFloat(value
) : 1.0;
1883 return value
== 'auto' ? null : value
;
1886 getOpacity: function(element
) {
1887 return $(element
).getStyle('opacity');
1890 setStyle: function(element
, styles
) {
1891 element
= $(element
);
1892 var elementStyle
= element
.style
, match
;
1893 if (Object
.isString(styles
)) {
1894 element
.style
.cssText
+= ';' + styles
;
1895 return styles
.include('opacity') ?
1896 element
.setOpacity(styles
.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element
;
1898 for (var property
in styles
)
1899 if (property
== 'opacity') element
.setOpacity(styles
[property
]);
1901 elementStyle
[(property
== 'float' || property
== 'cssFloat') ?
1902 (elementStyle
.styleFloat
=== undefined ? 'cssFloat' : 'styleFloat') :
1903 property
] = styles
[property
];
1908 setOpacity: function(element
, value
) {
1909 element
= $(element
);
1910 element
.style
.opacity
= (value
== 1 || value
=== '') ? '' :
1911 (value
< 0.00001) ? 0 : value
;
1915 getDimensions: function(element
) {
1916 element
= $(element
);
1917 var display
= $(element
).getStyle('display');
1918 if (display
!= 'none' && display
!= null) // Safari bug
1919 return {width
: element
.offsetWidth
, height
: element
.offsetHeight
};
1921 // All *Width and *Height properties give 0 on elements with display none,
1922 // so enable the element temporarily
1923 var els
= element
.style
;
1924 var originalVisibility
= els
.visibility
;
1925 var originalPosition
= els
.position
;
1926 var originalDisplay
= els
.display
;
1927 els
.visibility
= 'hidden';
1928 els
.position
= 'absolute';
1929 els
.display
= 'block';
1930 var originalWidth
= element
.clientWidth
;
1931 var originalHeight
= element
.clientHeight
;
1932 els
.display
= originalDisplay
;
1933 els
.position
= originalPosition
;
1934 els
.visibility
= originalVisibility
;
1935 return {width
: originalWidth
, height
: originalHeight
};
1938 makePositioned: function(element
) {
1939 element
= $(element
);
1940 var pos
= Element
.getStyle(element
, 'position');
1941 if (pos
== 'static' || !pos
) {
1942 element
._madePositioned
= true;
1943 element
.style
.position
= 'relative';
1944 // Opera returns the offset relative to the positioning context, when an
1945 // element is position relative but top and left have not been defined
1947 element
.style
.top
= 0;
1948 element
.style
.left
= 0;
1954 undoPositioned: function(element
) {
1955 element
= $(element
);
1956 if (element
._madePositioned
) {
1957 element
._madePositioned
= undefined;
1958 element
.style
.position
=
1960 element
.style
.left
=
1961 element
.style
.bottom
=
1962 element
.style
.right
= '';
1967 makeClipping: function(element
) {
1968 element
= $(element
);
1969 if (element
._overflow
) return element
;
1970 element
._overflow
= Element
.getStyle(element
, 'overflow') || 'auto';
1971 if (element
._overflow
!== 'hidden')
1972 element
.style
.overflow
= 'hidden';
1976 undoClipping: function(element
) {
1977 element
= $(element
);
1978 if (!element
._overflow
) return element
;
1979 element
.style
.overflow
= element
._overflow
== 'auto' ? '' : element
._overflow
;
1980 element
._overflow
= null;
1984 cumulativeOffset: function(element
) {
1985 var valueT
= 0, valueL
= 0;
1987 valueT
+= element
.offsetTop
|| 0;
1988 valueL
+= element
.offsetLeft
|| 0;
1989 element
= element
.offsetParent
;
1991 return Element
._returnOffset(valueL
, valueT
);
1994 positionedOffset: function(element
) {
1995 var valueT
= 0, valueL
= 0;
1997 valueT
+= element
.offsetTop
|| 0;
1998 valueL
+= element
.offsetLeft
|| 0;
1999 element
= element
.offsetParent
;
2001 if (element
.tagName
== 'BODY') break;
2002 var p
= Element
.getStyle(element
, 'position');
2003 if (p
== 'relative' || p
== 'absolute') break;
2006 return Element
._returnOffset(valueL
, valueT
);
2009 absolutize: function(element
) {
2010 element
= $(element
);
2011 if (element
.getStyle('position') == 'absolute') return;
2012 // Position.prepare(); // To be done manually by Scripty when it needs it.
2014 var offsets
= element
.positionedOffset();
2015 var top
= offsets
[1];
2016 var left
= offsets
[0];
2017 var width
= element
.clientWidth
;
2018 var height
= element
.clientHeight
;
2020 element
._originalLeft
= left
- parseFloat(element
.style
.left
|| 0);
2021 element
._originalTop
= top
- parseFloat(element
.style
.top
|| 0);
2022 element
._originalWidth
= element
.style
.width
;
2023 element
._originalHeight
= element
.style
.height
;
2025 element
.style
.position
= 'absolute';
2026 element
.style
.top
= top
+ 'px';
2027 element
.style
.left
= left
+ 'px';
2028 element
.style
.width
= width
+ 'px';
2029 element
.style
.height
= height
+ 'px';
2033 relativize: function(element
) {
2034 element
= $(element
);
2035 if (element
.getStyle('position') == 'relative') return;
2036 // Position.prepare(); // To be done manually by Scripty when it needs it.
2038 element
.style
.position
= 'relative';
2039 var top
= parseFloat(element
.style
.top
|| 0) - (element
._originalTop
|| 0);
2040 var left
= parseFloat(element
.style
.left
|| 0) - (element
._originalLeft
|| 0);
2042 element
.style
.top
= top
+ 'px';
2043 element
.style
.left
= left
+ 'px';
2044 element
.style
.height
= element
._originalHeight
;
2045 element
.style
.width
= element
._originalWidth
;
2049 cumulativeScrollOffset: function(element
) {
2050 var valueT
= 0, valueL
= 0;
2052 valueT
+= element
.scrollTop
|| 0;
2053 valueL
+= element
.scrollLeft
|| 0;
2054 element
= element
.parentNode
;
2056 return Element
._returnOffset(valueL
, valueT
);
2059 getOffsetParent: function(element
) {
2060 if (element
.offsetParent
) return $(element
.offsetParent
);
2061 if (element
== document
.body
) return $(element
);
2063 while ((element
= element
.parentNode
) && element
!= document
.body
)
2064 if (Element
.getStyle(element
, 'position') != 'static')
2067 return $(document
.body
);
2070 viewportOffset: function(forElement
) {
2071 var valueT
= 0, valueL
= 0;
2073 var element
= forElement
;
2075 valueT
+= element
.offsetTop
|| 0;
2076 valueL
+= element
.offsetLeft
|| 0;
2079 if (element
.offsetParent
== document
.body
&&
2080 Element
.getStyle(element
, 'position') == 'absolute') break;
2082 } while (element
= element
.offsetParent
);
2084 element
= forElement
;
2086 if (!Prototype
.Browser
.Opera
|| element
.tagName
== 'BODY') {
2087 valueT
-= element
.scrollTop
|| 0;
2088 valueL
-= element
.scrollLeft
|| 0;
2090 } while (element
= element
.parentNode
);
2092 return Element
._returnOffset(valueL
, valueT
);
2095 clonePosition: function(element
, source
) {
2096 var options
= Object
.extend({
2103 }, arguments
[2] || { });
2105 // find page position of source
2107 var p
= source
.viewportOffset();
2109 // find coordinate system to use
2110 element
= $(element
);
2113 // delta [0,0] will do fine with position: fixed elements,
2114 // position:absolute needs offsetParent deltas
2115 if (Element
.getStyle(element
, 'position') == 'absolute') {
2116 parent
= element
.getOffsetParent();
2117 delta
= parent
.viewportOffset();
2120 // correct by body offsets (fixes Safari)
2121 if (parent
== document
.body
) {
2122 delta
[0] -= document
.body
.offsetLeft
;
2123 delta
[1] -= document
.body
.offsetTop
;
2127 if (options
.setLeft
) element
.style
.left
= (p
[0] - delta
[0] + options
.offsetLeft
) + 'px';
2128 if (options
.setTop
) element
.style
.top
= (p
[1] - delta
[1] + options
.offsetTop
) + 'px';
2129 if (options
.setWidth
) element
.style
.width
= source
.offsetWidth
+ 'px';
2130 if (options
.setHeight
) element
.style
.height
= source
.offsetHeight
+ 'px';
2135 Element
.Methods
.identify
.counter
= 1;
2137 Object
.extend(Element
.Methods
, {
2138 getElementsBySelector
: Element
.Methods
.select
,
2139 childElements
: Element
.Methods
.immediateDescendants
2142 Element
._attributeTranslations
= {
2153 if (!document
.createRange
|| Prototype
.Browser
.Opera
) {
2154 Element
.Methods
.insert = function(element
, insertions
) {
2155 element
= $(element
);
2157 if (Object
.isString(insertions
) || Object
.isNumber(insertions
) ||
2158 Object
.isElement(insertions
) || (insertions
&& (insertions
.toElement
|| insertions
.toHTML
)))
2159 insertions
= { bottom
: insertions
};
2161 var t
= Element
._insertionTranslations
, content
, position
, pos
, tagName
;
2163 for (position
in insertions
) {
2164 content
= insertions
[position
];
2165 position
= position
.toLowerCase();
2168 if (content
&& content
.toElement
) content
= content
.toElement();
2169 if (Object
.isElement(content
)) {
2170 pos
.insert(element
, content
);
2174 content
= Object
.toHTML(content
);
2175 tagName
= ((position
== 'before' || position
== 'after')
2176 ? element
.parentNode
: element
).tagName
.toUpperCase();
2178 if (t
.tags
[tagName
]) {
2179 var fragments
= Element
._getContentFromAnonymousElement(tagName
, content
.stripScripts());
2180 if (position
== 'top' || position
== 'after') fragments
.reverse();
2181 fragments
.each(pos
.insert
.curry(element
));
2183 else element
.insertAdjacentHTML(pos
.adjacency
, content
.stripScripts());
2185 content
.evalScripts
.bind(content
).defer();
2192 if (Prototype
.Browser
.Opera
) {
2193 Element
.Methods
._getStyle
= Element
.Methods
.getStyle
;
2194 Element
.Methods
.getStyle = function(element
, style
) {
2200 if (Element
._getStyle(element
, 'position') == 'static') return null;
2201 default: return Element
._getStyle(element
, style
);
2204 Element
.Methods
._readAttribute
= Element
.Methods
.readAttribute
;
2205 Element
.Methods
.readAttribute = function(element
, attribute
) {
2206 if (attribute
== 'title') return element
.title
;
2207 return Element
._readAttribute(element
, attribute
);
2211 else if (Prototype
.Browser
.IE
) {
2212 $w('positionedOffset getOffsetParent viewportOffset').each(function(method
) {
2213 Element
.Methods
[method
] = Element
.Methods
[method
].wrap(
2214 function(proceed
, element
) {
2215 element
= $(element
);
2216 var position
= element
.getStyle('position');
2217 if (position
!= 'static') return proceed(element
);
2218 element
.setStyle({ position
: 'relative' });
2219 var value
= proceed(element
);
2220 element
.setStyle({ position
: position
});
2226 Element
.Methods
.getStyle = function(element
, style
) {
2227 element
= $(element
);
2228 style
= (style
== 'float' || style
== 'cssFloat') ? 'styleFloat' : style
.camelize();
2229 var value
= element
.style
[style
];
2230 if (!value
&& element
.currentStyle
) value
= element
.currentStyle
[style
];
2232 if (style
== 'opacity') {
2233 if (value
= (element
.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
2234 if (value
[1]) return parseFloat(value
[1]) / 100;
2238 if (value
== 'auto') {
2239 if ((style
== 'width' || style
== 'height') && (element
.getStyle('display') != 'none'))
2240 return element
['offset' + style
.capitalize()] + 'px';
2246 Element
.Methods
.setOpacity = function(element
, value
) {
2247 function stripAlpha(filter
){
2248 return filter
.replace(/alpha\([^\)]*\)/gi,'');
2250 element
= $(element
);
2251 if (!element
.currentStyle
.hasLayout
) element
.style
.zoom
= 1;
2252 var filter
= element
.getStyle('filter'), style
= element
.style
;
2253 if (value
== 1 || value
=== '') {
2254 (filter
= stripAlpha(filter
)) ?
2255 style
.filter
= filter
: style
.removeAttribute('filter');
2257 } else if (value
< 0.00001) value
= 0;
2258 style
.filter
= stripAlpha(filter
) +
2259 'alpha(opacity=' + (value
* 100) + ')';
2263 Element
._attributeTranslations
= {
2266 'class': 'className',
2270 _getAttr: function(element
, attribute
) {
2271 return element
.getAttribute(attribute
, 2);
2273 _getAttrNode: function(element
, attribute
) {
2274 var node
= element
.getAttributeNode(attribute
);
2275 return node
? node
.value
: "";
2277 _getEv: function(element
, attribute
) {
2278 var attribute
= element
.getAttribute(attribute
);
2279 return attribute
? attribute
.toString().slice(23, -2) : null;
2281 _flag: function(element
, attribute
) {
2282 return $(element
).hasAttribute(attribute
) ? attribute
: null;
2284 style: function(element
) {
2285 return element
.style
.cssText
.toLowerCase();
2287 title: function(element
) {
2288 return element
.title
;
2294 Element
._attributeTranslations
.write
= {
2295 names
: Object
.clone(Element
._attributeTranslations
.read
.names
),
2297 checked: function(element
, value
) {
2298 element
.checked
= !!value
;
2301 style: function(element
, value
) {
2302 element
.style
.cssText
= value
? value
: '';
2307 Element
._attributeTranslations
.has
= {};
2309 $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
2310 'encType maxLength readOnly longDesc').each(function(attr
) {
2311 Element
._attributeTranslations
.write
.names
[attr
.toLowerCase()] = attr
;
2312 Element
._attributeTranslations
.has
[attr
.toLowerCase()] = attr
;
2320 action
: v
._getAttrNode
,
2328 ondblclick
: v
._getEv
,
2329 onmousedown
: v
._getEv
,
2330 onmouseup
: v
._getEv
,
2331 onmouseover
: v
._getEv
,
2332 onmousemove
: v
._getEv
,
2333 onmouseout
: v
._getEv
,
2336 onkeypress
: v
._getEv
,
2337 onkeydown
: v
._getEv
,
2344 })(Element
._attributeTranslations
.read
.values
);
2347 else if (Prototype
.Browser
.Gecko
) {
2348 Element
.Methods
.setOpacity = function(element
, value
) {
2349 element
= $(element
);
2350 element
.style
.opacity
= (value
== 1) ? 0.999999 :
2351 (value
=== '') ? '' : (value
< 0.00001) ? 0 : value
;
2356 else if (Prototype
.Browser
.WebKit
) {
2357 Element
.Methods
.setOpacity = function(element
, value
) {
2358 element
= $(element
);
2359 element
.style
.opacity
= (value
== 1 || value
=== '') ? '' :
2360 (value
< 0.00001) ? 0 : value
;
2363 if(element
.tagName
== 'IMG' && element
.width
) {
2364 element
.width
++; element
.width
--;
2366 var n
= document
.createTextNode(' ');
2367 element
.appendChild(n
);
2368 element
.removeChild(n
);
2374 // Safari returns margins on body which is incorrect if the child is absolutely
2375 // positioned. For performance reasons, redefine Position.cumulativeOffset for
2376 // KHTML/WebKit only.
2377 Element
.Methods
.cumulativeOffset = function(element
) {
2378 var valueT
= 0, valueL
= 0;
2380 valueT
+= element
.offsetTop
|| 0;
2381 valueL
+= element
.offsetLeft
|| 0;
2382 if (element
.offsetParent
== document
.body
)
2383 if (Element
.getStyle(element
, 'position') == 'absolute') break;
2385 element
= element
.offsetParent
;
2388 return Element
._returnOffset(valueL
, valueT
);
2392 if (Prototype
.Browser
.IE
|| Prototype
.Browser
.Opera
) {
2393 // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
2394 Element
.Methods
.update = function(element
, content
) {
2395 element
= $(element
);
2397 if (content
&& content
.toElement
) content
= content
.toElement();
2398 if (Object
.isElement(content
)) return element
.update().insert(content
);
2400 content
= Object
.toHTML(content
);
2401 var tagName
= element
.tagName
.toUpperCase();
2403 if (tagName
in Element
._insertionTranslations
.tags
) {
2404 $A(element
.childNodes
).each(function(node
) { element
.removeChild(node
) });
2405 Element
._getContentFromAnonymousElement(tagName
, content
.stripScripts())
2406 .each(function(node
) { element
.appendChild(node
) });
2408 else element
.innerHTML
= content
.stripScripts();
2410 content
.evalScripts
.bind(content
).defer();
2415 if (document
.createElement('div').outerHTML
) {
2416 Element
.Methods
.replace = function(element
, content
) {
2417 element
= $(element
);
2419 if (content
&& content
.toElement
) content
= content
.toElement();
2420 if (Object
.isElement(content
)) {
2421 element
.parentNode
.replaceChild(content
, element
);
2425 content
= Object
.toHTML(content
);
2426 var parent
= element
.parentNode
, tagName
= parent
.tagName
.toUpperCase();
2428 if (Element
._insertionTranslations
.tags
[tagName
]) {
2429 var nextSibling
= element
.next();
2430 var fragments
= Element
._getContentFromAnonymousElement(tagName
, content
.stripScripts());
2431 parent
.removeChild(element
);
2433 fragments
.each(function(node
) { parent
.insertBefore(node
, nextSibling
) });
2435 fragments
.each(function(node
) { parent
.appendChild(node
) });
2437 else element
.outerHTML
= content
.stripScripts();
2439 content
.evalScripts
.bind(content
).defer();
2444 Element
._returnOffset = function(l
, t
) {
2445 var result
= [l
, t
];
2451 Element
._getContentFromAnonymousElement = function(tagName
, html
) {
2452 var div
= new Element('div'), t
= Element
._insertionTranslations
.tags
[tagName
];
2453 div
.innerHTML
= t
[0] + html
+ t
[1];
2454 t
[2].times(function() { div
= div
.firstChild
});
2455 return $A(div
.childNodes
);
2458 Element
._insertionTranslations
= {
2460 adjacency
: 'beforeBegin',
2461 insert: function(element
, node
) {
2462 element
.parentNode
.insertBefore(node
, element
);
2464 initializeRange: function(element
, range
) {
2465 range
.setStartBefore(element
);
2469 adjacency
: 'afterBegin',
2470 insert: function(element
, node
) {
2471 element
.insertBefore(node
, element
.firstChild
);
2473 initializeRange: function(element
, range
) {
2474 range
.selectNodeContents(element
);
2475 range
.collapse(true);
2479 adjacency
: 'beforeEnd',
2480 insert: function(element
, node
) {
2481 element
.appendChild(node
);
2485 adjacency
: 'afterEnd',
2486 insert: function(element
, node
) {
2487 element
.parentNode
.insertBefore(node
, element
.nextSibling
);
2489 initializeRange: function(element
, range
) {
2490 range
.setStartAfter(element
);
2494 TABLE
: ['<table>', '</table>', 1],
2495 TBODY
: ['<table><tbody>', '</tbody></table>', 2],
2496 TR
: ['<table><tbody><tr>', '</tr></tbody></table>', 3],
2497 TD
: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
2498 SELECT
: ['<select>', '</select>', 1]
2503 this.bottom
.initializeRange
= this.top
.initializeRange
;
2504 Object
.extend(this.tags
, {
2505 THEAD
: this.tags
.TBODY
,
2506 TFOOT
: this.tags
.TBODY
,
2509 }).call(Element
._insertionTranslations
);
2511 Element
.Methods
.Simulated
= {
2512 hasAttribute: function(element
, attribute
) {
2513 attribute
= Element
._attributeTranslations
.has
[attribute
] || attribute
;
2514 var node
= $(element
).getAttributeNode(attribute
);
2515 return node
&& node
.specified
;
2519 Element
.Methods
.ByTag
= { };
2521 Object
.extend(Element
, Element
.Methods
);
2523 if (!Prototype
.BrowserFeatures
.ElementExtensions
&&
2524 document
.createElement('div').__proto__
) {
2525 window
.HTMLElement
= { };
2526 window
.HTMLElement
.prototype = document
.createElement('div').__proto__
;
2527 Prototype
.BrowserFeatures
.ElementExtensions
= true;
2530 Element
.extend
= (function() {
2531 if (Prototype
.BrowserFeatures
.SpecificElementExtensions
)
2534 var Methods
= { }, ByTag
= Element
.Methods
.ByTag
;
2536 var extend
= Object
.extend(function(element
) {
2537 if (!element
|| element
._extendedByPrototype
||
2538 element
.nodeType
!= 1 || element
== window
) return element
;
2540 var methods
= Object
.clone(Methods
),
2541 tagName
= element
.tagName
, property
, value
;
2543 // extend methods for specific tags
2544 if (ByTag
[tagName
]) Object
.extend(methods
, ByTag
[tagName
]);
2546 for (property
in methods
) {
2547 value
= methods
[property
];
2548 if (Object
.isFunction(value
) && !(property
in element
))
2549 element
[property
] = value
.methodize();
2552 element
._extendedByPrototype
= Prototype
.emptyFunction
;
2556 refresh: function() {
2557 // extend methods for all tags (Safari doesn't need this)
2558 if (!Prototype
.BrowserFeatures
.ElementExtensions
) {
2559 Object
.extend(Methods
, Element
.Methods
);
2560 Object
.extend(Methods
, Element
.Methods
.Simulated
);
2569 Element
.hasAttribute = function(element
, attribute
) {
2570 if (element
.hasAttribute
) return element
.hasAttribute(attribute
);
2571 return Element
.Methods
.Simulated
.hasAttribute(element
, attribute
);
2574 Element
.addMethods = function(methods
) {
2575 var F
= Prototype
.BrowserFeatures
, T
= Element
.Methods
.ByTag
;
2578 Object
.extend(Form
, Form
.Methods
);
2579 Object
.extend(Form
.Element
, Form
.Element
.Methods
);
2580 Object
.extend(Element
.Methods
.ByTag
, {
2581 "FORM": Object
.clone(Form
.Methods
),
2582 "INPUT": Object
.clone(Form
.Element
.Methods
),
2583 "SELECT": Object
.clone(Form
.Element
.Methods
),
2584 "TEXTAREA": Object
.clone(Form
.Element
.Methods
)
2588 if (arguments
.length
== 2) {
2589 var tagName
= methods
;
2590 methods
= arguments
[1];
2593 if (!tagName
) Object
.extend(Element
.Methods
, methods
|| { });
2595 if (Object
.isArray(tagName
)) tagName
.each(extend
);
2596 else extend(tagName
);
2599 function extend(tagName
) {
2600 tagName
= tagName
.toUpperCase();
2601 if (!Element
.Methods
.ByTag
[tagName
])
2602 Element
.Methods
.ByTag
[tagName
] = { };
2603 Object
.extend(Element
.Methods
.ByTag
[tagName
], methods
);
2606 function copy(methods
, destination
, onlyIfAbsent
) {
2607 onlyIfAbsent
= onlyIfAbsent
|| false;
2608 for (var property
in methods
) {
2609 var value
= methods
[property
];
2610 if (!Object
.isFunction(value
)) continue;
2611 if (!onlyIfAbsent
|| !(property
in destination
))
2612 destination
[property
] = value
.methodize();
2616 function findDOMClass(tagName
) {
2619 "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
2620 "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
2621 "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
2622 "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
2623 "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
2624 "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
2625 "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
2626 "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
2627 "FrameSet", "IFRAME": "IFrame"
2629 if (trans
[tagName
]) klass
= 'HTML' + trans
[tagName
] + 'Element';
2630 if (window
[klass
]) return window
[klass
];
2631 klass
= 'HTML' + tagName
+ 'Element';
2632 if (window
[klass
]) return window
[klass
];
2633 klass
= 'HTML' + tagName
.capitalize() + 'Element';
2634 if (window
[klass
]) return window
[klass
];
2636 window
[klass
] = { };
2637 window
[klass
].prototype = document
.createElement(tagName
).__proto__
;
2638 return window
[klass
];
2641 if (F
.ElementExtensions
) {
2642 copy(Element
.Methods
, HTMLElement
.prototype);
2643 copy(Element
.Methods
.Simulated
, HTMLElement
.prototype, true);
2646 if (F
.SpecificElementExtensions
) {
2647 for (var tag
in Element
.Methods
.ByTag
) {
2648 var klass
= findDOMClass(tag
);
2649 if (Object
.isUndefined(klass
)) continue;
2650 copy(T
[tag
], klass
.prototype);
2654 Object
.extend(Element
, Element
.Methods
);
2655 delete Element
.ByTag
;
2657 if (Element
.extend
.refresh
) Element
.extend
.refresh();
2658 Element
.cache
= { };
2661 document
.viewport
= {
2662 getDimensions: function() {
2663 var dimensions
= { };
2664 $w('width height').each(function(d
) {
2665 var D
= d
.capitalize();
2666 dimensions
[d
] = self
['inner' + D
] ||
2667 (document
.documentElement
['client' + D
] || document
.body
['client' + D
]);
2672 getWidth: function() {
2673 return this.getDimensions().width
;
2676 getHeight: function() {
2677 return this.getDimensions().height
;
2680 getScrollOffsets: function() {
2681 return Element
._returnOffset(
2682 window
.pageXOffset
|| document
.documentElement
.scrollLeft
|| document
.body
.scrollLeft
,
2683 window
.pageYOffset
|| document
.documentElement
.scrollTop
|| document
.body
.scrollTop
);
2686 /* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
2687 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
2688 * license. Please see http://www.yui-ext.com/ for more information. */
2690 var Selector
= Class
.create({
2691 initialize: function(expression
) {
2692 this.expression
= expression
.strip();
2693 this.compileMatcher();
2696 compileMatcher: function() {
2697 // Selectors with namespaced attributes can't use the XPath version
2698 if (Prototype
.BrowserFeatures
.XPath
&& !(/(\[[\w-]*?:|:checked)/).test(this.expression
))
2699 return this.compileXPathMatcher();
2701 var e
= this.expression
, ps
= Selector
.patterns
, h
= Selector
.handlers
,
2702 c
= Selector
.criteria
, le
, p
, m
;
2704 if (Selector
._cache
[e
]) {
2705 this.matcher
= Selector
._cache
[e
];
2709 this.matcher
= ["this.matcher = function(root) {",
2710 "var r = root, h = Selector.handlers, c = false, n;"];
2712 while (e
&& le
!= e
&& (/\S/).test(e
)) {
2716 if (m
= e
.match(p
)) {
2717 this.matcher
.push(Object
.isFunction(c
[i
]) ? c
[i
](m
) :
2718 new Template(c
[i
]).evaluate(m
));
2719 e
= e
.replace(m
[0], '');
2725 this.matcher
.push("return h.unique(n);\n}");
2726 eval(this.matcher
.join('\n'));
2727 Selector
._cache
[this.expression
] = this.matcher
;
2730 compileXPathMatcher: function() {
2731 var e
= this.expression
, ps
= Selector
.patterns
,
2732 x
= Selector
.xpath
, le
, m
;
2734 if (Selector
._cache
[e
]) {
2735 this.xpath
= Selector
._cache
[e
]; return;
2738 this.matcher
= ['.//*'];
2739 while (e
&& le
!= e
&& (/\S/).test(e
)) {
2742 if (m
= e
.match(ps
[i
])) {
2743 this.matcher
.push(Object
.isFunction(x
[i
]) ? x
[i
](m
) :
2744 new Template(x
[i
]).evaluate(m
));
2745 e
= e
.replace(m
[0], '');
2751 this.xpath
= this.matcher
.join('');
2752 Selector
._cache
[this.expression
] = this.xpath
;
2755 findElements: function(root
) {
2756 root
= root
|| document
;
2757 if (this.xpath
) return document
._getElementsByXPath(this.xpath
, root
);
2758 return this.matcher(root
);
2761 match: function(element
) {
2764 var e
= this.expression
, ps
= Selector
.patterns
, as
= Selector
.assertions
;
2767 while (e
&& le
!== e
&& (/\S/).test(e
)) {
2771 if (m
= e
.match(p
)) {
2772 // use the Selector.assertions methods unless the selector
2775 this.tokens
.push([i
, Object
.clone(m
)]);
2776 e
= e
.replace(m
[0], '');
2778 // reluctantly do a document-wide search
2779 // and look for a match in the array
2780 return this.findElements(document
).include(element
);
2786 var match
= true, name
, matches
;
2787 for (var i
= 0, token
; token
= this.tokens
[i
]; i
++) {
2788 name
= token
[0], matches
= token
[1];
2789 if (!Selector
.assertions
[name
](element
, matches
)) {
2790 match
= false; break;
2797 toString: function() {
2798 return this.expression
;
2801 inspect: function() {
2802 return "#<Selector:" + this.expression
.inspect() + ">";
2806 Object
.extend(Selector
, {
2812 adjacent
: "/following-sibling::*[1]",
2813 laterSibling
: '/following-sibling::*',
2814 tagName: function(m
) {
2815 if (m
[1] == '*') return '';
2816 return "[local-name()='" + m
[1].toLowerCase() +
2817 "' or local-name()='" + m
[1].toUpperCase() + "']";
2819 className
: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
2821 attrPresence
: "[@#{1}]",
2823 m
[3] = m
[5] || m
[6];
2824 return new Template(Selector
.xpath
.operators
[m
[2]]).evaluate(m
);
2826 pseudo: function(m
) {
2827 var h
= Selector
.xpath
.pseudos
[m
[1]];
2829 if (Object
.isFunction(h
)) return h(m
);
2830 return new Template(Selector
.xpath
.pseudos
[m
[1]]).evaluate(m
);
2833 '=': "[@#{1}='#{3}']",
2834 '!=': "[@#{1}!='#{3}']",
2835 '^=': "[starts-with(@#{1}, '#{3}')]",
2836 '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
2837 '*=': "[contains(@#{1}, '#{3}')]",
2838 '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
2839 '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
2842 'first-child': '[not(preceding-sibling::*)]',
2843 'last-child': '[not(following-sibling::*)]',
2844 'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
2845 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
2846 'checked': "[@checked]",
2847 'disabled': "[@disabled]",
2848 'enabled': "[not(@disabled)]",
2849 'not': function(m
) {
2850 var e
= m
[6], p
= Selector
.patterns
,
2851 x
= Selector
.xpath
, le
, m
, v
;
2854 while (e
&& le
!= e
&& (/\S/).test(e
)) {
2857 if (m
= e
.match(p
[i
])) {
2858 v
= Object
.isFunction(x
[i
]) ? x
[i
](m
) : new Template(x
[i
]).evaluate(m
);
2859 exclusion
.push("(" + v
.substring(1, v
.length
- 1) + ")");
2860 e
= e
.replace(m
[0], '');
2865 return "[not(" + exclusion
.join(" and ") + ")]";
2867 'nth-child': function(m
) {
2868 return Selector
.xpath
.pseudos
.nth("(count(./preceding-sibling::*) + 1) ", m
);
2870 'nth-last-child': function(m
) {
2871 return Selector
.xpath
.pseudos
.nth("(count(./following-sibling::*) + 1) ", m
);
2873 'nth-of-type': function(m
) {
2874 return Selector
.xpath
.pseudos
.nth("position() ", m
);
2876 'nth-last-of-type': function(m
) {
2877 return Selector
.xpath
.pseudos
.nth("(last() + 1 - position()) ", m
);
2879 'first-of-type': function(m
) {
2880 m
[6] = "1"; return Selector
.xpath
.pseudos
['nth-of-type'](m
);
2882 'last-of-type': function(m
) {
2883 m
[6] = "1"; return Selector
.xpath
.pseudos
['nth-last-of-type'](m
);
2885 'only-of-type': function(m
) {
2886 var p
= Selector
.xpath
.pseudos
; return p
['first-of-type'](m
) + p
['last-of-type'](m
);
2888 nth: function(fragment
, m
) {
2889 var mm
, formula
= m
[6], predicate
;
2890 if (formula
== 'even') formula
= '2n+0';
2891 if (formula
== 'odd') formula
= '2n+1';
2892 if (mm
= formula
.match(/^(\d+)$/)) // digit only
2893 return '[' + fragment
+ "= " + mm
[1] + ']';
2894 if (mm
= formula
.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
2895 if (mm
[1] == "-") mm
[1] = -1;
2896 var a
= mm
[1] ? Number(mm
[1]) : 1;
2897 var b
= mm
[2] ? Number(mm
[2]) : 0;
2898 predicate
= "[((#{fragment} - #{b}) mod #{a} = 0) and " +
2899 "((#{fragment} - #{b}) div #{a} >= 0)]";
2900 return new Template(predicate
).evaluate({
2901 fragment
: fragment
, a
: a
, b
: b
});
2908 tagName
: 'n = h.tagName(n, r, "#{1}", c); c = false;',
2909 className
: 'n = h.className(n, r, "#{1}", c); c = false;',
2910 id
: 'n = h.id(n, r, "#{1}", c); c = false;',
2911 attrPresence
: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
2913 m
[3] = (m
[5] || m
[6]);
2914 return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m
);
2916 pseudo: function(m
) {
2917 if (m
[6]) m
[6] = m
[6].replace(/"/g, '\\"');
2918 return new Template('n
= h
.pseudo(n
, "#{1}", "#{6}", r
, c
); c
= false;').evaluate(m);
2920 descendant: 'c
= "descendant";',
2921 child: 'c
= "child";',
2922 adjacent: 'c
= "adjacent";',
2923 laterSibling: 'c
= "laterSibling";'
2927 // combinators must be listed first
2928 // (and descendant needs to be last combinator)
2929 laterSibling: /^\s*~\s*/,
2931 adjacent: /^\s*\+\s*/,
2935 tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
2936 id: /^#([\w\-\*]+)(\b|$)/,
2937 className: /^\.([\w\-\*]+)(\b|$)/,
2938 pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/,
2939 attrPresence: /^\[([\w]+)\]/,
2940 attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
2943 // for Selector.match and Element#match
2945 tagName: function(element
, matches
) {
2946 return matches
[1].toUpperCase() == element
.tagName
.toUpperCase();
2949 className: function(element
, matches
) {
2950 return Element
.hasClassName(element
, matches
[1]);
2953 id: function(element
, matches
) {
2954 return element
.id
=== matches
[1];
2957 attrPresence: function(element
, matches
) {
2958 return Element
.hasAttribute(element
, matches
[1]);
2961 attr: function(element
, matches
) {
2962 var nodeValue
= Element
.readAttribute(element
, matches
[1]);
2963 return Selector
.operators
[matches
[2]](nodeValue
, matches
[3]);
2968 // UTILITY FUNCTIONS
2969 // joins two collections
2970 concat: function(a
, b
) {
2971 for (var i
= 0, node
; node
= b
[i
]; i
++)
2976 // marks an array of nodes for counting
2977 mark: function(nodes
) {
2978 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
2979 node
._counted
= true;
2983 unmark: function(nodes
) {
2984 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
2985 node
._counted
= undefined;
2989 // mark each child node with its position (for nth calls)
2990 // "ofType" flag indicates whether we're indexing for nth-of-type
2991 // rather than nth-child
2992 index: function(parentNode
, reverse
, ofType
) {
2993 parentNode
._counted
= true;
2995 for (var nodes
= parentNode
.childNodes
, i
= nodes
.length
- 1, j
= 1; i
>= 0; i
--) {
2996 var node
= nodes
[i
];
2997 if (node
.nodeType
== 1 && (!ofType
|| node
._counted
)) node
.nodeIndex
= j
++;
3000 for (var i
= 0, j
= 1, nodes
= parentNode
.childNodes
; node
= nodes
[i
]; i
++)
3001 if (node
.nodeType
== 1 && (!ofType
|| node
._counted
)) node
.nodeIndex
= j
++;
3005 // filters out duplicates and extends all nodes
3006 unique: function(nodes
) {
3007 if (nodes
.length
== 0) return nodes
;
3008 var results
= [], n
;
3009 for (var i
= 0, l
= nodes
.length
; i
< l
; i
++)
3010 if (!(n
= nodes
[i
])._counted
) {
3012 results
.push(Element
.extend(n
));
3014 return Selector
.handlers
.unmark(results
);
3017 // COMBINATOR FUNCTIONS
3018 descendant: function(nodes
) {
3019 var h
= Selector
.handlers
;
3020 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3021 h
.concat(results
, node
.getElementsByTagName('*'));
3025 child: function(nodes
) {
3026 var h
= Selector
.handlers
;
3027 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++) {
3028 for (var j
= 0, children
= [], child
; child
= node
.childNodes
[j
]; j
++)
3029 if (child
.nodeType
== 1 && child
.tagName
!= '!') results
.push(child
);
3034 adjacent: function(nodes
) {
3035 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++) {
3036 var next
= this.nextElementSibling(node
);
3037 if (next
) results
.push(next
);
3042 laterSibling: function(nodes
) {
3043 var h
= Selector
.handlers
;
3044 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3045 h
.concat(results
, Element
.nextSiblings(node
));
3049 nextElementSibling: function(node
) {
3050 while (node
= node
.nextSibling
)
3051 if (node
.nodeType
== 1) return node
;
3055 previousElementSibling: function(node
) {
3056 while (node
= node
.previousSibling
)
3057 if (node
.nodeType
== 1) return node
;
3062 tagName: function(nodes
, root
, tagName
, combinator
) {
3063 tagName
= tagName
.toUpperCase();
3064 var results
= [], h
= Selector
.handlers
;
3067 // fastlane for ordinary descendant combinators
3068 if (combinator
== "descendant") {
3069 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3070 h
.concat(results
, node
.getElementsByTagName(tagName
));
3072 } else nodes
= this[combinator
](nodes
);
3073 if (tagName
== "*") return nodes
;
3075 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3076 if (node
.tagName
.toUpperCase() == tagName
) results
.push(node
);
3078 } else return root
.getElementsByTagName(tagName
);
3081 id: function(nodes
, root
, id
, combinator
) {
3082 var targetNode
= $(id
), h
= Selector
.handlers
;
3083 if (!targetNode
) return [];
3084 if (!nodes
&& root
== document
) return [targetNode
];
3087 if (combinator
== 'child') {
3088 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3089 if (targetNode
.parentNode
== node
) return [targetNode
];
3090 } else if (combinator
== 'descendant') {
3091 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3092 if (Element
.descendantOf(targetNode
, node
)) return [targetNode
];
3093 } else if (combinator
== 'adjacent') {
3094 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3095 if (Selector
.handlers
.previousElementSibling(targetNode
) == node
)
3096 return [targetNode
];
3097 } else nodes
= h
[combinator
](nodes
);
3099 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3100 if (node
== targetNode
) return [targetNode
];
3103 return (targetNode
&& Element
.descendantOf(targetNode
, root
)) ? [targetNode
] : [];
3106 className: function(nodes
, root
, className
, combinator
) {
3107 if (nodes
&& combinator
) nodes
= this[combinator
](nodes
);
3108 return Selector
.handlers
.byClassName(nodes
, root
, className
);
3111 byClassName: function(nodes
, root
, className
) {
3112 if (!nodes
) nodes
= Selector
.handlers
.descendant([root
]);
3113 var needle
= ' ' + className
+ ' ';
3114 for (var i
= 0, results
= [], node
, nodeClassName
; node
= nodes
[i
]; i
++) {
3115 nodeClassName
= node
.className
;
3116 if (nodeClassName
.length
== 0) continue;
3117 if (nodeClassName
== className
|| (' ' + nodeClassName
+ ' ').include(needle
))
3123 attrPresence: function(nodes
, root
, attr
) {
3125 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3126 if (Element
.hasAttribute(node
, attr
)) results
.push(node
);
3130 attr: function(nodes
, root
, attr
, value
, operator
) {
3131 if (!nodes
) nodes
= root
.getElementsByTagName("*");
3132 var handler
= Selector
.operators
[operator
], results
= [];
3133 for (var i
= 0, node
; node
= nodes
[i
]; i
++) {
3134 var nodeValue
= Element
.readAttribute(node
, attr
);
3135 if (nodeValue
=== null) continue;
3136 if (handler(nodeValue
, value
)) results
.push(node
);
3141 pseudo: function(nodes
, name
, value
, root
, combinator
) {
3142 if (nodes
&& combinator
) nodes
= this[combinator
](nodes
);
3143 if (!nodes
) nodes
= root
.getElementsByTagName("*");
3144 return Selector
.pseudos
[name
](nodes
, value
, root
);
3149 'first-child': function(nodes
, value
, root
) {
3150 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++) {
3151 if (Selector
.handlers
.previousElementSibling(node
)) continue;
3156 'last-child': function(nodes
, value
, root
) {
3157 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++) {
3158 if (Selector
.handlers
.nextElementSibling(node
)) continue;
3163 'only-child': function(nodes
, value
, root
) {
3164 var h
= Selector
.handlers
;
3165 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3166 if (!h
.previousElementSibling(node
) && !h
.nextElementSibling(node
))
3170 'nth-child': function(nodes
, formula
, root
) {
3171 return Selector
.pseudos
.nth(nodes
, formula
, root
);
3173 'nth-last-child': function(nodes
, formula
, root
) {
3174 return Selector
.pseudos
.nth(nodes
, formula
, root
, true);
3176 'nth-of-type': function(nodes
, formula
, root
) {
3177 return Selector
.pseudos
.nth(nodes
, formula
, root
, false, true);
3179 'nth-last-of-type': function(nodes
, formula
, root
) {
3180 return Selector
.pseudos
.nth(nodes
, formula
, root
, true, true);
3182 'first-of-type': function(nodes
, formula
, root
) {
3183 return Selector
.pseudos
.nth(nodes
, "1", root
, false, true);
3185 'last-of-type': function(nodes
, formula
, root
) {
3186 return Selector
.pseudos
.nth(nodes
, "1", root
, true, true);
3188 'only-of-type': function(nodes
, formula
, root
) {
3189 var p
= Selector
.pseudos
;
3190 return p
['last-of-type'](p
['first-of-type'](nodes
, formula
, root
), formula
, root
);
3193 // handles the an+b logic
3194 getIndices: function(a
, b
, total
) {
3195 if (a
== 0) return b
> 0 ? [b
] : [];
3196 return $R(1, total
).inject([], function(memo
, i
) {
3197 if (0 == (i
- b
) % a
&& (i
- b
) / a
>= 0) memo
.push(i
);
3202 // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
3203 nth: function(nodes
, formula
, root
, reverse
, ofType
) {
3204 if (nodes
.length
== 0) return [];
3205 if (formula
== 'even') formula
= '2n+0';
3206 if (formula
== 'odd') formula
= '2n+1';
3207 var h
= Selector
.handlers
, results
= [], indexed
= [], m
;
3209 for (var i
= 0, node
; node
= nodes
[i
]; i
++) {
3210 if (!node
.parentNode
._counted
) {
3211 h
.index(node
.parentNode
, reverse
, ofType
);
3212 indexed
.push(node
.parentNode
);
3215 if (formula
.match(/^\d+$/)) { // just a number
3216 formula
= Number(formula
);
3217 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3218 if (node
.nodeIndex
== formula
) results
.push(node
);
3219 } else if (m
= formula
.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
3220 if (m
[1] == "-") m
[1] = -1;
3221 var a
= m
[1] ? Number(m
[1]) : 1;
3222 var b
= m
[2] ? Number(m
[2]) : 0;
3223 var indices
= Selector
.pseudos
.getIndices(a
, b
, nodes
.length
);
3224 for (var i
= 0, node
, l
= indices
.length
; node
= nodes
[i
]; i
++) {
3225 for (var j
= 0; j
< l
; j
++)
3226 if (node
.nodeIndex
== indices
[j
]) results
.push(node
);
3234 'empty': function(nodes
, value
, root
) {
3235 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++) {
3236 // IE treats comments as element nodes
3237 if (node
.tagName
== '!' || (node
.firstChild
&& !node
.innerHTML
.match(/^\s*$/))) continue;
3243 'not': function(nodes
, selector
, root
) {
3244 var h
= Selector
.handlers
, selectorType
, m
;
3245 var exclusions
= new Selector(selector
).findElements(root
);
3247 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3248 if (!node
._counted
) results
.push(node
);
3249 h
.unmark(exclusions
);
3253 'enabled': function(nodes
, value
, root
) {
3254 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3255 if (!node
.disabled
) results
.push(node
);
3259 'disabled': function(nodes
, value
, root
) {
3260 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3261 if (node
.disabled
) results
.push(node
);
3265 'checked': function(nodes
, value
, root
) {
3266 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3267 if (node
.checked
) results
.push(node
);
3273 '=': function(nv
, v
) { return nv
== v
; },
3274 '!=': function(nv
, v
) { return nv
!= v
; },
3275 '^=': function(nv
, v
) { return nv
.startsWith(v
); },
3276 '$=': function(nv
, v
) { return nv
.endsWith(v
); },
3277 '*=': function(nv
, v
) { return nv
.include(v
); },
3278 '~=': function(nv
, v
) { return (' ' + nv
+ ' ').include(' ' + v
+ ' '); },
3279 '|=': function(nv
, v
) { return ('-' + nv
.toUpperCase() + '-').include('-' + v
.toUpperCase() + '-'); }
3282 matchElements: function(elements
, expression
) {
3283 var matches
= new Selector(expression
).findElements(), h
= Selector
.handlers
;
3285 for (var i
= 0, results
= [], element
; element
= elements
[i
]; i
++)
3286 if (element
._counted
) results
.push(element
);
3291 findElement: function(elements
, expression
, index
) {
3292 if (Object
.isNumber(expression
)) {
3293 index
= expression
; expression
= false;
3295 return Selector
.matchElements(elements
, expression
|| '*')[index
|| 0];
3298 findChildElements: function(element
, expressions
) {
3299 var exprs
= expressions
.join(','), expressions
= [];
3300 exprs
.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m
) {
3301 expressions
.push(m
[1].strip());
3303 var results
= [], h
= Selector
.handlers
;
3304 for (var i
= 0, l
= expressions
.length
, selector
; i
< l
; i
++) {
3305 selector
= new Selector(expressions
[i
].strip());
3306 h
.concat(results
, selector
.findElements(element
));
3308 return (l
> 1) ? h
.unique(results
) : results
;
3313 return Selector
.findChildElements(document
, $A(arguments
));
3316 reset: function(form
) {
3321 serializeElements: function(elements
, options
) {
3322 if (typeof options
!= 'object') options
= { hash
: !!options
};
3323 else if (options
.hash
=== undefined) options
.hash
= true;
3324 var key
, value
, submitted
= false, submit
= options
.submit
;
3326 var data
= elements
.inject({ }, function(result
, element
) {
3327 if (!element
.disabled
&& element
.name
) {
3328 key
= element
.name
; value
= $(element
).getValue();
3329 if (value
!= null && (element
.type
!= 'submit' || (!submitted
&&
3330 submit
!== false && (!submit
|| key
== submit
) && (submitted
= true)))) {
3331 if (key
in result
) {
3332 // a key is already present; construct an array of values
3333 if (!Object
.isArray(result
[key
])) result
[key
] = [result
[key
]];
3334 result
[key
].push(value
);
3336 else result
[key
] = value
;
3342 return options
.hash
? data
: Object
.toQueryString(data
);
3347 serialize: function(form
, options
) {
3348 return Form
.serializeElements(Form
.getElements(form
), options
);
3351 getElements: function(form
) {
3352 return $A($(form
).getElementsByTagName('*')).inject([],
3353 function(elements
, child
) {
3354 if (Form
.Element
.Serializers
[child
.tagName
.toLowerCase()])
3355 elements
.push(Element
.extend(child
));
3361 getInputs: function(form
, typeName
, name
) {
3363 var inputs
= form
.getElementsByTagName('input');
3365 if (!typeName
&& !name
) return $A(inputs
).map(Element
.extend
);
3367 for (var i
= 0, matchingInputs
= [], length
= inputs
.length
; i
< length
; i
++) {
3368 var input
= inputs
[i
];
3369 if ((typeName
&& input
.type
!= typeName
) || (name
&& input
.name
!= name
))
3371 matchingInputs
.push(Element
.extend(input
));
3374 return matchingInputs
;
3377 disable: function(form
) {
3379 Form
.getElements(form
).invoke('disable');
3383 enable: function(form
) {
3385 Form
.getElements(form
).invoke('enable');
3389 findFirstElement: function(form
) {
3390 var elements
= $(form
).getElements().findAll(function(element
) {
3391 return 'hidden' != element
.type
&& !element
.disabled
;
3393 var firstByIndex
= elements
.findAll(function(element
) {
3394 return element
.hasAttribute('tabIndex') && element
.tabIndex
>= 0;
3395 }).sortBy(function(element
) { return element
.tabIndex
}).first();
3397 return firstByIndex
? firstByIndex
: elements
.find(function(element
) {
3398 return ['input', 'select', 'textarea'].include(element
.tagName
.toLowerCase());
3402 focusFirstElement: function(form
) {
3404 form
.findFirstElement().activate();
3408 request: function(form
, options
) {
3409 form
= $(form
), options
= Object
.clone(options
|| { });
3411 var params
= options
.parameters
, action
= form
.readAttribute('action') || '';
3412 if (action
.blank()) action
= window
.location
.href
;
3413 options
.parameters
= form
.serialize(true);
3416 if (Object
.isString(params
)) params
= params
.toQueryParams();
3417 Object
.extend(options
.parameters
, params
);
3420 if (form
.hasAttribute('method') && !options
.method
)
3421 options
.method
= form
.method
;
3423 return new Ajax
.Request(action
, options
);
3427 /*--------------------------------------------------------------------------*/
3430 focus: function(element
) {
3435 select: function(element
) {
3436 $(element
).select();
3441 Form
.Element
.Methods
= {
3442 serialize: function(element
) {
3443 element
= $(element
);
3444 if (!element
.disabled
&& element
.name
) {
3445 var value
= element
.getValue();
3446 if (value
!= undefined) {
3448 pair
[element
.name
] = value
;
3449 return Object
.toQueryString(pair
);
3455 getValue: function(element
) {
3456 element
= $(element
);
3457 var method
= element
.tagName
.toLowerCase();
3458 return Form
.Element
.Serializers
[method
](element
);
3461 setValue: function(element
, value
) {
3462 element
= $(element
);
3463 var method
= element
.tagName
.toLowerCase();
3464 Form
.Element
.Serializers
[method
](element
, value
);
3468 clear: function(element
) {
3469 $(element
).value
= '';
3473 present: function(element
) {
3474 return $(element
).value
!= '';
3477 activate: function(element
) {
3478 element
= $(element
);
3481 if (element
.select
&& (element
.tagName
.toLowerCase() != 'input' ||
3482 !['button', 'reset', 'submit'].include(element
.type
)))
3488 disable: function(element
) {
3489 element
= $(element
);
3491 element
.disabled
= true;
3495 enable: function(element
) {
3496 element
= $(element
);
3497 element
.disabled
= false;
3502 /*--------------------------------------------------------------------------*/
3504 var Field
= Form
.Element
;
3505 var $F
= Form
.Element
.Methods
.getValue
;
3507 /*--------------------------------------------------------------------------*/
3509 Form
.Element
.Serializers
= {
3510 input: function(element
, value
) {
3511 switch (element
.type
.toLowerCase()) {
3514 return Form
.Element
.Serializers
.inputSelector(element
, value
);
3516 return Form
.Element
.Serializers
.textarea(element
, value
);
3520 inputSelector: function(element
, value
) {
3521 if (value
=== undefined) return element
.checked
? element
.value
: null;
3522 else element
.checked
= !!value
;
3525 textarea: function(element
, value
) {
3526 if (value
=== undefined) return element
.value
;
3527 else element
.value
= value
;
3530 select: function(element
, index
) {
3531 if (index
=== undefined)
3532 return this[element
.type
== 'select-one' ?
3533 'selectOne' : 'selectMany'](element
);
3535 var opt
, value
, single
= !Object
.isArray(index
);
3536 for (var i
= 0, length
= element
.length
; i
< length
; i
++) {
3537 opt
= element
.options
[i
];
3538 value
= this.optionValue(opt
);
3540 if (value
== index
) {
3541 opt
.selected
= true;
3545 else opt
.selected
= index
.include(value
);
3550 selectOne: function(element
) {
3551 var index
= element
.selectedIndex
;
3552 return index
>= 0 ? this.optionValue(element
.options
[index
]) : null;
3555 selectMany: function(element
) {
3556 var values
, length
= element
.length
;
3557 if (!length
) return null;
3559 for (var i
= 0, values
= []; i
< length
; i
++) {
3560 var opt
= element
.options
[i
];
3561 if (opt
.selected
) values
.push(this.optionValue(opt
));
3566 optionValue: function(opt
) {
3567 // extend element because hasAttribute may not be native
3568 return Element
.extend(opt
).hasAttribute('value') ? opt
.value
: opt
.text
;
3572 /*--------------------------------------------------------------------------*/
3574 Abstract
.TimedObserver
= Class
.create(PeriodicalExecuter
, {
3575 initialize: function($super, element
, frequency
, callback
) {
3576 $super(callback
, frequency
);
3577 this.element
= $(element
);
3578 this.lastValue
= this.getValue();
3581 execute: function() {
3582 var value
= this.getValue();
3583 if (Object
.isString(this.lastValue
) && Object
.isString(value
) ?
3584 this.lastValue
!= value
: String(this.lastValue
) != String(value
)) {
3585 this.callback(this.element
, value
);
3586 this.lastValue
= value
;
3591 Form
.Element
.Observer
= Class
.create(Abstract
.TimedObserver
, {
3592 getValue: function() {
3593 return Form
.Element
.getValue(this.element
);
3597 Form
.Observer
= Class
.create(Abstract
.TimedObserver
, {
3598 getValue: function() {
3599 return Form
.serialize(this.element
);
3603 /*--------------------------------------------------------------------------*/
3605 Abstract
.EventObserver
= Class
.create({
3606 initialize: function(element
, callback
) {
3607 this.element
= $(element
);
3608 this.callback
= callback
;
3610 this.lastValue
= this.getValue();
3611 if (this.element
.tagName
.toLowerCase() == 'form')
3612 this.registerFormCallbacks();
3614 this.registerCallback(this.element
);
3617 onElementEvent: function() {
3618 var value
= this.getValue();
3619 if (this.lastValue
!= value
) {
3620 this.callback(this.element
, value
);
3621 this.lastValue
= value
;
3625 registerFormCallbacks: function() {
3626 Form
.getElements(this.element
).each(this.registerCallback
, this);
3629 registerCallback: function(element
) {
3631 switch (element
.type
.toLowerCase()) {
3634 Event
.observe(element
, 'click', this.onElementEvent
.bind(this));
3637 Event
.observe(element
, 'change', this.onElementEvent
.bind(this));
3644 Form
.Element
.EventObserver
= Class
.create(Abstract
.EventObserver
, {
3645 getValue: function() {
3646 return Form
.Element
.getValue(this.element
);
3650 Form
.EventObserver
= Class
.create(Abstract
.EventObserver
, {
3651 getValue: function() {
3652 return Form
.serialize(this.element
);
3655 if (!window
.Event
) var Event
= { };
3657 Object
.extend(Event
, {
3675 relatedTarget: function(event
) {
3677 switch(event
.type
) {
3678 case 'mouseover': element
= event
.fromElement
; break;
3679 case 'mouseout': element
= event
.toElement
; break;
3680 default: return null;
3682 return Element
.extend(element
);
3686 Event
.Methods
= (function() {
3687 if (Prototype
.Browser
.IE
) {
3688 function isButton(event
, code
) {
3689 return event
.button
== ({ 0: 1, 1: 4, 2: 2 })[code
];
3692 } else if (Prototype
.Browser
.WebKit
) {
3693 function isButton(event
, code
) {
3695 case 0: return event
.which
== 1 && !event
.metaKey
;
3696 case 1: return event
.which
== 1 && event
.metaKey
;
3697 default: return false;
3702 function isButton(event
, code
) {
3703 return event
.which
? (event
.which
=== code
+ 1) : (event
.button
=== code
);
3708 isLeftClick: function(event
) { return isButton(event
, 0) },
3709 isMiddleClick: function(event
) { return isButton(event
, 1) },
3710 isRightClick: function(event
) { return isButton(event
, 2) },
3712 element: function(event
) {
3713 var node
= Event
.extend(event
).target
;
3714 return Element
.extend(node
.nodeType
== Node
.TEXT_NODE
? node
.parentNode
: node
);
3717 findElement: function(event
, expression
) {
3718 var element
= Event
.element(event
);
3719 return element
.match(expression
) ? element
: element
.up(expression
);
3722 pointer: function(event
) {
3724 x
: event
.pageX
|| (event
.clientX
+
3725 (document
.documentElement
.scrollLeft
|| document
.body
.scrollLeft
)),
3726 y
: event
.pageY
|| (event
.clientY
+
3727 (document
.documentElement
.scrollTop
|| document
.body
.scrollTop
))
3731 pointerX: function(event
) { return Event
.pointer(event
).x
},
3732 pointerY: function(event
) { return Event
.pointer(event
).y
},
3734 stop: function(event
) {
3735 Event
.extend(event
);
3736 event
.preventDefault();
3737 event
.stopPropagation();
3742 Event
.extend
= (function() {
3743 var methods
= Object
.keys(Event
.Methods
).inject({ }, function(m
, name
) {
3744 m
[name
] = Event
.Methods
[name
].methodize();
3748 if (Prototype
.Browser
.IE
) {
3749 Object
.extend(methods
, {
3750 stopPropagation: function() { this.cancelBubble
= true },
3751 preventDefault: function() { this.returnValue
= false },
3752 inspect: function() { return "[object Event]" }
3755 return function(event
) {
3756 if (!event
) return false;
3757 if (event
._extendedByPrototype
) return event
;
3759 event
._extendedByPrototype
= Prototype
.emptyFunction
;
3760 var pointer
= Event
.pointer(event
);
3761 Object
.extend(event
, {
3762 target
: event
.srcElement
,
3763 relatedTarget
: Event
.relatedTarget(event
),
3767 return Object
.extend(event
, methods
);
3771 Event
.prototype = Event
.prototype || document
.createEvent("HTMLEvents").__proto__
;
3772 Object
.extend(Event
.prototype, methods
);
3777 Object
.extend(Event
, (function() {
3778 var cache
= Event
.cache
;
3780 function getEventID(element
) {
3781 if (element
._eventID
) return element
._eventID
;
3782 arguments
.callee
.id
= arguments
.callee
.id
|| 1;
3783 return element
._eventID
= ++arguments
.callee
.id
;
3786 function getDOMEventName(eventName
) {
3787 if (eventName
&& eventName
.match(/:/)) return "dataavailable";
3791 function getCacheForID(id
) {
3792 return cache
[id
] = cache
[id
] || { };
3795 function getWrappersForEventName(id
, eventName
) {
3796 var c
= getCacheForID(id
);
3797 return c
[eventName
] = c
[eventName
] || [];
3800 function createWrapper(element
, eventName
, handler
) {
3801 var id
= getEventID(element
);
3802 var c
= getWrappersForEventName(id
, eventName
);
3803 if (c
.pluck("handler").include(handler
)) return false;
3805 var wrapper = function(event
) {
3806 if (event
.eventName
&& event
.eventName
!= eventName
)
3809 Event
.extend(event
);
3810 handler
.call(element
, event
)
3813 wrapper
.handler
= handler
;
3818 function findWrapper(id
, eventName
, handler
) {
3819 var c
= getWrappersForEventName(id
, eventName
);
3820 return c
.find(function(wrapper
) { return wrapper
.handler
== handler
});
3823 function destroyWrapper(id
, eventName
, handler
) {
3824 var c
= getCacheForID(id
);
3825 if (!c
[eventName
]) return false;
3826 c
[eventName
] = c
[eventName
].without(findWrapper(id
, eventName
, handler
));
3829 function destroyCache() {
3830 for (var id
in cache
)
3831 for (var eventName
in cache
[id
])
3832 cache
[id
][eventName
] = null;
3835 if (window
.attachEvent
) {
3836 window
.attachEvent("onunload", destroyCache
);
3840 observe: function(element
, eventName
, handler
) {
3841 element
= $(element
);
3842 var name
= getDOMEventName(eventName
);
3844 var wrapper
= createWrapper(element
, eventName
, handler
);
3845 if (!wrapper
) return element
;
3847 if (element
.addEventListener
) {
3848 element
.addEventListener(name
, wrapper
, false);
3850 element
.attachEvent("on" + name
, wrapper
);
3856 stopObserving: function(element
, eventName
, handler
) {
3857 element
= $(element
);
3858 var id
= getEventID(element
), name
= getDOMEventName(eventName
);
3860 if (!handler
&& eventName
) {
3861 getWrappersForEventName(id
, eventName
).each(function(wrapper
) {
3862 element
.stopObserving(eventName
, wrapper
.handler
);
3866 } else if (!eventName
) {
3867 Object
.keys(getCacheForID(id
)).each(function(eventName
) {
3868 element
.stopObserving(eventName
);
3873 var wrapper
= findWrapper(id
, eventName
, handler
);
3874 if (!wrapper
) return element
;
3876 if (element
.removeEventListener
) {
3877 element
.removeEventListener(name
, wrapper
, false);
3879 element
.detachEvent("on" + name
, wrapper
);
3882 destroyWrapper(id
, eventName
, handler
);
3887 fire: function(element
, eventName
, memo
) {
3888 element
= $(element
);
3889 if (element
== document
&& document
.createEvent
&& !element
.dispatchEvent
)
3890 element
= document
.documentElement
;
3892 if (document
.createEvent
) {
3893 var event
= document
.createEvent("HTMLEvents");
3894 event
.initEvent("dataavailable", true, true);
3896 var event
= document
.createEventObject();
3897 event
.eventType
= "ondataavailable";
3900 event
.eventName
= eventName
;
3901 event
.memo
= memo
|| { };
3903 if (document
.createEvent
) {
3904 element
.dispatchEvent(event
);
3906 element
.fireEvent(event
.eventType
, event
);
3914 Object
.extend(Event
, Event
.Methods
);
3916 Element
.addMethods({
3918 observe
: Event
.observe
,
3919 stopObserving
: Event
.stopObserving
3922 Object
.extend(document
, {
3923 fire
: Element
.Methods
.fire
.methodize(),
3924 observe
: Element
.Methods
.observe
.methodize(),
3925 stopObserving
: Element
.Methods
.stopObserving
.methodize()
3929 /* Support for the DOMContentLoaded event is based on work by Dan Webb,
3930 Matthias Miller, Dean Edwards and John Resig. */
3932 var timer
, fired
= false;
3934 function fireContentLoadedEvent() {
3936 if (timer
) window
.clearInterval(timer
);
3937 document
.fire("dom:loaded");
3941 if (document
.addEventListener
) {
3942 if (Prototype
.Browser
.WebKit
) {
3943 timer
= window
.setInterval(function() {
3944 if (/loaded|complete/.test(document
.readyState
))
3945 fireContentLoadedEvent();
3948 Event
.observe(window
, "load", fireContentLoadedEvent
);
3951 document
.addEventListener("DOMContentLoaded",
3952 fireContentLoadedEvent
, false);
3956 document
.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
3957 $("__onDOMContentLoaded").onreadystatechange = function() {
3958 if (this.readyState
== "complete") {
3959 this.onreadystatechange
= null;
3960 fireContentLoadedEvent();
3965 /*------------------------------- DEPRECATED -------------------------------*/
3967 Hash
.toQueryString
= Object
.toQueryString
;
3969 var Toggle
= { display
: Element
.toggle
};
3971 Element
.Methods
.childOf
= Element
.Methods
.descendantOf
;
3974 Before: function(element
, content
) {
3975 return Element
.insert(element
, {before
:content
});
3978 Top: function(element
, content
) {
3979 return Element
.insert(element
, {top
:content
});
3982 Bottom: function(element
, content
) {
3983 return Element
.insert(element
, {bottom
:content
});
3986 After: function(element
, content
) {
3987 return Element
.insert(element
, {after
:content
});
3991 var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
3993 // This should be moved to script.aculo.us; notice the deprecated methods
3994 // further below, that map to the newer Element methods.
3996 // set to true if needed, warning: firefox performance problems
3997 // NOT neeeded for page scrolling, only if draggable contained in
3998 // scrollable elements
3999 includeScrollOffsets
: false,
4001 // must be called before calling withinIncludingScrolloffset, every time the
4003 prepare: function() {
4004 this.deltaX
= window
.pageXOffset
4005 || document
.documentElement
.scrollLeft
4006 || document
.body
.scrollLeft
4008 this.deltaY
= window
.pageYOffset
4009 || document
.documentElement
.scrollTop
4010 || document
.body
.scrollTop
4014 // caches x/y coordinate pair to use with overlap
4015 within: function(element
, x
, y
) {
4016 if (this.includeScrollOffsets
)
4017 return this.withinIncludingScrolloffsets(element
, x
, y
);
4020 this.offset
= Element
.cumulativeOffset(element
);
4022 return (y
>= this.offset
[1] &&
4023 y
< this.offset
[1] + element
.offsetHeight
&&
4024 x
>= this.offset
[0] &&
4025 x
< this.offset
[0] + element
.offsetWidth
);
4028 withinIncludingScrolloffsets: function(element
, x
, y
) {
4029 var offsetcache
= Element
.cumulativeScrollOffset(element
);
4031 this.xcomp
= x
+ offsetcache
[0] - this.deltaX
;
4032 this.ycomp
= y
+ offsetcache
[1] - this.deltaY
;
4033 this.offset
= Element
.cumulativeOffset(element
);
4035 return (this.ycomp
>= this.offset
[1] &&
4036 this.ycomp
< this.offset
[1] + element
.offsetHeight
&&
4037 this.xcomp
>= this.offset
[0] &&
4038 this.xcomp
< this.offset
[0] + element
.offsetWidth
);
4041 // within must be called directly before
4042 overlap: function(mode
, element
) {
4043 if (!mode
) return 0;
4044 if (mode
== 'vertical')
4045 return ((this.offset
[1] + element
.offsetHeight
) - this.ycomp
) /
4046 element
.offsetHeight
;
4047 if (mode
== 'horizontal')
4048 return ((this.offset
[0] + element
.offsetWidth
) - this.xcomp
) /
4049 element
.offsetWidth
;
4052 // Deprecation layer -- use newer Element methods now (1.5.2).
4054 cumulativeOffset
: Element
.Methods
.cumulativeOffset
,
4056 positionedOffset
: Element
.Methods
.positionedOffset
,
4058 absolutize: function(element
) {
4060 return Element
.absolutize(element
);
4063 relativize: function(element
) {
4065 return Element
.relativize(element
);
4068 realOffset
: Element
.Methods
.cumulativeScrollOffset
,
4070 offsetParent
: Element
.Methods
.getOffsetParent
,
4072 page
: Element
.Methods
.viewportOffset
,
4074 clone: function(source
, target
, options
) {
4075 options
= options
|| { };
4076 return Element
.clonePosition(target
, source
, options
);
4080 /*--------------------------------------------------------------------------*/
4082 if (!document
.getElementsByClassName
) document
.getElementsByClassName = function(instanceMethods
){
4083 function iter(name
) {
4084 return name
.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name
+ " ')]";
4087 instanceMethods
.getElementsByClassName
= Prototype
.BrowserFeatures
.XPath
?
4088 function(element
, className
) {
4089 className
= className
.toString().strip();
4090 var cond
= /\s/.test(className
) ? $w(className
).map(iter
).join('') : iter(className
);
4091 return cond
? document
._getElementsByXPath('.//*' + cond
, element
) : [];
4092 } : function(element
, className
) {
4093 className
= className
.toString().strip();
4094 var elements
= [], classNames
= (/\s/.test(className
) ? $w(className
) : null);
4095 if (!classNames
&& !className
) return elements
;
4097 var nodes
= $(element
).getElementsByTagName('*');
4098 className
= ' ' + className
+ ' ';
4100 for (var i
= 0, child
, cn
; child
= nodes
[i
]; i
++) {
4101 if (child
.className
&& (cn
= ' ' + child
.className
+ ' ') && (cn
.include(className
) ||
4102 (classNames
&& classNames
.all(function(name
) {
4103 return !name
.toString().blank() && cn
.include(' ' + name
+ ' ');
4105 elements
.push(Element
.extend(child
));
4110 return function(className
, parentElement
) {
4111 return $(parentElement
|| document
.body
).getElementsByClassName(className
);
4115 /*--------------------------------------------------------------------------*/
4117 Element
.ClassNames
= Class
.create();
4118 Element
.ClassNames
.prototype = {
4119 initialize: function(element
) {
4120 this.element
= $(element
);
4123 _each: function(iterator
) {
4124 this.element
.className
.split(/\s+/).select(function(name
) {
4125 return name
.length
> 0;
4129 set: function(className
) {
4130 this.element
.className
= className
;
4133 add: function(classNameToAdd
) {
4134 if (this.include(classNameToAdd
)) return;
4135 this.set($A(this).concat(classNameToAdd
).join(' '));
4138 remove: function(classNameToRemove
) {
4139 if (!this.include(classNameToRemove
)) return;
4140 this.set($A(this).without(classNameToRemove
).join(' '));
4143 toString: function() {
4144 return $A(this).join(' ');
4148 Object
.extend(Element
.ClassNames
.prototype, Enumerable
);
4150 /*--------------------------------------------------------------------------*/
4152 Element
.addMethods();