1 /* Prototype JavaScript framework, version 1.6.0.3
2 * (c) 2005-2008 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
&&
14 navigator
.userAgent
.indexOf('Opera') === -1),
15 Opera
: navigator
.userAgent
.indexOf('Opera') > -1,
16 WebKit
: navigator
.userAgent
.indexOf('AppleWebKit/') > -1,
17 Gecko
: navigator
.userAgent
.indexOf('Gecko') > -1 &&
18 navigator
.userAgent
.indexOf('KHTML') === -1,
19 MobileSafari
: !!navigator
.userAgent
.match(/Apple.*Mobile.*Safari/)
23 XPath
: !!document
.evaluate
,
24 SelectorsAPI
: !!document
.querySelector
,
25 ElementExtensions
: !!window
.HTMLElement
,
26 SpecificElementExtensions
:
27 document
.createElement('div')['__proto__'] &&
28 document
.createElement('div')['__proto__'] !==
29 document
.createElement('form')['__proto__']
32 ScriptFragment
: '<script[^>]*>([\\S\\s]*?)<\/script>',
33 JSONFilter
: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
35 emptyFunction: function() { },
36 K: function(x
) { return x
}
39 if (Prototype
.Browser
.MobileSafari
)
40 Prototype
.BrowserFeatures
.SpecificElementExtensions
= false;
43 /* Based on Alex Arnell's inheritance implementation. */
46 var parent
= null, properties
= $A(arguments
);
47 if (Object
.isFunction(properties
[0]))
48 parent
= properties
.shift();
51 this.initialize
.apply(this, arguments
);
54 Object
.extend(klass
, Class
.Methods
);
55 klass
.superclass
= parent
;
56 klass
.subclasses
= [];
59 var subclass = function() { };
60 subclass
.prototype = parent
.prototype;
61 klass
.prototype = new subclass
;
62 parent
.subclasses
.push(klass
);
65 for (var i
= 0; i
< properties
.length
; i
++)
66 klass
.addMethods(properties
[i
]);
68 if (!klass
.prototype.initialize
)
69 klass
.prototype.initialize
= Prototype
.emptyFunction
;
71 klass
.prototype.constructor = klass
;
78 addMethods: function(source
) {
79 var ancestor
= this.superclass
&& this.superclass
.prototype;
80 var properties
= Object
.keys(source
);
82 if (!Object
.keys({ toString
: true }).length
)
83 properties
.push("toString", "valueOf");
85 for (var i
= 0, length
= properties
.length
; i
< length
; i
++) {
86 var property
= properties
[i
], value
= source
[property
];
87 if (ancestor
&& Object
.isFunction(value
) &&
88 value
.argumentNames().first() == "$super") {
90 value
= (function(m
) {
91 return function() { return ancestor
[m
].apply(this, arguments
) };
92 })(property
).wrap(method
);
94 value
.valueOf
= method
.valueOf
.bind(method
);
95 value
.toString
= method
.toString
.bind(method
);
97 this.prototype[property
] = value
;
106 Object
.extend = function(destination
, source
) {
107 for (var property
in source
)
108 destination
[property
] = source
[property
];
112 Object
.extend(Object
, {
113 inspect: function(object
) {
115 if (Object
.isUndefined(object
)) return 'undefined';
116 if (object
=== null) return 'null';
117 return object
.inspect
? object
.inspect() : String(object
);
119 if (e
instanceof RangeError
) return '...';
124 toJSON: function(object
) {
125 var type
= typeof object
;
129 case 'unknown': return;
130 case 'boolean': return object
.toString();
133 if (object
=== null) return 'null';
134 if (object
.toJSON
) return object
.toJSON();
135 if (Object
.isElement(object
)) return;
138 for (var property
in object
) {
139 var value
= Object
.toJSON(object
[property
]);
140 if (!Object
.isUndefined(value
))
141 results
.push(property
.toJSON() + ': ' + value
);
144 return '{' + results
.join(', ') + '}';
147 toQueryString: function(object
) {
148 return $H(object
).toQueryString();
151 toHTML: function(object
) {
152 return object
&& object
.toHTML
? object
.toHTML() : String
.interpret(object
);
155 keys: function(object
) {
157 for (var property
in object
)
162 values: function(object
) {
164 for (var property
in object
)
165 values
.push(object
[property
]);
169 clone: function(object
) {
170 return Object
.extend({ }, object
);
173 isElement: function(object
) {
174 return !!(object
&& object
.nodeType
== 1);
177 isArray: function(object
) {
178 return object
!= null && typeof object
== "object" &&
179 'splice' in object
&& 'join' in object
;
182 isHash: function(object
) {
183 return object
instanceof Hash
;
186 isFunction: function(object
) {
187 return typeof object
== "function";
190 isString: function(object
) {
191 return typeof object
== "string";
194 isNumber: function(object
) {
195 return typeof object
== "number";
198 isUndefined: function(object
) {
199 return typeof object
== "undefined";
203 Object
.extend(Function
.prototype, {
204 argumentNames: function() {
205 var names
= this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1]
206 .replace(/\s+/g, '').split(',');
207 return names
.length
== 1 && !names
[0] ? [] : names
;
211 if (arguments
.length
< 2 && Object
.isUndefined(arguments
[0])) return this;
212 var __method
= this, args
= $A(arguments
), object
= args
.shift();
214 return __method
.apply(object
, args
.concat($A(arguments
)));
218 bindAsEventListener: function() {
219 var __method
= this, args
= $A(arguments
), object
= args
.shift();
220 return function(event
) {
221 return __method
.apply(object
, [event
|| window
.event
].concat(args
));
226 if (!arguments
.length
) return this;
227 var __method
= this, args
= $A(arguments
);
229 return __method
.apply(this, args
.concat($A(arguments
)));
234 var __method
= this, args
= $A(arguments
), timeout
= args
.shift() * 1000;
235 return window
.setTimeout(function() {
236 return __method
.apply(__method
, args
);
241 var args
= [0.01].concat($A(arguments
));
242 return this.delay
.apply(this, args
);
245 wrap: function(wrapper
) {
248 return wrapper
.apply(this, [__method
.bind(this)].concat($A(arguments
)));
252 methodize: function() {
253 if (this._methodized
) return this._methodized
;
255 return this._methodized = function() {
256 return __method
.apply(null, [this].concat($A(arguments
)));
261 Date
.prototype.toJSON = function() {
262 return '"' + this.getUTCFullYear() + '-' +
263 (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
264 this.getUTCDate().toPaddedString(2) + 'T' +
265 this.getUTCHours().toPaddedString(2) + ':' +
266 this.getUTCMinutes().toPaddedString(2) + ':' +
267 this.getUTCSeconds().toPaddedString(2) + 'Z"';
274 for (var i
= 0, length
= arguments
.length
; i
< length
; i
++) {
275 var lambda
= arguments
[i
];
277 returnValue
= lambda();
286 RegExp
.prototype.match
= RegExp
.prototype.test
;
288 RegExp
.escape = function(str
) {
289 return String(str
).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
292 /*--------------------------------------------------------------------------*/
294 var PeriodicalExecuter
= Class
.create({
295 initialize: function(callback
, frequency
) {
296 this.callback
= callback
;
297 this.frequency
= frequency
;
298 this.currentlyExecuting
= false;
300 this.registerCallback();
303 registerCallback: function() {
304 this.timer
= setInterval(this.onTimerEvent
.bind(this), this.frequency
* 1000);
307 execute: function() {
312 if (!this.timer
) return;
313 clearInterval(this.timer
);
317 onTimerEvent: function() {
318 if (!this.currentlyExecuting
) {
320 this.currentlyExecuting
= true;
323 this.currentlyExecuting
= false;
328 Object
.extend(String
, {
329 interpret: function(value
) {
330 return value
== null ? '' : String(value
);
342 Object
.extend(String
.prototype, {
343 gsub: function(pattern
, replacement
) {
344 var result
= '', source
= this, match
;
345 replacement
= arguments
.callee
.prepareReplacement(replacement
);
347 while (source
.length
> 0) {
348 if (match
= source
.match(pattern
)) {
349 result
+= source
.slice(0, match
.index
);
350 result
+= String
.interpret(replacement(match
));
351 source
= source
.slice(match
.index
+ match
[0].length
);
353 result
+= source
, source
= '';
359 sub: function(pattern
, replacement
, count
) {
360 replacement
= this.gsub
.prepareReplacement(replacement
);
361 count
= Object
.isUndefined(count
) ? 1 : count
;
363 return this.gsub(pattern
, function(match
) {
364 if (--count
< 0) return match
[0];
365 return replacement(match
);
369 scan: function(pattern
, iterator
) {
370 this.gsub(pattern
, iterator
);
374 truncate: function(length
, truncation
) {
375 length
= length
|| 30;
376 truncation
= Object
.isUndefined(truncation
) ? '...' : truncation
;
377 return this.length
> length
?
378 this.slice(0, length
- truncation
.length
) + truncation
: String(this);
382 return this.replace(/^\s+/, '').replace(/\s+$/, '');
385 stripTags: function() {
386 return this.replace(/<\/?[^>]+>/gi, '');
389 stripScripts: function() {
390 return this.replace(new RegExp(Prototype
.ScriptFragment
, 'img'), '');
393 extractScripts: function() {
394 var matchAll
= new RegExp(Prototype
.ScriptFragment
, 'img');
395 var matchOne
= new RegExp(Prototype
.ScriptFragment
, 'im');
396 return (this.match(matchAll
) || []).map(function(scriptTag
) {
397 return (scriptTag
.match(matchOne
) || ['', ''])[1];
401 evalScripts: function() {
402 return this.extractScripts().map(function(script
) { return eval(script
) });
405 escapeHTML: function() {
406 var self
= arguments
.callee
;
407 self
.text
.data
= this;
408 return self
.div
.innerHTML
;
411 unescapeHTML: function() {
412 var div
= new Element('div');
413 div
.innerHTML
= this.stripTags();
414 return div
.childNodes
[0] ? (div
.childNodes
.length
> 1 ?
415 $A(div
.childNodes
).inject('', function(memo
, node
) { return memo
+node
.nodeValue
}) :
416 div
.childNodes
[0].nodeValue
) : '';
419 toQueryParams: function(separator
) {
420 var match
= this.strip().match(/([^?#]*)(#.*)?$/);
421 if (!match
) return { };
423 return match
[1].split(separator
|| '&').inject({ }, function(hash
, pair
) {
424 if ((pair
= pair
.split('='))[0]) {
425 var key
= decodeURIComponent(pair
.shift());
426 var value
= pair
.length
> 1 ? pair
.join('=') : pair
[0];
427 if (value
!= undefined) value
= decodeURIComponent(value
);
430 if (!Object
.isArray(hash
[key
])) hash
[key
] = [hash
[key
]];
431 hash
[key
].push(value
);
433 else hash
[key
] = value
;
439 toArray: function() {
440 return this.split('');
444 return this.slice(0, this.length
- 1) +
445 String
.fromCharCode(this.charCodeAt(this.length
- 1) + 1);
448 times: function(count
) {
449 return count
< 1 ? '' : new Array(count
+ 1).join(this);
452 camelize: function() {
453 var parts
= this.split('-'), len
= parts
.length
;
454 if (len
== 1) return parts
[0];
456 var camelized
= this.charAt(0) == '-'
457 ? parts
[0].charAt(0).toUpperCase() + parts
[0].substring(1)
460 for (var i
= 1; i
< len
; i
++)
461 camelized
+= parts
[i
].charAt(0).toUpperCase() + parts
[i
].substring(1);
466 capitalize: function() {
467 return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
470 underscore: function() {
471 return this.gsub(/::/, '/').gsub(/([A
-Z
]+)([A
-Z
][a
-z
])/,'#{1}_#{2}').gsub(/([a
-z
\d
])([A
-Z
])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
474 dasherize: function() {
475 return this.gsub(/_
/,'-');
478 inspect: function(useDoubleQuotes
) {
479 var escapedString
= this.gsub(/[\x00-\x1f\\]/, function(match
) {
480 var character
= String
.specialChar
[match
[0]];
481 return character
? character
: '\\u00' + match
[0].charCodeAt().toPaddedString(2, 16);
483 if (useDoubleQuotes
) return '"' + escapedString
.replace(/"/g, '\\"') + '"';
484 return "'" + escapedString.replace(/'/g
, '\\\'') + "'";
488 return this.inspect(true);
491 unfilterJSON: function(filter
) {
492 return this.sub(filter
|| Prototype
.JSONFilter
, '#{1}');
497 if (str
.blank()) return false;
498 str
= this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
499 return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str
);
502 evalJSON: function(sanitize
) {
503 var json
= this.unfilterJSON();
505 if (!sanitize
|| json
.isJSON()) return eval('(' + json
+ ')');
507 throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
510 include: function(pattern
) {
511 return this.indexOf(pattern
) > -1;
514 startsWith: function(pattern
) {
515 return this.indexOf(pattern
) === 0;
518 endsWith: function(pattern
) {
519 var d
= this.length
- pattern
.length
;
520 return d
>= 0 && this.lastIndexOf(pattern
) === d
;
528 return /^\s*$/.test(this);
531 interpolate: function(object
, pattern
) {
532 return new Template(this, pattern
).evaluate(object
);
536 if (Prototype
.Browser
.WebKit
|| Prototype
.Browser
.IE
) Object
.extend(String
.prototype, {
537 escapeHTML: function() {
538 return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g
,'>');
540 unescapeHTML: function() {
541 return this.stripTags().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
545 String
.prototype.gsub
.prepareReplacement = function(replacement
) {
546 if (Object
.isFunction(replacement
)) return replacement
;
547 var template
= new Template(replacement
);
548 return function(match
) { return template
.evaluate(match
) };
551 String
.prototype.parseQuery
= String
.prototype.toQueryParams
;
553 Object
.extend(String
.prototype.escapeHTML
, {
554 div
: document
.createElement('div'),
555 text
: document
.createTextNode('')
558 String
.prototype.escapeHTML
.div
.appendChild(String
.prototype.escapeHTML
.text
);
560 var Template
= Class
.create({
561 initialize: function(template
, pattern
) {
562 this.template
= template
.toString();
563 this.pattern
= pattern
|| Template
.Pattern
;
566 evaluate: function(object
) {
567 if (Object
.isFunction(object
.toTemplateReplacements
))
568 object
= object
.toTemplateReplacements();
570 return this.template
.gsub(this.pattern
, function(match
) {
571 if (object
== null) return '';
573 var before
= match
[1] || '';
574 if (before
== '\\') return match
[2];
576 var ctx
= object
, expr
= match
[3];
577 var pattern
= /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
578 match
= pattern
.exec(expr
);
579 if (match
== null) return before
;
581 while (match
!= null) {
582 var comp
= match
[1].startsWith('[') ? match
[2].gsub('\\\\]', ']') : match
[1];
584 if (null == ctx
|| '' == match
[3]) break;
585 expr
= expr
.substring('[' == match
[3] ? match
[1].length
: match
[0].length
);
586 match
= pattern
.exec(expr
);
589 return before
+ String
.interpret(ctx
);
593 Template
.Pattern
= /(^|.|\r|\n)(#\{(.*?)\})/;
598 each: function(iterator
, context
) {
601 this._each(function(value
) {
602 iterator
.call(context
, value
, index
++);
605 if (e
!= $break) throw e
;
610 eachSlice: function(number
, iterator
, context
) {
611 var index
= -number
, slices
= [], array
= this.toArray();
612 if (number
< 1) return array
;
613 while ((index
+= number
) < array
.length
)
614 slices
.push(array
.slice(index
, index
+number
));
615 return slices
.collect(iterator
, context
);
618 all: function(iterator
, context
) {
619 iterator
= iterator
|| Prototype
.K
;
621 this.each(function(value
, index
) {
622 result
= result
&& !!iterator
.call(context
, value
, index
);
623 if (!result
) throw $break;
628 any: function(iterator
, context
) {
629 iterator
= iterator
|| Prototype
.K
;
631 this.each(function(value
, index
) {
632 if (result
= !!iterator
.call(context
, value
, index
))
638 collect: function(iterator
, context
) {
639 iterator
= iterator
|| Prototype
.K
;
641 this.each(function(value
, index
) {
642 results
.push(iterator
.call(context
, value
, index
));
647 detect: function(iterator
, context
) {
649 this.each(function(value
, index
) {
650 if (iterator
.call(context
, value
, index
)) {
658 findAll: function(iterator
, context
) {
660 this.each(function(value
, index
) {
661 if (iterator
.call(context
, value
, index
))
667 grep: function(filter
, iterator
, context
) {
668 iterator
= iterator
|| Prototype
.K
;
671 if (Object
.isString(filter
))
672 filter
= new RegExp(filter
);
674 this.each(function(value
, index
) {
675 if (filter
.match(value
))
676 results
.push(iterator
.call(context
, value
, index
));
681 include: function(object
) {
682 if (Object
.isFunction(this.indexOf
))
683 if (this.indexOf(object
) != -1) return true;
686 this.each(function(value
) {
687 if (value
== object
) {
695 inGroupsOf: function(number
, fillWith
) {
696 fillWith
= Object
.isUndefined(fillWith
) ? null : fillWith
;
697 return this.eachSlice(number
, function(slice
) {
698 while(slice
.length
< number
) slice
.push(fillWith
);
703 inject: function(memo
, iterator
, context
) {
704 this.each(function(value
, index
) {
705 memo
= iterator
.call(context
, memo
, value
, index
);
710 invoke: function(method
) {
711 var args
= $A(arguments
).slice(1);
712 return this.map(function(value
) {
713 return value
[method
].apply(value
, args
);
717 max: function(iterator
, context
) {
718 iterator
= iterator
|| Prototype
.K
;
720 this.each(function(value
, index
) {
721 value
= iterator
.call(context
, value
, index
);
722 if (result
== null || value
>= result
)
728 min: function(iterator
, context
) {
729 iterator
= iterator
|| Prototype
.K
;
731 this.each(function(value
, index
) {
732 value
= iterator
.call(context
, value
, index
);
733 if (result
== null || value
< result
)
739 partition: function(iterator
, context
) {
740 iterator
= iterator
|| Prototype
.K
;
741 var trues
= [], falses
= [];
742 this.each(function(value
, index
) {
743 (iterator
.call(context
, value
, index
) ?
744 trues
: falses
).push(value
);
746 return [trues
, falses
];
749 pluck: function(property
) {
751 this.each(function(value
) {
752 results
.push(value
[property
]);
757 reject: function(iterator
, context
) {
759 this.each(function(value
, index
) {
760 if (!iterator
.call(context
, value
, index
))
766 sortBy: function(iterator
, context
) {
767 return this.map(function(value
, index
) {
770 criteria
: iterator
.call(context
, value
, index
)
772 }).sort(function(left
, right
) {
773 var a
= left
.criteria
, b
= right
.criteria
;
774 return a
< b
? -1 : a
> b
? 1 : 0;
778 toArray: function() {
783 var iterator
= Prototype
.K
, args
= $A(arguments
);
784 if (Object
.isFunction(args
.last()))
785 iterator
= args
.pop();
787 var collections
= [this].concat(args
).map($A
);
788 return this.map(function(value
, index
) {
789 return iterator(collections
.pluck(index
));
794 return this.toArray().length
;
797 inspect: function() {
798 return '#<Enumerable:' + this.toArray().inspect() + '>';
802 Object
.extend(Enumerable
, {
803 map
: Enumerable
.collect
,
804 find
: Enumerable
.detect
,
805 select
: Enumerable
.findAll
,
806 filter
: Enumerable
.findAll
,
807 member
: Enumerable
.include
,
808 entries
: Enumerable
.toArray
,
809 every
: Enumerable
.all
,
812 function $A(iterable
) {
813 if (!iterable
) return [];
814 if (iterable
.toArray
) return iterable
.toArray();
815 var length
= iterable
.length
|| 0, results
= new Array(length
);
816 while (length
--) results
[length
] = iterable
[length
];
820 if (Prototype
.Browser
.WebKit
) {
821 $A = function(iterable
) {
822 if (!iterable
) return [];
823 // In Safari, only use the `toArray` method if it's not a NodeList.
824 // A NodeList is a function, has an function `item` property, and a numeric
825 // `length` property. Adapted from Google Doctype.
826 if (!(typeof iterable
=== 'function' && typeof iterable
.length
===
827 'number' && typeof iterable
.item
=== 'function') && iterable
.toArray
)
828 return iterable
.toArray();
829 var length
= iterable
.length
|| 0, results
= new Array(length
);
830 while (length
--) results
[length
] = iterable
[length
];
837 Object
.extend(Array
.prototype, Enumerable
);
839 if (!Array
.prototype._reverse
) Array
.prototype._reverse
= Array
.prototype.reverse
;
841 Object
.extend(Array
.prototype, {
842 _each: function(iterator
) {
843 for (var i
= 0, length
= this.length
; i
< length
; i
++)
857 return this[this.length
- 1];
860 compact: function() {
861 return this.select(function(value
) {
862 return value
!= null;
866 flatten: function() {
867 return this.inject([], function(array
, value
) {
868 return array
.concat(Object
.isArray(value
) ?
869 value
.flatten() : [value
]);
873 without: function() {
874 var values
= $A(arguments
);
875 return this.select(function(value
) {
876 return !values
.include(value
);
880 reverse: function(inline
) {
881 return (inline
!== false ? this : this.toArray())._reverse();
885 return this.length
> 1 ? this : this[0];
888 uniq: function(sorted
) {
889 return this.inject([], function(array
, value
, index
) {
890 if (0 == index
|| (sorted
? array
.last() != value
: !array
.include(value
)))
896 intersect: function(array
) {
897 return this.uniq().findAll(function(item
) {
898 return array
.detect(function(value
) { return item
=== value
});
903 return [].concat(this);
910 inspect: function() {
911 return '[' + this.map(Object
.inspect
).join(', ') + ']';
916 this.each(function(object
) {
917 var value
= Object
.toJSON(object
);
918 if (!Object
.isUndefined(value
)) results
.push(value
);
920 return '[' + results
.join(', ') + ']';
924 // use native browser JS 1.6 implementation if available
925 if (Object
.isFunction(Array
.prototype.forEach
))
926 Array
.prototype._each
= Array
.prototype.forEach
;
928 if (!Array
.prototype.indexOf
) Array
.prototype.indexOf = function(item
, i
) {
930 var length
= this.length
;
931 if (i
< 0) i
= length
+ i
;
932 for (; i
< length
; i
++)
933 if (this[i
] === item
) return i
;
937 if (!Array
.prototype.lastIndexOf
) Array
.prototype.lastIndexOf = function(item
, i
) {
938 i
= isNaN(i
) ? this.length
: (i
< 0 ? this.length
+ i
: i
) + 1;
939 var n
= this.slice(0, i
).reverse().indexOf(item
);
940 return (n
< 0) ? n
: i
- n
- 1;
943 Array
.prototype.toArray
= Array
.prototype.clone
;
945 function $w(string
) {
946 if (!Object
.isString(string
)) return [];
947 string
= string
.strip();
948 return string
? string
.split(/\s+/) : [];
951 if (Prototype
.Browser
.Opera
){
952 Array
.prototype.concat = function() {
954 for (var i
= 0, length
= this.length
; i
< length
; i
++) array
.push(this[i
]);
955 for (var i
= 0, length
= arguments
.length
; i
< length
; i
++) {
956 if (Object
.isArray(arguments
[i
])) {
957 for (var j
= 0, arrayLength
= arguments
[i
].length
; j
< arrayLength
; j
++)
958 array
.push(arguments
[i
][j
]);
960 array
.push(arguments
[i
]);
966 Object
.extend(Number
.prototype, {
967 toColorPart: function() {
968 return this.toPaddedString(2, 16);
975 times: function(iterator
, context
) {
976 $R(0, this, true).each(iterator
, context
);
980 toPaddedString: function(length
, radix
) {
981 var string
= this.toString(radix
|| 10);
982 return '0'.times(length
- string
.length
) + string
;
986 return isFinite(this) ? this.toString() : 'null';
990 $w('abs round ceil floor').each(function(method
){
991 Number
.prototype[method
] = Math
[method
].methodize();
993 function $H(object
) {
994 return new Hash(object
);
997 var Hash
= Class
.create(Enumerable
, (function() {
999 function toQueryPair(key
, value
) {
1000 if (Object
.isUndefined(value
)) return key
;
1001 return key
+ '=' + encodeURIComponent(String
.interpret(value
));
1005 initialize: function(object
) {
1006 this._object
= Object
.isHash(object
) ? object
.toObject() : Object
.clone(object
);
1009 _each: function(iterator
) {
1010 for (var key
in this._object
) {
1011 var value
= this._object
[key
], pair
= [key
, value
];
1018 set: function(key
, value
) {
1019 return this._object
[key
] = value
;
1022 get: function(key
) {
1023 // simulating poorly supported hasOwnProperty
1024 if (this._object
[key
] !== Object
.prototype[key
])
1025 return this._object
[key
];
1028 unset: function(key
) {
1029 var value
= this._object
[key
];
1030 delete this._object
[key
];
1034 toObject: function() {
1035 return Object
.clone(this._object
);
1039 return this.pluck('key');
1042 values: function() {
1043 return this.pluck('value');
1046 index: function(value
) {
1047 var match
= this.detect(function(pair
) {
1048 return pair
.value
=== value
;
1050 return match
&& match
.key
;
1053 merge: function(object
) {
1054 return this.clone().update(object
);
1057 update: function(object
) {
1058 return new Hash(object
).inject(this, function(result
, pair
) {
1059 result
.set(pair
.key
, pair
.value
);
1064 toQueryString: function() {
1065 return this.inject([], function(results
, pair
) {
1066 var key
= encodeURIComponent(pair
.key
), values
= pair
.value
;
1068 if (values
&& typeof values
== 'object') {
1069 if (Object
.isArray(values
))
1070 return results
.concat(values
.map(toQueryPair
.curry(key
)));
1071 } else results
.push(toQueryPair(key
, values
));
1076 inspect: function() {
1077 return '#<Hash:{' + this.map(function(pair
) {
1078 return pair
.map(Object
.inspect
).join(': ');
1079 }).join(', ') + '}>';
1082 toJSON: function() {
1083 return Object
.toJSON(this.toObject());
1087 return new Hash(this);
1092 Hash
.prototype.toTemplateReplacements
= Hash
.prototype.toObject
;
1094 var ObjectRange
= Class
.create(Enumerable
, {
1095 initialize: function(start
, end
, exclusive
) {
1098 this.exclusive
= exclusive
;
1101 _each: function(iterator
) {
1102 var value
= this.start
;
1103 while (this.include(value
)) {
1105 value
= value
.succ();
1109 include: function(value
) {
1110 if (value
< this.start
)
1113 return value
< this.end
;
1114 return value
<= this.end
;
1118 var $R = function(start
, end
, exclusive
) {
1119 return new ObjectRange(start
, end
, exclusive
);
1123 getTransport: function() {
1125 function() {return new XMLHttpRequest()},
1126 function() {return new ActiveXObject('Msxml2.XMLHTTP')},
1127 function() {return new ActiveXObject('Microsoft.XMLHTTP')}
1131 activeRequestCount
: 0
1137 _each: function(iterator
) {
1138 this.responders
._each(iterator
);
1141 register: function(responder
) {
1142 if (!this.include(responder
))
1143 this.responders
.push(responder
);
1146 unregister: function(responder
) {
1147 this.responders
= this.responders
.without(responder
);
1150 dispatch: function(callback
, request
, transport
, json
) {
1151 this.each(function(responder
) {
1152 if (Object
.isFunction(responder
[callback
])) {
1154 responder
[callback
].apply(responder
, [request
, transport
, json
]);
1161 Object
.extend(Ajax
.Responders
, Enumerable
);
1163 Ajax
.Responders
.register({
1164 onCreate: function() { Ajax
.activeRequestCount
++ },
1165 onComplete: function() { Ajax
.activeRequestCount
-- }
1168 Ajax
.Base
= Class
.create({
1169 initialize: function(options
) {
1173 contentType
: 'application/x-www-form-urlencoded',
1179 Object
.extend(this.options
, options
|| { });
1181 this.options
.method
= this.options
.method
.toLowerCase();
1183 if (Object
.isString(this.options
.parameters
))
1184 this.options
.parameters
= this.options
.parameters
.toQueryParams();
1185 else if (Object
.isHash(this.options
.parameters
))
1186 this.options
.parameters
= this.options
.parameters
.toObject();
1190 Ajax
.Request
= Class
.create(Ajax
.Base
, {
1193 initialize: function($super, url
, options
) {
1195 this.transport
= Ajax
.getTransport();
1199 request: function(url
) {
1201 this.method
= this.options
.method
;
1202 var params
= Object
.clone(this.options
.parameters
);
1204 if (!['get', 'post'].include(this.method
)) {
1205 // simulate other verbs over post
1206 params
['_method'] = this.method
;
1207 this.method
= 'post';
1210 this.parameters
= params
;
1212 if (params
= Object
.toQueryString(params
)) {
1213 // when GET, append parameters to URL
1214 if (this.method
== 'get')
1215 this.url
+= (this.url
.include('?') ? '&' : '?') + params
;
1216 else if (/Konqueror|Safari|KHTML/.test(navigator
.userAgent
))
1221 var response
= new Ajax
.Response(this);
1222 if (this.options
.onCreate
) this.options
.onCreate(response
);
1223 Ajax
.Responders
.dispatch('onCreate', this, response
);
1225 this.transport
.open(this.method
.toUpperCase(), this.url
,
1226 this.options
.asynchronous
);
1228 if (this.options
.asynchronous
) this.respondToReadyState
.bind(this).defer(1);
1230 this.transport
.onreadystatechange
= this.onStateChange
.bind(this);
1231 this.setRequestHeaders();
1233 this.body
= this.method
== 'post' ? (this.options
.postBody
|| params
) : null;
1234 this.transport
.send(this.body
);
1236 /* Force Firefox to handle ready state 4 for synchronous requests */
1237 if (!this.options
.asynchronous
&& this.transport
.overrideMimeType
)
1238 this.onStateChange();
1242 this.dispatchException(e
);
1246 onStateChange: function() {
1247 var readyState
= this.transport
.readyState
;
1248 if (readyState
> 1 && !((readyState
== 4) && this._complete
))
1249 this.respondToReadyState(this.transport
.readyState
);
1252 setRequestHeaders: function() {
1254 'X-Requested-With': 'XMLHttpRequest',
1255 'X-Prototype-Version': Prototype
.Version
,
1256 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
1259 if (this.method
== 'post') {
1260 headers
['Content-type'] = this.options
.contentType
+
1261 (this.options
.encoding
? '; charset=' + this.options
.encoding
: '');
1263 /* Force "Connection: close" for older Mozilla browsers to work
1264 * around a bug where XMLHttpRequest sends an incorrect
1265 * Content-length header. See Mozilla Bugzilla #246651.
1267 if (this.transport
.overrideMimeType
&&
1268 (navigator
.userAgent
.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
1269 headers
['Connection'] = 'close';
1272 // user-defined headers
1273 if (typeof this.options
.requestHeaders
== 'object') {
1274 var extras
= this.options
.requestHeaders
;
1276 if (Object
.isFunction(extras
.push
))
1277 for (var i
= 0, length
= extras
.length
; i
< length
; i
+= 2)
1278 headers
[extras
[i
]] = extras
[i
+1];
1280 $H(extras
).each(function(pair
) { headers
[pair
.key
] = pair
.value
});
1283 for (var name
in headers
)
1284 this.transport
.setRequestHeader(name
, headers
[name
]);
1287 success: function() {
1288 var status
= this.getStatus();
1289 return !status
|| (status
>= 200 && status
< 300);
1292 getStatus: function() {
1294 return this.transport
.status
|| 0;
1295 } catch (e
) { return 0 }
1298 respondToReadyState: function(readyState
) {
1299 var state
= Ajax
.Request
.Events
[readyState
], response
= new Ajax
.Response(this);
1301 if (state
== 'Complete') {
1303 this._complete
= true;
1304 (this.options
['on' + response
.status
]
1305 || this.options
['on' + (this.success() ? 'Success' : 'Failure')]
1306 || Prototype
.emptyFunction
)(response
, response
.headerJSON
);
1308 this.dispatchException(e
);
1311 var contentType
= response
.getHeader('Content-type');
1312 if (this.options
.evalJS
== 'force'
1313 || (this.options
.evalJS
&& this.isSameOrigin() && contentType
1314 && contentType
.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
1315 this.evalResponse();
1319 (this.options
['on' + state
] || Prototype
.emptyFunction
)(response
, response
.headerJSON
);
1320 Ajax
.Responders
.dispatch('on' + state
, this, response
, response
.headerJSON
);
1322 this.dispatchException(e
);
1325 if (state
== 'Complete') {
1326 // avoid memory leak in MSIE: clean up
1327 this.transport
.onreadystatechange
= Prototype
.emptyFunction
;
1331 isSameOrigin: function() {
1332 var m
= this.url
.match(/^\s*https?:\/\/[^\/]*/);
1333 return !m
|| (m
[0] == '#{protocol}//#{domain}#{port}'.interpolate({
1334 protocol
: location
.protocol
,
1335 domain
: document
.domain
,
1336 port
: location
.port
? ':' + location
.port
: ''
1340 getHeader: function(name
) {
1342 return this.transport
.getResponseHeader(name
) || null;
1343 } catch (e
) { return null }
1346 evalResponse: function() {
1348 return eval((this.transport
.responseText
|| '').unfilterJSON());
1350 this.dispatchException(e
);
1354 dispatchException: function(exception
) {
1355 (this.options
.onException
|| Prototype
.emptyFunction
)(this, exception
);
1356 Ajax
.Responders
.dispatch('onException', this, exception
);
1360 Ajax
.Request
.Events
=
1361 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
1363 Ajax
.Response
= Class
.create({
1364 initialize: function(request
){
1365 this.request
= request
;
1366 var transport
= this.transport
= request
.transport
,
1367 readyState
= this.readyState
= transport
.readyState
;
1369 if((readyState
> 2 && !Prototype
.Browser
.IE
) || readyState
== 4) {
1370 this.status
= this.getStatus();
1371 this.statusText
= this.getStatusText();
1372 this.responseText
= String
.interpret(transport
.responseText
);
1373 this.headerJSON
= this._getHeaderJSON();
1376 if(readyState
== 4) {
1377 var xml
= transport
.responseXML
;
1378 this.responseXML
= Object
.isUndefined(xml
) ? null : xml
;
1379 this.responseJSON
= this._getResponseJSON();
1386 getStatus
: Ajax
.Request
.prototype.getStatus
,
1388 getStatusText: function() {
1390 return this.transport
.statusText
|| '';
1391 } catch (e
) { return '' }
1394 getHeader
: Ajax
.Request
.prototype.getHeader
,
1396 getAllHeaders: function() {
1398 return this.getAllResponseHeaders();
1399 } catch (e
) { return null }
1402 getResponseHeader: function(name
) {
1403 return this.transport
.getResponseHeader(name
);
1406 getAllResponseHeaders: function() {
1407 return this.transport
.getAllResponseHeaders();
1410 _getHeaderJSON: function() {
1411 var json
= this.getHeader('X-JSON');
1412 if (!json
) return null;
1413 json
= decodeURIComponent(escape(json
));
1415 return json
.evalJSON(this.request
.options
.sanitizeJSON
||
1416 !this.request
.isSameOrigin());
1418 this.request
.dispatchException(e
);
1422 _getResponseJSON: function() {
1423 var options
= this.request
.options
;
1424 if (!options
.evalJSON
|| (options
.evalJSON
!= 'force' &&
1425 !(this.getHeader('Content-type') || '').include('application/json')) ||
1426 this.responseText
.blank())
1429 return this.responseText
.evalJSON(options
.sanitizeJSON
||
1430 !this.request
.isSameOrigin());
1432 this.request
.dispatchException(e
);
1437 Ajax
.Updater
= Class
.create(Ajax
.Request
, {
1438 initialize: function($super, container
, url
, options
) {
1440 success
: (container
.success
|| container
),
1441 failure
: (container
.failure
|| (container
.success
? null : container
))
1444 options
= Object
.clone(options
);
1445 var onComplete
= options
.onComplete
;
1446 options
.onComplete
= (function(response
, json
) {
1447 this.updateContent(response
.responseText
);
1448 if (Object
.isFunction(onComplete
)) onComplete(response
, json
);
1451 $super(url
, options
);
1454 updateContent: function(responseText
) {
1455 var receiver
= this.container
[this.success() ? 'success' : 'failure'],
1456 options
= this.options
;
1458 if (!options
.evalScripts
) responseText
= responseText
.stripScripts();
1460 if (receiver
= $(receiver
)) {
1461 if (options
.insertion
) {
1462 if (Object
.isString(options
.insertion
)) {
1463 var insertion
= { }; insertion
[options
.insertion
] = responseText
;
1464 receiver
.insert(insertion
);
1466 else options
.insertion(receiver
, responseText
);
1468 else receiver
.update(responseText
);
1473 Ajax
.PeriodicalUpdater
= Class
.create(Ajax
.Base
, {
1474 initialize: function($super, container
, url
, options
) {
1476 this.onComplete
= this.options
.onComplete
;
1478 this.frequency
= (this.options
.frequency
|| 2);
1479 this.decay
= (this.options
.decay
|| 1);
1482 this.container
= container
;
1489 this.options
.onComplete
= this.updateComplete
.bind(this);
1490 this.onTimerEvent();
1494 this.updater
.options
.onComplete
= undefined;
1495 clearTimeout(this.timer
);
1496 (this.onComplete
|| Prototype
.emptyFunction
).apply(this, arguments
);
1499 updateComplete: function(response
) {
1500 if (this.options
.decay
) {
1501 this.decay
= (response
.responseText
== this.lastText
?
1502 this.decay
* this.options
.decay
: 1);
1504 this.lastText
= response
.responseText
;
1506 this.timer
= this.onTimerEvent
.bind(this).delay(this.decay
* this.frequency
);
1509 onTimerEvent: function() {
1510 this.updater
= new Ajax
.Updater(this.container
, this.url
, this.options
);
1513 function $(element
) {
1514 if (arguments
.length
> 1) {
1515 for (var i
= 0, elements
= [], length
= arguments
.length
; i
< length
; i
++)
1516 elements
.push($(arguments
[i
]));
1519 if (Object
.isString(element
))
1520 element
= document
.getElementById(element
);
1521 return Element
.extend(element
);
1524 if (Prototype
.BrowserFeatures
.XPath
) {
1525 document
._getElementsByXPath = function(expression
, parentElement
) {
1527 var query
= document
.evaluate(expression
, $(parentElement
) || document
,
1528 null, XPathResult
.ORDERED_NODE_SNAPSHOT_TYPE
, null);
1529 for (var i
= 0, length
= query
.snapshotLength
; i
< length
; i
++)
1530 results
.push(Element
.extend(query
.snapshotItem(i
)));
1535 /*--------------------------------------------------------------------------*/
1537 if (!window
.Node
) var Node
= { };
1539 if (!Node
.ELEMENT_NODE
) {
1540 // DOM level 2 ECMAScript Language Binding
1541 Object
.extend(Node
, {
1545 CDATA_SECTION_NODE
: 4,
1546 ENTITY_REFERENCE_NODE
: 5,
1548 PROCESSING_INSTRUCTION_NODE
: 7,
1551 DOCUMENT_TYPE_NODE
: 10,
1552 DOCUMENT_FRAGMENT_NODE
: 11,
1558 var element
= this.Element
;
1559 this.Element = function(tagName
, attributes
) {
1560 attributes
= attributes
|| { };
1561 tagName
= tagName
.toLowerCase();
1562 var cache
= Element
.cache
;
1563 if (Prototype
.Browser
.IE
&& attributes
.name
) {
1564 tagName
= '<' + tagName
+ ' name="' + attributes
.name
+ '">';
1565 delete attributes
.name
;
1566 return Element
.writeAttribute(document
.createElement(tagName
), attributes
);
1568 if (!cache
[tagName
]) cache
[tagName
] = Element
.extend(document
.createElement(tagName
));
1569 return Element
.writeAttribute(cache
[tagName
].cloneNode(false), attributes
);
1571 Object
.extend(this.Element
, element
|| { });
1572 if (element
) this.Element
.prototype = element
.prototype;
1575 Element
.cache
= { };
1578 visible: function(element
) {
1579 return $(element
).style
.display
!= 'none';
1582 toggle: function(element
) {
1583 element
= $(element
);
1584 Element
[Element
.visible(element
) ? 'hide' : 'show'](element
);
1588 hide: function(element
) {
1589 element
= $(element
);
1590 element
.style
.display
= 'none';
1594 show: function(element
) {
1595 element
= $(element
);
1596 element
.style
.display
= '';
1600 remove: function(element
) {
1601 element
= $(element
);
1602 element
.parentNode
.removeChild(element
);
1606 update: function(element
, content
) {
1607 element
= $(element
);
1608 if (content
&& content
.toElement
) content
= content
.toElement();
1609 if (Object
.isElement(content
)) return element
.update().insert(content
);
1610 content
= Object
.toHTML(content
);
1611 element
.innerHTML
= content
.stripScripts();
1612 content
.evalScripts
.bind(content
).defer();
1616 replace: function(element
, content
) {
1617 element
= $(element
);
1618 if (content
&& content
.toElement
) content
= content
.toElement();
1619 else if (!Object
.isElement(content
)) {
1620 content
= Object
.toHTML(content
);
1621 var range
= element
.ownerDocument
.createRange();
1622 range
.selectNode(element
);
1623 content
.evalScripts
.bind(content
).defer();
1624 content
= range
.createContextualFragment(content
.stripScripts());
1626 element
.parentNode
.replaceChild(content
, element
);
1630 insert: function(element
, insertions
) {
1631 element
= $(element
);
1633 if (Object
.isString(insertions
) || Object
.isNumber(insertions
) ||
1634 Object
.isElement(insertions
) || (insertions
&& (insertions
.toElement
|| insertions
.toHTML
)))
1635 insertions
= {bottom
:insertions
};
1637 var content
, insert
, tagName
, childNodes
;
1639 for (var position
in insertions
) {
1640 content
= insertions
[position
];
1641 position
= position
.toLowerCase();
1642 insert
= Element
._insertionTranslations
[position
];
1644 if (content
&& content
.toElement
) content
= content
.toElement();
1645 if (Object
.isElement(content
)) {
1646 insert(element
, content
);
1650 content
= Object
.toHTML(content
);
1652 tagName
= ((position
== 'before' || position
== 'after')
1653 ? element
.parentNode
: element
).tagName
.toUpperCase();
1655 childNodes
= Element
._getContentFromAnonymousElement(tagName
, content
.stripScripts());
1657 if (position
== 'top' || position
== 'after') childNodes
.reverse();
1658 childNodes
.each(insert
.curry(element
));
1660 content
.evalScripts
.bind(content
).defer();
1666 wrap: function(element
, wrapper
, attributes
) {
1667 element
= $(element
);
1668 if (Object
.isElement(wrapper
))
1669 $(wrapper
).writeAttribute(attributes
|| { });
1670 else if (Object
.isString(wrapper
)) wrapper
= new Element(wrapper
, attributes
);
1671 else wrapper
= new Element('div', wrapper
);
1672 if (element
.parentNode
)
1673 element
.parentNode
.replaceChild(wrapper
, element
);
1674 wrapper
.appendChild(element
);
1678 inspect: function(element
) {
1679 element
= $(element
);
1680 var result
= '<' + element
.tagName
.toLowerCase();
1681 $H({'id': 'id', 'className': 'class'}).each(function(pair
) {
1682 var property
= pair
.first(), attribute
= pair
.last();
1683 var value
= (element
[property
] || '').toString();
1684 if (value
) result
+= ' ' + attribute
+ '=' + value
.inspect(true);
1686 return result
+ '>';
1689 recursivelyCollect: function(element
, property
) {
1690 element
= $(element
);
1692 while (element
= element
[property
])
1693 if (element
.nodeType
== 1)
1694 elements
.push(Element
.extend(element
));
1698 ancestors: function(element
) {
1699 return $(element
).recursivelyCollect('parentNode');
1702 descendants: function(element
) {
1703 return $(element
).select("*");
1706 firstDescendant: function(element
) {
1707 element
= $(element
).firstChild
;
1708 while (element
&& element
.nodeType
!= 1) element
= element
.nextSibling
;
1712 immediateDescendants: function(element
) {
1713 if (!(element
= $(element
).firstChild
)) return [];
1714 while (element
&& element
.nodeType
!= 1) element
= element
.nextSibling
;
1715 if (element
) return [element
].concat($(element
).nextSiblings());
1719 previousSiblings: function(element
) {
1720 return $(element
).recursivelyCollect('previousSibling');
1723 nextSiblings: function(element
) {
1724 return $(element
).recursivelyCollect('nextSibling');
1727 siblings: function(element
) {
1728 element
= $(element
);
1729 return element
.previousSiblings().reverse().concat(element
.nextSiblings());
1732 match: function(element
, selector
) {
1733 if (Object
.isString(selector
))
1734 selector
= new Selector(selector
);
1735 return selector
.match($(element
));
1738 up: function(element
, expression
, index
) {
1739 element
= $(element
);
1740 if (arguments
.length
== 1) return $(element
.parentNode
);
1741 var ancestors
= element
.ancestors();
1742 return Object
.isNumber(expression
) ? ancestors
[expression
] :
1743 Selector
.findElement(ancestors
, expression
, index
);
1746 down: function(element
, expression
, index
) {
1747 element
= $(element
);
1748 if (arguments
.length
== 1) return element
.firstDescendant();
1749 return Object
.isNumber(expression
) ? element
.descendants()[expression
] :
1750 Element
.select(element
, expression
)[index
|| 0];
1753 previous: function(element
, expression
, index
) {
1754 element
= $(element
);
1755 if (arguments
.length
== 1) return $(Selector
.handlers
.previousElementSibling(element
));
1756 var previousSiblings
= element
.previousSiblings();
1757 return Object
.isNumber(expression
) ? previousSiblings
[expression
] :
1758 Selector
.findElement(previousSiblings
, expression
, index
);
1761 next: function(element
, expression
, index
) {
1762 element
= $(element
);
1763 if (arguments
.length
== 1) return $(Selector
.handlers
.nextElementSibling(element
));
1764 var nextSiblings
= element
.nextSiblings();
1765 return Object
.isNumber(expression
) ? nextSiblings
[expression
] :
1766 Selector
.findElement(nextSiblings
, expression
, index
);
1769 select: function() {
1770 var args
= $A(arguments
), element
= $(args
.shift());
1771 return Selector
.findChildElements(element
, args
);
1774 adjacent: function() {
1775 var args
= $A(arguments
), element
= $(args
.shift());
1776 return Selector
.findChildElements(element
.parentNode
, args
).without(element
);
1779 identify: function(element
) {
1780 element
= $(element
);
1781 var id
= element
.readAttribute('id'), self
= arguments
.callee
;
1783 do { id
= 'anonymous_element_' + self
.counter
++ } while ($(id
));
1784 element
.writeAttribute('id', id
);
1788 readAttribute: function(element
, name
) {
1789 element
= $(element
);
1790 if (Prototype
.Browser
.IE
) {
1791 var t
= Element
._attributeTranslations
.read
;
1792 if (t
.values
[name
]) return t
.values
[name
](element
, name
);
1793 if (t
.names
[name
]) name
= t
.names
[name
];
1794 if (name
.include(':')) {
1795 return (!element
.attributes
|| !element
.attributes
[name
]) ? null :
1796 element
.attributes
[name
].value
;
1799 return element
.getAttribute(name
);
1802 writeAttribute: function(element
, name
, value
) {
1803 element
= $(element
);
1804 var attributes
= { }, t
= Element
._attributeTranslations
.write
;
1806 if (typeof name
== 'object') attributes
= name
;
1807 else attributes
[name
] = Object
.isUndefined(value
) ? true : value
;
1809 for (var attr
in attributes
) {
1810 name
= t
.names
[attr
] || attr
;
1811 value
= attributes
[attr
];
1812 if (t
.values
[attr
]) name
= t
.values
[attr
](element
, value
);
1813 if (value
=== false || value
=== null)
1814 element
.removeAttribute(name
);
1815 else if (value
=== true)
1816 element
.setAttribute(name
, name
);
1817 else element
.setAttribute(name
, value
);
1822 getHeight: function(element
) {
1823 return $(element
).getDimensions().height
;
1826 getWidth: function(element
) {
1827 return $(element
).getDimensions().width
;
1830 classNames: function(element
) {
1831 return new Element
.ClassNames(element
);
1834 hasClassName: function(element
, className
) {
1835 if (!(element
= $(element
))) return;
1836 var elementClassName
= element
.className
;
1837 return (elementClassName
.length
> 0 && (elementClassName
== className
||
1838 new RegExp("(^|\\s)" + className
+ "(\\s|$)").test(elementClassName
)));
1841 addClassName: function(element
, className
) {
1842 if (!(element
= $(element
))) return;
1843 if (!element
.hasClassName(className
))
1844 element
.className
+= (element
.className
? ' ' : '') + className
;
1848 removeClassName: function(element
, className
) {
1849 if (!(element
= $(element
))) return;
1850 element
.className
= element
.className
.replace(
1851 new RegExp("(^|\\s+)" + className
+ "(\\s+|$)"), ' ').strip();
1855 toggleClassName: function(element
, className
) {
1856 if (!(element
= $(element
))) return;
1857 return element
[element
.hasClassName(className
) ?
1858 'removeClassName' : 'addClassName'](className
);
1861 // removes whitespace-only text node children
1862 cleanWhitespace: function(element
) {
1863 element
= $(element
);
1864 var node
= element
.firstChild
;
1866 var nextNode
= node
.nextSibling
;
1867 if (node
.nodeType
== 3 && !/\S/.test(node
.nodeValue
))
1868 element
.removeChild(node
);
1874 empty: function(element
) {
1875 return $(element
).innerHTML
.blank();
1878 descendantOf: function(element
, ancestor
) {
1879 element
= $(element
), ancestor
= $(ancestor
);
1881 if (element
.compareDocumentPosition
)
1882 return (element
.compareDocumentPosition(ancestor
) & 8) === 8;
1884 if (ancestor
.contains
)
1885 return ancestor
.contains(element
) && ancestor
!== element
;
1887 while (element
= element
.parentNode
)
1888 if (element
== ancestor
) return true;
1893 scrollTo: function(element
) {
1894 element
= $(element
);
1895 var pos
= element
.cumulativeOffset();
1896 window
.scrollTo(pos
[0], pos
[1]);
1900 getStyle: function(element
, style
) {
1901 element
= $(element
);
1902 style
= style
== 'float' ? 'cssFloat' : style
.camelize();
1903 var value
= element
.style
[style
];
1904 if (!value
|| value
== 'auto') {
1905 var css
= document
.defaultView
.getComputedStyle(element
, null);
1906 value
= css
? css
[style
] : null;
1908 if (style
== 'opacity') return value
? parseFloat(value
) : 1.0;
1909 return value
== 'auto' ? null : value
;
1912 getOpacity: function(element
) {
1913 return $(element
).getStyle('opacity');
1916 setStyle: function(element
, styles
) {
1917 element
= $(element
);
1918 var elementStyle
= element
.style
, match
;
1919 if (Object
.isString(styles
)) {
1920 element
.style
.cssText
+= ';' + styles
;
1921 return styles
.include('opacity') ?
1922 element
.setOpacity(styles
.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element
;
1924 for (var property
in styles
)
1925 if (property
== 'opacity') element
.setOpacity(styles
[property
]);
1927 elementStyle
[(property
== 'float' || property
== 'cssFloat') ?
1928 (Object
.isUndefined(elementStyle
.styleFloat
) ? 'cssFloat' : 'styleFloat') :
1929 property
] = styles
[property
];
1934 setOpacity: function(element
, value
) {
1935 element
= $(element
);
1936 element
.style
.opacity
= (value
== 1 || value
=== '') ? '' :
1937 (value
< 0.00001) ? 0 : value
;
1941 getDimensions: function(element
) {
1942 element
= $(element
);
1943 var display
= element
.getStyle('display');
1944 if (display
!= 'none' && display
!= null) // Safari bug
1945 return {width
: element
.offsetWidth
, height
: element
.offsetHeight
};
1947 // All *Width and *Height properties give 0 on elements with display none,
1948 // so enable the element temporarily
1949 var els
= element
.style
;
1950 var originalVisibility
= els
.visibility
;
1951 var originalPosition
= els
.position
;
1952 var originalDisplay
= els
.display
;
1953 els
.visibility
= 'hidden';
1954 els
.position
= 'absolute';
1955 els
.display
= 'block';
1956 var originalWidth
= element
.clientWidth
;
1957 var originalHeight
= element
.clientHeight
;
1958 els
.display
= originalDisplay
;
1959 els
.position
= originalPosition
;
1960 els
.visibility
= originalVisibility
;
1961 return {width
: originalWidth
, height
: originalHeight
};
1964 makePositioned: function(element
) {
1965 element
= $(element
);
1966 var pos
= Element
.getStyle(element
, 'position');
1967 if (pos
== 'static' || !pos
) {
1968 element
._madePositioned
= true;
1969 element
.style
.position
= 'relative';
1970 // Opera returns the offset relative to the positioning context, when an
1971 // element is position relative but top and left have not been defined
1972 if (Prototype
.Browser
.Opera
) {
1973 element
.style
.top
= 0;
1974 element
.style
.left
= 0;
1980 undoPositioned: function(element
) {
1981 element
= $(element
);
1982 if (element
._madePositioned
) {
1983 element
._madePositioned
= undefined;
1984 element
.style
.position
=
1986 element
.style
.left
=
1987 element
.style
.bottom
=
1988 element
.style
.right
= '';
1993 makeClipping: function(element
) {
1994 element
= $(element
);
1995 if (element
._overflow
) return element
;
1996 element
._overflow
= Element
.getStyle(element
, 'overflow') || 'auto';
1997 if (element
._overflow
!== 'hidden')
1998 element
.style
.overflow
= 'hidden';
2002 undoClipping: function(element
) {
2003 element
= $(element
);
2004 if (!element
._overflow
) return element
;
2005 element
.style
.overflow
= element
._overflow
== 'auto' ? '' : element
._overflow
;
2006 element
._overflow
= null;
2010 cumulativeOffset: function(element
) {
2011 var valueT
= 0, valueL
= 0;
2013 valueT
+= element
.offsetTop
|| 0;
2014 valueL
+= element
.offsetLeft
|| 0;
2015 element
= element
.offsetParent
;
2017 return Element
._returnOffset(valueL
, valueT
);
2020 positionedOffset: function(element
) {
2021 var valueT
= 0, valueL
= 0;
2023 valueT
+= element
.offsetTop
|| 0;
2024 valueL
+= element
.offsetLeft
|| 0;
2025 element
= element
.offsetParent
;
2027 if (element
.tagName
.toUpperCase() == 'BODY') break;
2028 var p
= Element
.getStyle(element
, 'position');
2029 if (p
!== 'static') break;
2032 return Element
._returnOffset(valueL
, valueT
);
2035 absolutize: function(element
) {
2036 element
= $(element
);
2037 if (element
.getStyle('position') == 'absolute') return element
;
2038 // Position.prepare(); // To be done manually by Scripty when it needs it.
2040 var offsets
= element
.positionedOffset();
2041 var top
= offsets
[1];
2042 var left
= offsets
[0];
2043 var width
= element
.clientWidth
;
2044 var height
= element
.clientHeight
;
2046 element
._originalLeft
= left
- parseFloat(element
.style
.left
|| 0);
2047 element
._originalTop
= top
- parseFloat(element
.style
.top
|| 0);
2048 element
._originalWidth
= element
.style
.width
;
2049 element
._originalHeight
= element
.style
.height
;
2051 element
.style
.position
= 'absolute';
2052 element
.style
.top
= top
+ 'px';
2053 element
.style
.left
= left
+ 'px';
2054 element
.style
.width
= width
+ 'px';
2055 element
.style
.height
= height
+ 'px';
2059 relativize: function(element
) {
2060 element
= $(element
);
2061 if (element
.getStyle('position') == 'relative') return element
;
2062 // Position.prepare(); // To be done manually by Scripty when it needs it.
2064 element
.style
.position
= 'relative';
2065 var top
= parseFloat(element
.style
.top
|| 0) - (element
._originalTop
|| 0);
2066 var left
= parseFloat(element
.style
.left
|| 0) - (element
._originalLeft
|| 0);
2068 element
.style
.top
= top
+ 'px';
2069 element
.style
.left
= left
+ 'px';
2070 element
.style
.height
= element
._originalHeight
;
2071 element
.style
.width
= element
._originalWidth
;
2075 cumulativeScrollOffset: function(element
) {
2076 var valueT
= 0, valueL
= 0;
2078 valueT
+= element
.scrollTop
|| 0;
2079 valueL
+= element
.scrollLeft
|| 0;
2080 element
= element
.parentNode
;
2082 return Element
._returnOffset(valueL
, valueT
);
2085 getOffsetParent: function(element
) {
2086 if (element
.offsetParent
) return $(element
.offsetParent
);
2087 if (element
== document
.body
) return $(element
);
2089 while ((element
= element
.parentNode
) && element
!= document
.body
)
2090 if (Element
.getStyle(element
, 'position') != 'static')
2093 return $(document
.body
);
2096 viewportOffset: function(forElement
) {
2097 var valueT
= 0, valueL
= 0;
2099 var element
= forElement
;
2101 valueT
+= element
.offsetTop
|| 0;
2102 valueL
+= element
.offsetLeft
|| 0;
2105 if (element
.offsetParent
== document
.body
&&
2106 Element
.getStyle(element
, 'position') == 'absolute') break;
2108 } while (element
= element
.offsetParent
);
2110 element
= forElement
;
2112 if (!Prototype
.Browser
.Opera
|| (element
.tagName
&& (element
.tagName
.toUpperCase() == 'BODY'))) {
2113 valueT
-= element
.scrollTop
|| 0;
2114 valueL
-= element
.scrollLeft
|| 0;
2116 } while (element
= element
.parentNode
);
2118 return Element
._returnOffset(valueL
, valueT
);
2121 clonePosition: function(element
, source
) {
2122 var options
= Object
.extend({
2129 }, arguments
[2] || { });
2131 // find page position of source
2133 var p
= source
.viewportOffset();
2135 // find coordinate system to use
2136 element
= $(element
);
2139 // delta [0,0] will do fine with position: fixed elements,
2140 // position:absolute needs offsetParent deltas
2141 if (Element
.getStyle(element
, 'position') == 'absolute') {
2142 parent
= element
.getOffsetParent();
2143 delta
= parent
.viewportOffset();
2146 // correct by body offsets (fixes Safari)
2147 if (parent
== document
.body
) {
2148 delta
[0] -= document
.body
.offsetLeft
;
2149 delta
[1] -= document
.body
.offsetTop
;
2153 if (options
.setLeft
) element
.style
.left
= (p
[0] - delta
[0] + options
.offsetLeft
) + 'px';
2154 if (options
.setTop
) element
.style
.top
= (p
[1] - delta
[1] + options
.offsetTop
) + 'px';
2155 if (options
.setWidth
) element
.style
.width
= source
.offsetWidth
+ 'px';
2156 if (options
.setHeight
) element
.style
.height
= source
.offsetHeight
+ 'px';
2161 Element
.Methods
.identify
.counter
= 1;
2163 Object
.extend(Element
.Methods
, {
2164 getElementsBySelector
: Element
.Methods
.select
,
2165 childElements
: Element
.Methods
.immediateDescendants
2168 Element
._attributeTranslations
= {
2178 if (Prototype
.Browser
.Opera
) {
2179 Element
.Methods
.getStyle
= Element
.Methods
.getStyle
.wrap(
2180 function(proceed
, element
, style
) {
2182 case 'left': case 'top': case 'right': case 'bottom':
2183 if (proceed(element
, 'position') === 'static') return null;
2184 case 'height': case 'width':
2185 // returns '0px' for hidden elements; we want it to return null
2186 if (!Element
.visible(element
)) return null;
2188 // returns the border-box dimensions rather than the content-box
2189 // dimensions, so we subtract padding and borders from the value
2190 var dim
= parseInt(proceed(element
, style
), 10);
2192 if (dim
!== element
['offset' + style
.capitalize()])
2196 if (style
=== 'height') {
2197 properties
= ['border-top-width', 'padding-top',
2198 'padding-bottom', 'border-bottom-width'];
2201 properties
= ['border-left-width', 'padding-left',
2202 'padding-right', 'border-right-width'];
2204 return properties
.inject(dim
, function(memo
, property
) {
2205 var val
= proceed(element
, property
);
2206 return val
=== null ? memo
: memo
- parseInt(val
, 10);
2208 default: return proceed(element
, style
);
2213 Element
.Methods
.readAttribute
= Element
.Methods
.readAttribute
.wrap(
2214 function(proceed
, element
, attribute
) {
2215 if (attribute
=== 'title') return element
.title
;
2216 return proceed(element
, attribute
);
2221 else if (Prototype
.Browser
.IE
) {
2222 // IE doesn't report offsets correctly for static elements, so we change them
2223 // to "relative" to get the values, then change them back.
2224 Element
.Methods
.getOffsetParent
= Element
.Methods
.getOffsetParent
.wrap(
2225 function(proceed
, element
) {
2226 element
= $(element
);
2227 // IE throws an error if element is not in document
2228 try { element
.offsetParent
}
2229 catch(e
) { return $(document
.body
) }
2230 var position
= element
.getStyle('position');
2231 if (position
!== 'static') return proceed(element
);
2232 element
.setStyle({ position
: 'relative' });
2233 var value
= proceed(element
);
2234 element
.setStyle({ position
: position
});
2239 $w('positionedOffset viewportOffset').each(function(method
) {
2240 Element
.Methods
[method
] = Element
.Methods
[method
].wrap(
2241 function(proceed
, element
) {
2242 element
= $(element
);
2243 try { element
.offsetParent
}
2244 catch(e
) { return Element
._returnOffset(0,0) }
2245 var position
= element
.getStyle('position');
2246 if (position
!== 'static') return proceed(element
);
2247 // Trigger hasLayout on the offset parent so that IE6 reports
2248 // accurate offsetTop and offsetLeft values for position: fixed.
2249 var offsetParent
= element
.getOffsetParent();
2250 if (offsetParent
&& offsetParent
.getStyle('position') === 'fixed')
2251 offsetParent
.setStyle({ zoom
: 1 });
2252 element
.setStyle({ position
: 'relative' });
2253 var value
= proceed(element
);
2254 element
.setStyle({ position
: position
});
2260 Element
.Methods
.cumulativeOffset
= Element
.Methods
.cumulativeOffset
.wrap(
2261 function(proceed
, element
) {
2262 try { element
.offsetParent
}
2263 catch(e
) { return Element
._returnOffset(0,0) }
2264 return proceed(element
);
2268 Element
.Methods
.getStyle = function(element
, style
) {
2269 element
= $(element
);
2270 style
= (style
== 'float' || style
== 'cssFloat') ? 'styleFloat' : style
.camelize();
2271 var value
= element
.style
[style
];
2272 if (!value
&& element
.currentStyle
) value
= element
.currentStyle
[style
];
2274 if (style
== 'opacity') {
2275 if (value
= (element
.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
2276 if (value
[1]) return parseFloat(value
[1]) / 100;
2280 if (value
== 'auto') {
2281 if ((style
== 'width' || style
== 'height') && (element
.getStyle('display') != 'none'))
2282 return element
['offset' + style
.capitalize()] + 'px';
2288 Element
.Methods
.setOpacity = function(element
, value
) {
2289 function stripAlpha(filter
){
2290 return filter
.replace(/alpha\([^\)]*\)/gi,'');
2292 element
= $(element
);
2293 var currentStyle
= element
.currentStyle
;
2294 if ((currentStyle
&& !currentStyle
.hasLayout
) ||
2295 (!currentStyle
&& element
.style
.zoom
== 'normal'))
2296 element
.style
.zoom
= 1;
2298 var filter
= element
.getStyle('filter'), style
= element
.style
;
2299 if (value
== 1 || value
=== '') {
2300 (filter
= stripAlpha(filter
)) ?
2301 style
.filter
= filter
: style
.removeAttribute('filter');
2303 } else if (value
< 0.00001) value
= 0;
2304 style
.filter
= stripAlpha(filter
) +
2305 'alpha(opacity=' + (value
* 100) + ')';
2309 Element
._attributeTranslations
= {
2312 'class': 'className',
2316 _getAttr: function(element
, attribute
) {
2317 return element
.getAttribute(attribute
, 2);
2319 _getAttrNode: function(element
, attribute
) {
2320 var node
= element
.getAttributeNode(attribute
);
2321 return node
? node
.value
: "";
2323 _getEv: function(element
, attribute
) {
2324 attribute
= element
.getAttribute(attribute
);
2325 return attribute
? attribute
.toString().slice(23, -2) : null;
2327 _flag: function(element
, attribute
) {
2328 return $(element
).hasAttribute(attribute
) ? attribute
: null;
2330 style: function(element
) {
2331 return element
.style
.cssText
.toLowerCase();
2333 title: function(element
) {
2334 return element
.title
;
2340 Element
._attributeTranslations
.write
= {
2341 names
: Object
.extend({
2342 cellpadding
: 'cellPadding',
2343 cellspacing
: 'cellSpacing'
2344 }, Element
._attributeTranslations
.read
.names
),
2346 checked: function(element
, value
) {
2347 element
.checked
= !!value
;
2350 style: function(element
, value
) {
2351 element
.style
.cssText
= value
? value
: '';
2356 Element
._attributeTranslations
.has
= {};
2358 $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
2359 'encType maxLength readOnly longDesc frameBorder').each(function(attr
) {
2360 Element
._attributeTranslations
.write
.names
[attr
.toLowerCase()] = attr
;
2361 Element
._attributeTranslations
.has
[attr
.toLowerCase()] = attr
;
2369 action
: v
._getAttrNode
,
2377 ondblclick
: v
._getEv
,
2378 onmousedown
: v
._getEv
,
2379 onmouseup
: v
._getEv
,
2380 onmouseover
: v
._getEv
,
2381 onmousemove
: v
._getEv
,
2382 onmouseout
: v
._getEv
,
2385 onkeypress
: v
._getEv
,
2386 onkeydown
: v
._getEv
,
2393 })(Element
._attributeTranslations
.read
.values
);
2396 else if (Prototype
.Browser
.Gecko
&& /rv:1\.8\.0/.test(navigator
.userAgent
)) {
2397 Element
.Methods
.setOpacity = function(element
, value
) {
2398 element
= $(element
);
2399 element
.style
.opacity
= (value
== 1) ? 0.999999 :
2400 (value
=== '') ? '' : (value
< 0.00001) ? 0 : value
;
2405 else if (Prototype
.Browser
.WebKit
) {
2406 Element
.Methods
.setOpacity = function(element
, value
) {
2407 element
= $(element
);
2408 element
.style
.opacity
= (value
== 1 || value
=== '') ? '' :
2409 (value
< 0.00001) ? 0 : value
;
2412 if(element
.tagName
.toUpperCase() == 'IMG' && element
.width
) {
2413 element
.width
++; element
.width
--;
2415 var n
= document
.createTextNode(' ');
2416 element
.appendChild(n
);
2417 element
.removeChild(n
);
2423 // Safari returns margins on body which is incorrect if the child is absolutely
2424 // positioned. For performance reasons, redefine Element#cumulativeOffset for
2425 // KHTML/WebKit only.
2426 Element
.Methods
.cumulativeOffset = function(element
) {
2427 var valueT
= 0, valueL
= 0;
2429 valueT
+= element
.offsetTop
|| 0;
2430 valueL
+= element
.offsetLeft
|| 0;
2431 if (element
.offsetParent
== document
.body
)
2432 if (Element
.getStyle(element
, 'position') == 'absolute') break;
2434 element
= element
.offsetParent
;
2437 return Element
._returnOffset(valueL
, valueT
);
2441 if (Prototype
.Browser
.IE
|| Prototype
.Browser
.Opera
) {
2442 // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
2443 Element
.Methods
.update = function(element
, content
) {
2444 element
= $(element
);
2446 if (content
&& content
.toElement
) content
= content
.toElement();
2447 if (Object
.isElement(content
)) return element
.update().insert(content
);
2449 content
= Object
.toHTML(content
);
2450 var tagName
= element
.tagName
.toUpperCase();
2452 if (tagName
in Element
._insertionTranslations
.tags
) {
2453 $A(element
.childNodes
).each(function(node
) { element
.removeChild(node
) });
2454 Element
._getContentFromAnonymousElement(tagName
, content
.stripScripts())
2455 .each(function(node
) { element
.appendChild(node
) });
2457 else element
.innerHTML
= content
.stripScripts();
2459 content
.evalScripts
.bind(content
).defer();
2464 if ('outerHTML' in document
.createElement('div')) {
2465 Element
.Methods
.replace = function(element
, content
) {
2466 element
= $(element
);
2468 if (content
&& content
.toElement
) content
= content
.toElement();
2469 if (Object
.isElement(content
)) {
2470 element
.parentNode
.replaceChild(content
, element
);
2474 content
= Object
.toHTML(content
);
2475 var parent
= element
.parentNode
, tagName
= parent
.tagName
.toUpperCase();
2477 if (Element
._insertionTranslations
.tags
[tagName
]) {
2478 var nextSibling
= element
.next();
2479 var fragments
= Element
._getContentFromAnonymousElement(tagName
, content
.stripScripts());
2480 parent
.removeChild(element
);
2482 fragments
.each(function(node
) { parent
.insertBefore(node
, nextSibling
) });
2484 fragments
.each(function(node
) { parent
.appendChild(node
) });
2486 else element
.outerHTML
= content
.stripScripts();
2488 content
.evalScripts
.bind(content
).defer();
2493 Element
._returnOffset = function(l
, t
) {
2494 var result
= [l
, t
];
2500 Element
._getContentFromAnonymousElement = function(tagName
, html
) {
2501 var div
= new Element('div'), t
= Element
._insertionTranslations
.tags
[tagName
];
2503 div
.innerHTML
= t
[0] + html
+ t
[1];
2504 t
[2].times(function() { div
= div
.firstChild
});
2505 } else div
.innerHTML
= html
;
2506 return $A(div
.childNodes
);
2509 Element
._insertionTranslations
= {
2510 before: function(element
, node
) {
2511 element
.parentNode
.insertBefore(node
, element
);
2513 top: function(element
, node
) {
2514 element
.insertBefore(node
, element
.firstChild
);
2516 bottom: function(element
, node
) {
2517 element
.appendChild(node
);
2519 after: function(element
, node
) {
2520 element
.parentNode
.insertBefore(node
, element
.nextSibling
);
2523 TABLE
: ['<table>', '</table>', 1],
2524 TBODY
: ['<table><tbody>', '</tbody></table>', 2],
2525 TR
: ['<table><tbody><tr>', '</tr></tbody></table>', 3],
2526 TD
: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
2527 SELECT
: ['<select>', '</select>', 1]
2532 Object
.extend(this.tags
, {
2533 THEAD
: this.tags
.TBODY
,
2534 TFOOT
: this.tags
.TBODY
,
2537 }).call(Element
._insertionTranslations
);
2539 Element
.Methods
.Simulated
= {
2540 hasAttribute: function(element
, attribute
) {
2541 attribute
= Element
._attributeTranslations
.has
[attribute
] || attribute
;
2542 var node
= $(element
).getAttributeNode(attribute
);
2543 return !!(node
&& node
.specified
);
2547 Element
.Methods
.ByTag
= { };
2549 Object
.extend(Element
, Element
.Methods
);
2551 if (!Prototype
.BrowserFeatures
.ElementExtensions
&&
2552 document
.createElement('div')['__proto__']) {
2553 window
.HTMLElement
= { };
2554 window
.HTMLElement
.prototype = document
.createElement('div')['__proto__'];
2555 Prototype
.BrowserFeatures
.ElementExtensions
= true;
2558 Element
.extend
= (function() {
2559 if (Prototype
.BrowserFeatures
.SpecificElementExtensions
)
2562 var Methods
= { }, ByTag
= Element
.Methods
.ByTag
;
2564 var extend
= Object
.extend(function(element
) {
2565 if (!element
|| element
._extendedByPrototype
||
2566 element
.nodeType
!= 1 || element
== window
) return element
;
2568 var methods
= Object
.clone(Methods
),
2569 tagName
= element
.tagName
.toUpperCase(), property
, value
;
2571 // extend methods for specific tags
2572 if (ByTag
[tagName
]) Object
.extend(methods
, ByTag
[tagName
]);
2574 for (property
in methods
) {
2575 value
= methods
[property
];
2576 if (Object
.isFunction(value
) && !(property
in element
))
2577 element
[property
] = value
.methodize();
2580 element
._extendedByPrototype
= Prototype
.emptyFunction
;
2584 refresh: function() {
2585 // extend methods for all tags (Safari doesn't need this)
2586 if (!Prototype
.BrowserFeatures
.ElementExtensions
) {
2587 Object
.extend(Methods
, Element
.Methods
);
2588 Object
.extend(Methods
, Element
.Methods
.Simulated
);
2597 Element
.hasAttribute = function(element
, attribute
) {
2598 if (element
.hasAttribute
) return element
.hasAttribute(attribute
);
2599 return Element
.Methods
.Simulated
.hasAttribute(element
, attribute
);
2602 Element
.addMethods = function(methods
) {
2603 var F
= Prototype
.BrowserFeatures
, T
= Element
.Methods
.ByTag
;
2606 Object
.extend(Form
, Form
.Methods
);
2607 Object
.extend(Form
.Element
, Form
.Element
.Methods
);
2608 Object
.extend(Element
.Methods
.ByTag
, {
2609 "FORM": Object
.clone(Form
.Methods
),
2610 "INPUT": Object
.clone(Form
.Element
.Methods
),
2611 "SELECT": Object
.clone(Form
.Element
.Methods
),
2612 "TEXTAREA": Object
.clone(Form
.Element
.Methods
)
2616 if (arguments
.length
== 2) {
2617 var tagName
= methods
;
2618 methods
= arguments
[1];
2621 if (!tagName
) Object
.extend(Element
.Methods
, methods
|| { });
2623 if (Object
.isArray(tagName
)) tagName
.each(extend
);
2624 else extend(tagName
);
2627 function extend(tagName
) {
2628 tagName
= tagName
.toUpperCase();
2629 if (!Element
.Methods
.ByTag
[tagName
])
2630 Element
.Methods
.ByTag
[tagName
] = { };
2631 Object
.extend(Element
.Methods
.ByTag
[tagName
], methods
);
2634 function copy(methods
, destination
, onlyIfAbsent
) {
2635 onlyIfAbsent
= onlyIfAbsent
|| false;
2636 for (var property
in methods
) {
2637 var value
= methods
[property
];
2638 if (!Object
.isFunction(value
)) continue;
2639 if (!onlyIfAbsent
|| !(property
in destination
))
2640 destination
[property
] = value
.methodize();
2644 function findDOMClass(tagName
) {
2647 "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
2648 "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
2649 "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
2650 "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
2651 "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
2652 "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
2653 "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
2654 "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
2655 "FrameSet", "IFRAME": "IFrame"
2657 if (trans
[tagName
]) klass
= 'HTML' + trans
[tagName
] + 'Element';
2658 if (window
[klass
]) return window
[klass
];
2659 klass
= 'HTML' + tagName
+ 'Element';
2660 if (window
[klass
]) return window
[klass
];
2661 klass
= 'HTML' + tagName
.capitalize() + 'Element';
2662 if (window
[klass
]) return window
[klass
];
2664 window
[klass
] = { };
2665 window
[klass
].prototype = document
.createElement(tagName
)['__proto__'];
2666 return window
[klass
];
2669 if (F
.ElementExtensions
) {
2670 copy(Element
.Methods
, HTMLElement
.prototype);
2671 copy(Element
.Methods
.Simulated
, HTMLElement
.prototype, true);
2674 if (F
.SpecificElementExtensions
) {
2675 for (var tag
in Element
.Methods
.ByTag
) {
2676 var klass
= findDOMClass(tag
);
2677 if (Object
.isUndefined(klass
)) continue;
2678 copy(T
[tag
], klass
.prototype);
2682 Object
.extend(Element
, Element
.Methods
);
2683 delete Element
.ByTag
;
2685 if (Element
.extend
.refresh
) Element
.extend
.refresh();
2686 Element
.cache
= { };
2689 document
.viewport
= {
2690 getDimensions: function() {
2691 var dimensions
= { }, B
= Prototype
.Browser
;
2692 $w('width height').each(function(d
) {
2693 var D
= d
.capitalize();
2694 if (B
.WebKit
&& !document
.evaluate
) {
2695 // Safari <3.0 needs self.innerWidth/Height
2696 dimensions
[d
] = self
['inner' + D
];
2697 } else if (B
.Opera
&& parseFloat(window
.opera
.version()) < 9.5) {
2698 // Opera <9.5 needs document.body.clientWidth/Height
2699 dimensions
[d
] = document
.body
['client' + D
]
2701 dimensions
[d
] = document
.documentElement
['client' + D
];
2707 getWidth: function() {
2708 return this.getDimensions().width
;
2711 getHeight: function() {
2712 return this.getDimensions().height
;
2715 getScrollOffsets: function() {
2716 return Element
._returnOffset(
2717 window
.pageXOffset
|| document
.documentElement
.scrollLeft
|| document
.body
.scrollLeft
,
2718 window
.pageYOffset
|| document
.documentElement
.scrollTop
|| document
.body
.scrollTop
);
2721 /* Portions of the Selector class are derived from Jack Slocum's DomQuery,
2722 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
2723 * license. Please see http://www.yui-ext.com/ for more information. */
2725 var Selector
= Class
.create({
2726 initialize: function(expression
) {
2727 this.expression
= expression
.strip();
2729 if (this.shouldUseSelectorsAPI()) {
2730 this.mode
= 'selectorsAPI';
2731 } else if (this.shouldUseXPath()) {
2732 this.mode
= 'xpath';
2733 this.compileXPathMatcher();
2735 this.mode
= "normal";
2736 this.compileMatcher();
2741 shouldUseXPath: function() {
2742 if (!Prototype
.BrowserFeatures
.XPath
) return false;
2744 var e
= this.expression
;
2746 // Safari 3 chokes on :*-of-type and :empty
2747 if (Prototype
.Browser
.WebKit
&&
2748 (e
.include("-of-type") || e
.include(":empty")))
2751 // XPath can't do namespaced attributes, nor can it read
2752 // the "checked" property from DOM nodes
2753 if ((/(\[[\w-]*?:|:checked)/).test(e
))
2759 shouldUseSelectorsAPI: function() {
2760 if (!Prototype
.BrowserFeatures
.SelectorsAPI
) return false;
2762 if (!Selector
._div
) Selector
._div
= new Element('div');
2764 // Make sure the browser treats the selector as valid. Test on an
2765 // isolated element to minimize cost of this check.
2767 Selector
._div
.querySelector(this.expression
);
2775 compileMatcher: function() {
2776 var e
= this.expression
, ps
= Selector
.patterns
, h
= Selector
.handlers
,
2777 c
= Selector
.criteria
, le
, p
, m
;
2779 if (Selector
._cache
[e
]) {
2780 this.matcher
= Selector
._cache
[e
];
2784 this.matcher
= ["this.matcher = function(root) {",
2785 "var r = root, h = Selector.handlers, c = false, n;"];
2787 while (e
&& le
!= e
&& (/\S/).test(e
)) {
2791 if (m
= e
.match(p
)) {
2792 this.matcher
.push(Object
.isFunction(c
[i
]) ? c
[i
](m
) :
2793 new Template(c
[i
]).evaluate(m
));
2794 e
= e
.replace(m
[0], '');
2800 this.matcher
.push("return h.unique(n);\n}");
2801 eval(this.matcher
.join('\n'));
2802 Selector
._cache
[this.expression
] = this.matcher
;
2805 compileXPathMatcher: function() {
2806 var e
= this.expression
, ps
= Selector
.patterns
,
2807 x
= Selector
.xpath
, le
, m
;
2809 if (Selector
._cache
[e
]) {
2810 this.xpath
= Selector
._cache
[e
]; return;
2813 this.matcher
= ['.//*'];
2814 while (e
&& le
!= e
&& (/\S/).test(e
)) {
2817 if (m
= e
.match(ps
[i
])) {
2818 this.matcher
.push(Object
.isFunction(x
[i
]) ? x
[i
](m
) :
2819 new Template(x
[i
]).evaluate(m
));
2820 e
= e
.replace(m
[0], '');
2826 this.xpath
= this.matcher
.join('');
2827 Selector
._cache
[this.expression
] = this.xpath
;
2830 findElements: function(root
) {
2831 root
= root
|| document
;
2832 var e
= this.expression
, results
;
2834 switch (this.mode
) {
2835 case 'selectorsAPI':
2836 // querySelectorAll queries document-wide, then filters to descendants
2837 // of the context element. That's not what we want.
2838 // Add an explicit context to the selector if necessary.
2839 if (root
!== document
) {
2840 var oldId
= root
.id
, id
= $(root
).identify();
2841 e
= "#" + id
+ " " + e
;
2844 results
= $A(root
.querySelectorAll(e
)).map(Element
.extend
);
2849 return document
._getElementsByXPath(this.xpath
, root
);
2851 return this.matcher(root
);
2855 match: function(element
) {
2858 var e
= this.expression
, ps
= Selector
.patterns
, as
= Selector
.assertions
;
2861 while (e
&& le
!== e
&& (/\S/).test(e
)) {
2865 if (m
= e
.match(p
)) {
2866 // use the Selector.assertions methods unless the selector
2869 this.tokens
.push([i
, Object
.clone(m
)]);
2870 e
= e
.replace(m
[0], '');
2872 // reluctantly do a document-wide search
2873 // and look for a match in the array
2874 return this.findElements(document
).include(element
);
2880 var match
= true, name
, matches
;
2881 for (var i
= 0, token
; token
= this.tokens
[i
]; i
++) {
2882 name
= token
[0], matches
= token
[1];
2883 if (!Selector
.assertions
[name
](element
, matches
)) {
2884 match
= false; break;
2891 toString: function() {
2892 return this.expression
;
2895 inspect: function() {
2896 return "#<Selector:" + this.expression
.inspect() + ">";
2900 Object
.extend(Selector
, {
2906 adjacent
: "/following-sibling::*[1]",
2907 laterSibling
: '/following-sibling::*',
2908 tagName: function(m
) {
2909 if (m
[1] == '*') return '';
2910 return "[local-name()='" + m
[1].toLowerCase() +
2911 "' or local-name()='" + m
[1].toUpperCase() + "']";
2913 className
: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
2915 attrPresence: function(m
) {
2916 m
[1] = m
[1].toLowerCase();
2917 return new Template("[@#{1}]").evaluate(m
);
2920 m
[1] = m
[1].toLowerCase();
2921 m
[3] = m
[5] || m
[6];
2922 return new Template(Selector
.xpath
.operators
[m
[2]]).evaluate(m
);
2924 pseudo: function(m
) {
2925 var h
= Selector
.xpath
.pseudos
[m
[1]];
2927 if (Object
.isFunction(h
)) return h(m
);
2928 return new Template(Selector
.xpath
.pseudos
[m
[1]]).evaluate(m
);
2931 '=': "[@#{1}='#{3}']",
2932 '!=': "[@#{1}!='#{3}']",
2933 '^=': "[starts-with(@#{1}, '#{3}')]",
2934 '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
2935 '*=': "[contains(@#{1}, '#{3}')]",
2936 '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
2937 '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
2940 'first-child': '[not(preceding-sibling::*)]',
2941 'last-child': '[not(following-sibling::*)]',
2942 'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
2943 'empty': "[count(*) = 0 and (count(text()) = 0)]",
2944 'checked': "[@checked]",
2945 'disabled': "[(@disabled) and (@type!='hidden')]",
2946 'enabled': "[not(@disabled) and (@type!='hidden')]",
2947 'not': function(m
) {
2948 var e
= m
[6], p
= Selector
.patterns
,
2949 x
= Selector
.xpath
, le
, v
;
2952 while (e
&& le
!= e
&& (/\S/).test(e
)) {
2955 if (m
= e
.match(p
[i
])) {
2956 v
= Object
.isFunction(x
[i
]) ? x
[i
](m
) : new Template(x
[i
]).evaluate(m
);
2957 exclusion
.push("(" + v
.substring(1, v
.length
- 1) + ")");
2958 e
= e
.replace(m
[0], '');
2963 return "[not(" + exclusion
.join(" and ") + ")]";
2965 'nth-child': function(m
) {
2966 return Selector
.xpath
.pseudos
.nth("(count(./preceding-sibling::*) + 1) ", m
);
2968 'nth-last-child': function(m
) {
2969 return Selector
.xpath
.pseudos
.nth("(count(./following-sibling::*) + 1) ", m
);
2971 'nth-of-type': function(m
) {
2972 return Selector
.xpath
.pseudos
.nth("position() ", m
);
2974 'nth-last-of-type': function(m
) {
2975 return Selector
.xpath
.pseudos
.nth("(last() + 1 - position()) ", m
);
2977 'first-of-type': function(m
) {
2978 m
[6] = "1"; return Selector
.xpath
.pseudos
['nth-of-type'](m
);
2980 'last-of-type': function(m
) {
2981 m
[6] = "1"; return Selector
.xpath
.pseudos
['nth-last-of-type'](m
);
2983 'only-of-type': function(m
) {
2984 var p
= Selector
.xpath
.pseudos
; return p
['first-of-type'](m
) + p
['last-of-type'](m
);
2986 nth: function(fragment
, m
) {
2987 var mm
, formula
= m
[6], predicate
;
2988 if (formula
== 'even') formula
= '2n+0';
2989 if (formula
== 'odd') formula
= '2n+1';
2990 if (mm
= formula
.match(/^(\d+)$/)) // digit only
2991 return '[' + fragment
+ "= " + mm
[1] + ']';
2992 if (mm
= formula
.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
2993 if (mm
[1] == "-") mm
[1] = -1;
2994 var a
= mm
[1] ? Number(mm
[1]) : 1;
2995 var b
= mm
[2] ? Number(mm
[2]) : 0;
2996 predicate
= "[((#{fragment} - #{b}) mod #{a} = 0) and " +
2997 "((#{fragment} - #{b}) div #{a} >= 0)]";
2998 return new Template(predicate
).evaluate({
2999 fragment
: fragment
, a
: a
, b
: b
});
3006 tagName
: 'n = h.tagName(n, r, "#{1}", c); c = false;',
3007 className
: 'n = h.className(n, r, "#{1}", c); c = false;',
3008 id
: 'n = h.id(n, r, "#{1}", c); c = false;',
3009 attrPresence
: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
3011 m
[3] = (m
[5] || m
[6]);
3012 return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m
);
3014 pseudo: function(m
) {
3015 if (m
[6]) m
[6] = m
[6].replace(/"/g, '\\"');
3016 return new Template('n
= h
.pseudo(n
, "#{1}", "#{6}", r
, c
); c
= false;').evaluate(m);
3018 descendant: 'c
= "descendant";',
3019 child: 'c
= "child";',
3020 adjacent: 'c
= "adjacent";',
3021 laterSibling: 'c
= "laterSibling";'
3025 // combinators must be listed first
3026 // (and descendant needs to be last combinator)
3027 laterSibling: /^\s*~\s*/,
3029 adjacent: /^\s*\+\s*/,
3033 tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
3034 id: /^#([\w\-\*]+)(\b|$)/,
3035 className: /^\.([\w\-\*]+)(\b|$)/,
3037 /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
3038 attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/,
3039 attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
3042 // for Selector.match and Element#match
3044 tagName: function(element
, matches
) {
3045 return matches
[1].toUpperCase() == element
.tagName
.toUpperCase();
3048 className: function(element
, matches
) {
3049 return Element
.hasClassName(element
, matches
[1]);
3052 id: function(element
, matches
) {
3053 return element
.id
=== matches
[1];
3056 attrPresence: function(element
, matches
) {
3057 return Element
.hasAttribute(element
, matches
[1]);
3060 attr: function(element
, matches
) {
3061 var nodeValue
= Element
.readAttribute(element
, matches
[1]);
3062 return nodeValue
&& Selector
.operators
[matches
[2]](nodeValue
, matches
[5] || matches
[6]);
3067 // UTILITY FUNCTIONS
3068 // joins two collections
3069 concat: function(a
, b
) {
3070 for (var i
= 0, node
; node
= b
[i
]; i
++)
3075 // marks an array of nodes for counting
3076 mark: function(nodes
) {
3077 var _true
= Prototype
.emptyFunction
;
3078 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3079 node
._countedByPrototype
= _true
;
3083 unmark: function(nodes
) {
3084 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3085 node
._countedByPrototype
= undefined;
3089 // mark each child node with its position (for nth calls)
3090 // "ofType" flag indicates whether we're indexing for nth-of-type
3091 // rather than nth-child
3092 index: function(parentNode
, reverse
, ofType
) {
3093 parentNode
._countedByPrototype
= Prototype
.emptyFunction
;
3095 for (var nodes
= parentNode
.childNodes
, i
= nodes
.length
- 1, j
= 1; i
>= 0; i
--) {
3096 var node
= nodes
[i
];
3097 if (node
.nodeType
== 1 && (!ofType
|| node
._countedByPrototype
)) node
.nodeIndex
= j
++;
3100 for (var i
= 0, j
= 1, nodes
= parentNode
.childNodes
; node
= nodes
[i
]; i
++)
3101 if (node
.nodeType
== 1 && (!ofType
|| node
._countedByPrototype
)) node
.nodeIndex
= j
++;
3105 // filters out duplicates and extends all nodes
3106 unique: function(nodes
) {
3107 if (nodes
.length
== 0) return nodes
;
3108 var results
= [], n
;
3109 for (var i
= 0, l
= nodes
.length
; i
< l
; i
++)
3110 if (!(n
= nodes
[i
])._countedByPrototype
) {
3111 n
._countedByPrototype
= Prototype
.emptyFunction
;
3112 results
.push(Element
.extend(n
));
3114 return Selector
.handlers
.unmark(results
);
3117 // COMBINATOR FUNCTIONS
3118 descendant: function(nodes
) {
3119 var h
= Selector
.handlers
;
3120 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3121 h
.concat(results
, node
.getElementsByTagName('*'));
3125 child: function(nodes
) {
3126 var h
= Selector
.handlers
;
3127 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++) {
3128 for (var j
= 0, child
; child
= node
.childNodes
[j
]; j
++)
3129 if (child
.nodeType
== 1 && child
.tagName
!= '!') results
.push(child
);
3134 adjacent: function(nodes
) {
3135 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++) {
3136 var next
= this.nextElementSibling(node
);
3137 if (next
) results
.push(next
);
3142 laterSibling: function(nodes
) {
3143 var h
= Selector
.handlers
;
3144 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3145 h
.concat(results
, Element
.nextSiblings(node
));
3149 nextElementSibling: function(node
) {
3150 while (node
= node
.nextSibling
)
3151 if (node
.nodeType
== 1) return node
;
3155 previousElementSibling: function(node
) {
3156 while (node
= node
.previousSibling
)
3157 if (node
.nodeType
== 1) return node
;
3162 tagName: function(nodes
, root
, tagName
, combinator
) {
3163 var uTagName
= tagName
.toUpperCase();
3164 var results
= [], h
= Selector
.handlers
;
3167 // fastlane for ordinary descendant combinators
3168 if (combinator
== "descendant") {
3169 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3170 h
.concat(results
, node
.getElementsByTagName(tagName
));
3172 } else nodes
= this[combinator
](nodes
);
3173 if (tagName
== "*") return nodes
;
3175 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3176 if (node
.tagName
.toUpperCase() === uTagName
) results
.push(node
);
3178 } else return root
.getElementsByTagName(tagName
);
3181 id: function(nodes
, root
, id
, combinator
) {
3182 var targetNode
= $(id
), h
= Selector
.handlers
;
3183 if (!targetNode
) return [];
3184 if (!nodes
&& root
== document
) return [targetNode
];
3187 if (combinator
== 'child') {
3188 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3189 if (targetNode
.parentNode
== node
) return [targetNode
];
3190 } else if (combinator
== 'descendant') {
3191 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3192 if (Element
.descendantOf(targetNode
, node
)) return [targetNode
];
3193 } else if (combinator
== 'adjacent') {
3194 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3195 if (Selector
.handlers
.previousElementSibling(targetNode
) == node
)
3196 return [targetNode
];
3197 } else nodes
= h
[combinator
](nodes
);
3199 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3200 if (node
== targetNode
) return [targetNode
];
3203 return (targetNode
&& Element
.descendantOf(targetNode
, root
)) ? [targetNode
] : [];
3206 className: function(nodes
, root
, className
, combinator
) {
3207 if (nodes
&& combinator
) nodes
= this[combinator
](nodes
);
3208 return Selector
.handlers
.byClassName(nodes
, root
, className
);
3211 byClassName: function(nodes
, root
, className
) {
3212 if (!nodes
) nodes
= Selector
.handlers
.descendant([root
]);
3213 var needle
= ' ' + className
+ ' ';
3214 for (var i
= 0, results
= [], node
, nodeClassName
; node
= nodes
[i
]; i
++) {
3215 nodeClassName
= node
.className
;
3216 if (nodeClassName
.length
== 0) continue;
3217 if (nodeClassName
== className
|| (' ' + nodeClassName
+ ' ').include(needle
))
3223 attrPresence: function(nodes
, root
, attr
, combinator
) {
3224 if (!nodes
) nodes
= root
.getElementsByTagName("*");
3225 if (nodes
&& combinator
) nodes
= this[combinator
](nodes
);
3227 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3228 if (Element
.hasAttribute(node
, attr
)) results
.push(node
);
3232 attr: function(nodes
, root
, attr
, value
, operator
, combinator
) {
3233 if (!nodes
) nodes
= root
.getElementsByTagName("*");
3234 if (nodes
&& combinator
) nodes
= this[combinator
](nodes
);
3235 var handler
= Selector
.operators
[operator
], results
= [];
3236 for (var i
= 0, node
; node
= nodes
[i
]; i
++) {
3237 var nodeValue
= Element
.readAttribute(node
, attr
);
3238 if (nodeValue
=== null) continue;
3239 if (handler(nodeValue
, value
)) results
.push(node
);
3244 pseudo: function(nodes
, name
, value
, root
, combinator
) {
3245 if (nodes
&& combinator
) nodes
= this[combinator
](nodes
);
3246 if (!nodes
) nodes
= root
.getElementsByTagName("*");
3247 return Selector
.pseudos
[name
](nodes
, value
, root
);
3252 'first-child': function(nodes
, value
, root
) {
3253 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++) {
3254 if (Selector
.handlers
.previousElementSibling(node
)) continue;
3259 'last-child': function(nodes
, value
, root
) {
3260 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++) {
3261 if (Selector
.handlers
.nextElementSibling(node
)) continue;
3266 'only-child': function(nodes
, value
, root
) {
3267 var h
= Selector
.handlers
;
3268 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3269 if (!h
.previousElementSibling(node
) && !h
.nextElementSibling(node
))
3273 'nth-child': function(nodes
, formula
, root
) {
3274 return Selector
.pseudos
.nth(nodes
, formula
, root
);
3276 'nth-last-child': function(nodes
, formula
, root
) {
3277 return Selector
.pseudos
.nth(nodes
, formula
, root
, true);
3279 'nth-of-type': function(nodes
, formula
, root
) {
3280 return Selector
.pseudos
.nth(nodes
, formula
, root
, false, true);
3282 'nth-last-of-type': function(nodes
, formula
, root
) {
3283 return Selector
.pseudos
.nth(nodes
, formula
, root
, true, true);
3285 'first-of-type': function(nodes
, formula
, root
) {
3286 return Selector
.pseudos
.nth(nodes
, "1", root
, false, true);
3288 'last-of-type': function(nodes
, formula
, root
) {
3289 return Selector
.pseudos
.nth(nodes
, "1", root
, true, true);
3291 'only-of-type': function(nodes
, formula
, root
) {
3292 var p
= Selector
.pseudos
;
3293 return p
['last-of-type'](p
['first-of-type'](nodes
, formula
, root
), formula
, root
);
3296 // handles the an+b logic
3297 getIndices: function(a
, b
, total
) {
3298 if (a
== 0) return b
> 0 ? [b
] : [];
3299 return $R(1, total
).inject([], function(memo
, i
) {
3300 if (0 == (i
- b
) % a
&& (i
- b
) / a
>= 0) memo
.push(i
);
3305 // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
3306 nth: function(nodes
, formula
, root
, reverse
, ofType
) {
3307 if (nodes
.length
== 0) return [];
3308 if (formula
== 'even') formula
= '2n+0';
3309 if (formula
== 'odd') formula
= '2n+1';
3310 var h
= Selector
.handlers
, results
= [], indexed
= [], m
;
3312 for (var i
= 0, node
; node
= nodes
[i
]; i
++) {
3313 if (!node
.parentNode
._countedByPrototype
) {
3314 h
.index(node
.parentNode
, reverse
, ofType
);
3315 indexed
.push(node
.parentNode
);
3318 if (formula
.match(/^\d+$/)) { // just a number
3319 formula
= Number(formula
);
3320 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3321 if (node
.nodeIndex
== formula
) results
.push(node
);
3322 } else if (m
= formula
.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
3323 if (m
[1] == "-") m
[1] = -1;
3324 var a
= m
[1] ? Number(m
[1]) : 1;
3325 var b
= m
[2] ? Number(m
[2]) : 0;
3326 var indices
= Selector
.pseudos
.getIndices(a
, b
, nodes
.length
);
3327 for (var i
= 0, node
, l
= indices
.length
; node
= nodes
[i
]; i
++) {
3328 for (var j
= 0; j
< l
; j
++)
3329 if (node
.nodeIndex
== indices
[j
]) results
.push(node
);
3337 'empty': function(nodes
, value
, root
) {
3338 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++) {
3339 // IE treats comments as element nodes
3340 if (node
.tagName
== '!' || node
.firstChild
) continue;
3346 'not': function(nodes
, selector
, root
) {
3347 var h
= Selector
.handlers
, selectorType
, m
;
3348 var exclusions
= new Selector(selector
).findElements(root
);
3350 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3351 if (!node
._countedByPrototype
) results
.push(node
);
3352 h
.unmark(exclusions
);
3356 'enabled': function(nodes
, value
, root
) {
3357 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3358 if (!node
.disabled
&& (!node
.type
|| node
.type
!== 'hidden'))
3363 'disabled': function(nodes
, value
, root
) {
3364 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3365 if (node
.disabled
) results
.push(node
);
3369 'checked': function(nodes
, value
, root
) {
3370 for (var i
= 0, results
= [], node
; node
= nodes
[i
]; i
++)
3371 if (node
.checked
) results
.push(node
);
3377 '=': function(nv
, v
) { return nv
== v
; },
3378 '!=': function(nv
, v
) { return nv
!= v
; },
3379 '^=': function(nv
, v
) { return nv
== v
|| nv
&& nv
.startsWith(v
); },
3380 '$=': function(nv
, v
) { return nv
== v
|| nv
&& nv
.endsWith(v
); },
3381 '*=': function(nv
, v
) { return nv
== v
|| nv
&& nv
.include(v
); },
3382 '$=': function(nv
, v
) { return nv
.endsWith(v
); },
3383 '*=': function(nv
, v
) { return nv
.include(v
); },
3384 '~=': function(nv
, v
) { return (' ' + nv
+ ' ').include(' ' + v
+ ' '); },
3385 '|=': function(nv
, v
) { return ('-' + (nv
|| "").toUpperCase() +
3386 '-').include('-' + (v
|| "").toUpperCase() + '-'); }
3389 split: function(expression
) {
3390 var expressions
= [];
3391 expression
.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m
) {
3392 expressions
.push(m
[1].strip());
3397 matchElements: function(elements
, expression
) {
3398 var matches
= $$(expression
), h
= Selector
.handlers
;
3400 for (var i
= 0, results
= [], element
; element
= elements
[i
]; i
++)
3401 if (element
._countedByPrototype
) results
.push(element
);
3406 findElement: function(elements
, expression
, index
) {
3407 if (Object
.isNumber(expression
)) {
3408 index
= expression
; expression
= false;
3410 return Selector
.matchElements(elements
, expression
|| '*')[index
|| 0];
3413 findChildElements: function(element
, expressions
) {
3414 expressions
= Selector
.split(expressions
.join(','));
3415 var results
= [], h
= Selector
.handlers
;
3416 for (var i
= 0, l
= expressions
.length
, selector
; i
< l
; i
++) {
3417 selector
= new Selector(expressions
[i
].strip());
3418 h
.concat(results
, selector
.findElements(element
));
3420 return (l
> 1) ? h
.unique(results
) : results
;
3424 if (Prototype
.Browser
.IE
) {
3425 Object
.extend(Selector
.handlers
, {
3426 // IE returns comment nodes on getElementsByTagName("*").
3428 concat: function(a
, b
) {
3429 for (var i
= 0, node
; node
= b
[i
]; i
++)
3430 if (node
.tagName
!== "!") a
.push(node
);
3434 // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
3435 unmark: function(nodes
) {
3436 for (var i
= 0, node
; node
= nodes
[i
]; i
++)
3437 node
.removeAttribute('_countedByPrototype');
3444 return Selector
.findChildElements(document
, $A(arguments
));
3447 reset: function(form
) {
3452 serializeElements: function(elements
, options
) {
3453 if (typeof options
!= 'object') options
= { hash
: !!options
};
3454 else if (Object
.isUndefined(options
.hash
)) options
.hash
= true;
3455 var key
, value
, submitted
= false, submit
= options
.submit
;
3457 var data
= elements
.inject({ }, function(result
, element
) {
3458 if (!element
.disabled
&& element
.name
) {
3459 key
= element
.name
; value
= $(element
).getValue();
3460 if (value
!= null && element
.type
!= 'file' && (element
.type
!= 'submit' || (!submitted
&&
3461 submit
!== false && (!submit
|| key
== submit
) && (submitted
= true)))) {
3462 if (key
in result
) {
3463 // a key is already present; construct an array of values
3464 if (!Object
.isArray(result
[key
])) result
[key
] = [result
[key
]];
3465 result
[key
].push(value
);
3467 else result
[key
] = value
;
3473 return options
.hash
? data
: Object
.toQueryString(data
);
3478 serialize: function(form
, options
) {
3479 return Form
.serializeElements(Form
.getElements(form
), options
);
3482 getElements: function(form
) {
3483 return $A($(form
).getElementsByTagName('*')).inject([],
3484 function(elements
, child
) {
3485 if (Form
.Element
.Serializers
[child
.tagName
.toLowerCase()])
3486 elements
.push(Element
.extend(child
));
3492 getInputs: function(form
, typeName
, name
) {
3494 var inputs
= form
.getElementsByTagName('input');
3496 if (!typeName
&& !name
) return $A(inputs
).map(Element
.extend
);
3498 for (var i
= 0, matchingInputs
= [], length
= inputs
.length
; i
< length
; i
++) {
3499 var input
= inputs
[i
];
3500 if ((typeName
&& input
.type
!= typeName
) || (name
&& input
.name
!= name
))
3502 matchingInputs
.push(Element
.extend(input
));
3505 return matchingInputs
;
3508 disable: function(form
) {
3510 Form
.getElements(form
).invoke('disable');
3514 enable: function(form
) {
3516 Form
.getElements(form
).invoke('enable');
3520 findFirstElement: function(form
) {
3521 var elements
= $(form
).getElements().findAll(function(element
) {
3522 return 'hidden' != element
.type
&& !element
.disabled
;
3524 var firstByIndex
= elements
.findAll(function(element
) {
3525 return element
.hasAttribute('tabIndex') && element
.tabIndex
>= 0;
3526 }).sortBy(function(element
) { return element
.tabIndex
}).first();
3528 return firstByIndex
? firstByIndex
: elements
.find(function(element
) {
3529 return ['input', 'select', 'textarea'].include(element
.tagName
.toLowerCase());
3533 focusFirstElement: function(form
) {
3535 form
.findFirstElement().activate();
3539 request: function(form
, options
) {
3540 form
= $(form
), options
= Object
.clone(options
|| { });
3542 var params
= options
.parameters
, action
= form
.readAttribute('action') || '';
3543 if (action
.blank()) action
= window
.location
.href
;
3544 options
.parameters
= form
.serialize(true);
3547 if (Object
.isString(params
)) params
= params
.toQueryParams();
3548 Object
.extend(options
.parameters
, params
);
3551 if (form
.hasAttribute('method') && !options
.method
)
3552 options
.method
= form
.method
;
3554 return new Ajax
.Request(action
, options
);
3558 /*--------------------------------------------------------------------------*/
3561 focus: function(element
) {
3566 select: function(element
) {
3567 $(element
).select();
3572 Form
.Element
.Methods
= {
3573 serialize: function(element
) {
3574 element
= $(element
);
3575 if (!element
.disabled
&& element
.name
) {
3576 var value
= element
.getValue();
3577 if (value
!= undefined) {
3579 pair
[element
.name
] = value
;
3580 return Object
.toQueryString(pair
);
3586 getValue: function(element
) {
3587 element
= $(element
);
3588 var method
= element
.tagName
.toLowerCase();
3589 return Form
.Element
.Serializers
[method
](element
);
3592 setValue: function(element
, value
) {
3593 element
= $(element
);
3594 var method
= element
.tagName
.toLowerCase();
3595 Form
.Element
.Serializers
[method
](element
, value
);
3599 clear: function(element
) {
3600 $(element
).value
= '';
3604 present: function(element
) {
3605 return $(element
).value
!= '';
3608 activate: function(element
) {
3609 element
= $(element
);
3612 if (element
.select
&& (element
.tagName
.toLowerCase() != 'input' ||
3613 !['button', 'reset', 'submit'].include(element
.type
)))
3619 disable: function(element
) {
3620 element
= $(element
);
3621 element
.disabled
= true;
3625 enable: function(element
) {
3626 element
= $(element
);
3627 element
.disabled
= false;
3632 /*--------------------------------------------------------------------------*/
3634 var Field
= Form
.Element
;
3635 var $F
= Form
.Element
.Methods
.getValue
;
3637 /*--------------------------------------------------------------------------*/
3639 Form
.Element
.Serializers
= {
3640 input: function(element
, value
) {
3641 switch (element
.type
.toLowerCase()) {
3644 return Form
.Element
.Serializers
.inputSelector(element
, value
);
3646 return Form
.Element
.Serializers
.textarea(element
, value
);
3650 inputSelector: function(element
, value
) {
3651 if (Object
.isUndefined(value
)) return element
.checked
? element
.value
: null;
3652 else element
.checked
= !!value
;
3655 textarea: function(element
, value
) {
3656 if (Object
.isUndefined(value
)) return element
.value
;
3657 else element
.value
= value
;
3660 select: function(element
, value
) {
3661 if (Object
.isUndefined(value
))
3662 return this[element
.type
== 'select-one' ?
3663 'selectOne' : 'selectMany'](element
);
3665 var opt
, currentValue
, single
= !Object
.isArray(value
);
3666 for (var i
= 0, length
= element
.length
; i
< length
; i
++) {
3667 opt
= element
.options
[i
];
3668 currentValue
= this.optionValue(opt
);
3670 if (currentValue
== value
) {
3671 opt
.selected
= true;
3675 else opt
.selected
= value
.include(currentValue
);
3680 selectOne: function(element
) {
3681 var index
= element
.selectedIndex
;
3682 return index
>= 0 ? this.optionValue(element
.options
[index
]) : null;
3685 selectMany: function(element
) {
3686 var values
, length
= element
.length
;
3687 if (!length
) return null;
3689 for (var i
= 0, values
= []; i
< length
; i
++) {
3690 var opt
= element
.options
[i
];
3691 if (opt
.selected
) values
.push(this.optionValue(opt
));
3696 optionValue: function(opt
) {
3697 // extend element because hasAttribute may not be native
3698 return Element
.extend(opt
).hasAttribute('value') ? opt
.value
: opt
.text
;
3702 /*--------------------------------------------------------------------------*/
3704 Abstract
.TimedObserver
= Class
.create(PeriodicalExecuter
, {
3705 initialize: function($super, element
, frequency
, callback
) {
3706 $super(callback
, frequency
);
3707 this.element
= $(element
);
3708 this.lastValue
= this.getValue();
3711 execute: function() {
3712 var value
= this.getValue();
3713 if (Object
.isString(this.lastValue
) && Object
.isString(value
) ?
3714 this.lastValue
!= value
: String(this.lastValue
) != String(value
)) {
3715 this.callback(this.element
, value
);
3716 this.lastValue
= value
;
3721 Form
.Element
.Observer
= Class
.create(Abstract
.TimedObserver
, {
3722 getValue: function() {
3723 return Form
.Element
.getValue(this.element
);
3727 Form
.Observer
= Class
.create(Abstract
.TimedObserver
, {
3728 getValue: function() {
3729 return Form
.serialize(this.element
);
3733 /*--------------------------------------------------------------------------*/
3735 Abstract
.EventObserver
= Class
.create({
3736 initialize: function(element
, callback
) {
3737 this.element
= $(element
);
3738 this.callback
= callback
;
3740 this.lastValue
= this.getValue();
3741 if (this.element
.tagName
.toLowerCase() == 'form')
3742 this.registerFormCallbacks();
3744 this.registerCallback(this.element
);
3747 onElementEvent: function() {
3748 var value
= this.getValue();
3749 if (this.lastValue
!= value
) {
3750 this.callback(this.element
, value
);
3751 this.lastValue
= value
;
3755 registerFormCallbacks: function() {
3756 Form
.getElements(this.element
).each(this.registerCallback
, this);
3759 registerCallback: function(element
) {
3761 switch (element
.type
.toLowerCase()) {
3764 Event
.observe(element
, 'click', this.onElementEvent
.bind(this));
3767 Event
.observe(element
, 'change', this.onElementEvent
.bind(this));
3774 Form
.Element
.EventObserver
= Class
.create(Abstract
.EventObserver
, {
3775 getValue: function() {
3776 return Form
.Element
.getValue(this.element
);
3780 Form
.EventObserver
= Class
.create(Abstract
.EventObserver
, {
3781 getValue: function() {
3782 return Form
.serialize(this.element
);
3785 if (!window
.Event
) var Event
= { };
3787 Object
.extend(Event
, {
3805 relatedTarget: function(event
) {
3807 switch(event
.type
) {
3808 case 'mouseover': element
= event
.fromElement
; break;
3809 case 'mouseout': element
= event
.toElement
; break;
3810 default: return null;
3812 return Element
.extend(element
);
3816 Event
.Methods
= (function() {
3819 if (Prototype
.Browser
.IE
) {
3820 var buttonMap
= { 0: 1, 1: 4, 2: 2 };
3821 isButton = function(event
, code
) {
3822 return event
.button
== buttonMap
[code
];
3825 } else if (Prototype
.Browser
.WebKit
) {
3826 isButton = function(event
, code
) {
3828 case 0: return event
.which
== 1 && !event
.metaKey
;
3829 case 1: return event
.which
== 1 && event
.metaKey
;
3830 default: return false;
3835 isButton = function(event
, code
) {
3836 return event
.which
? (event
.which
=== code
+ 1) : (event
.button
=== code
);
3841 isLeftClick: function(event
) { return isButton(event
, 0) },
3842 isMiddleClick: function(event
) { return isButton(event
, 1) },
3843 isRightClick: function(event
) { return isButton(event
, 2) },
3845 element: function(event
) {
3846 event
= Event
.extend(event
);
3848 var node
= event
.target
,
3850 currentTarget
= event
.currentTarget
;
3852 if (currentTarget
&& currentTarget
.tagName
) {
3853 // Firefox screws up the "click" event when moving between radio buttons
3854 // via arrow keys. It also screws up the "load" and "error" events on images,
3855 // reporting the document as the target instead of the original image.
3856 if (type
=== 'load' || type
=== 'error' ||
3857 (type
=== 'click' && currentTarget
.tagName
.toLowerCase() === 'input'
3858 && currentTarget
.type
=== 'radio'))
3859 node
= currentTarget
;
3861 if (node
.nodeType
== Node
.TEXT_NODE
) node
= node
.parentNode
;
3862 return Element
.extend(node
);
3865 findElement: function(event
, expression
) {
3866 var element
= Event
.element(event
);
3867 if (!expression
) return element
;
3868 var elements
= [element
].concat(element
.ancestors());
3869 return Selector
.findElement(elements
, expression
, 0);
3872 pointer: function(event
) {
3873 var docElement
= document
.documentElement
,
3874 body
= document
.body
|| { scrollLeft
: 0, scrollTop
: 0 };
3876 x
: event
.pageX
|| (event
.clientX
+
3877 (docElement
.scrollLeft
|| body
.scrollLeft
) -
3878 (docElement
.clientLeft
|| 0)),
3879 y
: event
.pageY
|| (event
.clientY
+
3880 (docElement
.scrollTop
|| body
.scrollTop
) -
3881 (docElement
.clientTop
|| 0))
3885 pointerX: function(event
) { return Event
.pointer(event
).x
},
3886 pointerY: function(event
) { return Event
.pointer(event
).y
},
3888 stop: function(event
) {
3889 Event
.extend(event
);
3890 event
.preventDefault();
3891 event
.stopPropagation();
3892 event
.stopped
= true;
3897 Event
.extend
= (function() {
3898 var methods
= Object
.keys(Event
.Methods
).inject({ }, function(m
, name
) {
3899 m
[name
] = Event
.Methods
[name
].methodize();
3903 if (Prototype
.Browser
.IE
) {
3904 Object
.extend(methods
, {
3905 stopPropagation: function() { this.cancelBubble
= true },
3906 preventDefault: function() { this.returnValue
= false },
3907 inspect: function() { return "[object Event]" }
3910 return function(event
) {
3911 if (!event
) return false;
3912 if (event
._extendedByPrototype
) return event
;
3914 event
._extendedByPrototype
= Prototype
.emptyFunction
;
3915 var pointer
= Event
.pointer(event
);
3916 Object
.extend(event
, {
3917 target
: event
.srcElement
,
3918 relatedTarget
: Event
.relatedTarget(event
),
3922 return Object
.extend(event
, methods
);
3926 Event
.prototype = Event
.prototype || document
.createEvent("HTMLEvents")['__proto__'];
3927 Object
.extend(Event
.prototype, methods
);
3932 Object
.extend(Event
, (function() {
3933 var cache
= Event
.cache
;
3935 function getEventID(element
) {
3936 if (element
._prototypeEventID
) return element
._prototypeEventID
[0];
3937 arguments
.callee
.id
= arguments
.callee
.id
|| 1;
3938 return element
._prototypeEventID
= [++arguments
.callee
.id
];
3941 function getDOMEventName(eventName
) {
3942 if (eventName
&& eventName
.include(':')) return "dataavailable";
3946 function getCacheForID(id
) {
3947 return cache
[id
] = cache
[id
] || { };
3950 function getWrappersForEventName(id
, eventName
) {
3951 var c
= getCacheForID(id
);
3952 return c
[eventName
] = c
[eventName
] || [];
3955 function createWrapper(element
, eventName
, handler
) {
3956 var id
= getEventID(element
);
3957 var c
= getWrappersForEventName(id
, eventName
);
3958 if (c
.pluck("handler").include(handler
)) return false;
3960 var wrapper = function(event
) {
3961 if (!Event
|| !Event
.extend
||
3962 (event
.eventName
&& event
.eventName
!= eventName
))
3965 Event
.extend(event
);
3966 handler
.call(element
, event
);
3969 wrapper
.handler
= handler
;
3974 function findWrapper(id
, eventName
, handler
) {
3975 var c
= getWrappersForEventName(id
, eventName
);
3976 return c
.find(function(wrapper
) { return wrapper
.handler
== handler
});
3979 function destroyWrapper(id
, eventName
, handler
) {
3980 var c
= getCacheForID(id
);
3981 if (!c
[eventName
]) return false;
3982 c
[eventName
] = c
[eventName
].without(findWrapper(id
, eventName
, handler
));
3985 function destroyCache() {
3986 for (var id
in cache
)
3987 for (var eventName
in cache
[id
])
3988 cache
[id
][eventName
] = null;
3992 // Internet Explorer needs to remove event handlers on page unload
3993 // in order to avoid memory leaks.
3994 if (window
.attachEvent
) {
3995 window
.attachEvent("onunload", destroyCache
);
3998 // Safari has a dummy event handler on page unload so that it won't
3999 // use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
4000 // object when page is returned to via the back button using its bfcache.
4001 if (Prototype
.Browser
.WebKit
) {
4002 window
.addEventListener('unload', Prototype
.emptyFunction
, false);
4006 observe: function(element
, eventName
, handler
) {
4007 element
= $(element
);
4008 var name
= getDOMEventName(eventName
);
4010 var wrapper
= createWrapper(element
, eventName
, handler
);
4011 if (!wrapper
) return element
;
4013 if (element
.addEventListener
) {
4014 element
.addEventListener(name
, wrapper
, false);
4016 element
.attachEvent("on" + name
, wrapper
);
4022 stopObserving: function(element
, eventName
, handler
) {
4023 element
= $(element
);
4024 var id
= getEventID(element
), name
= getDOMEventName(eventName
);
4026 if (!handler
&& eventName
) {
4027 getWrappersForEventName(id
, eventName
).each(function(wrapper
) {
4028 element
.stopObserving(eventName
, wrapper
.handler
);
4032 } else if (!eventName
) {
4033 Object
.keys(getCacheForID(id
)).each(function(eventName
) {
4034 element
.stopObserving(eventName
);
4039 var wrapper
= findWrapper(id
, eventName
, handler
);
4040 if (!wrapper
) return element
;
4042 if (element
.removeEventListener
) {
4043 element
.removeEventListener(name
, wrapper
, false);
4045 element
.detachEvent("on" + name
, wrapper
);
4048 destroyWrapper(id
, eventName
, handler
);
4053 fire: function(element
, eventName
, memo
) {
4054 element
= $(element
);
4055 if (element
== document
&& document
.createEvent
&& !element
.dispatchEvent
)
4056 element
= document
.documentElement
;
4059 if (document
.createEvent
) {
4060 event
= document
.createEvent("HTMLEvents");
4061 event
.initEvent("dataavailable", true, true);
4063 event
= document
.createEventObject();
4064 event
.eventType
= "ondataavailable";
4067 event
.eventName
= eventName
;
4068 event
.memo
= memo
|| { };
4070 if (document
.createEvent
) {
4071 element
.dispatchEvent(event
);
4073 element
.fireEvent(event
.eventType
, event
);
4076 return Event
.extend(event
);
4081 Object
.extend(Event
, Event
.Methods
);
4083 Element
.addMethods({
4085 observe
: Event
.observe
,
4086 stopObserving
: Event
.stopObserving
4089 Object
.extend(document
, {
4090 fire
: Element
.Methods
.fire
.methodize(),
4091 observe
: Element
.Methods
.observe
.methodize(),
4092 stopObserving
: Element
.Methods
.stopObserving
.methodize(),
4097 /* Support for the DOMContentLoaded event is based on work by Dan Webb,
4098 Matthias Miller, Dean Edwards and John Resig. */
4102 function fireContentLoadedEvent() {
4103 if (document
.loaded
) return;
4104 if (timer
) window
.clearInterval(timer
);
4105 document
.fire("dom:loaded");
4106 document
.loaded
= true;
4109 if (document
.addEventListener
) {
4110 if (Prototype
.Browser
.WebKit
) {
4111 timer
= window
.setInterval(function() {
4112 if (/loaded|complete/.test(document
.readyState
))
4113 fireContentLoadedEvent();
4116 Event
.observe(window
, "load", fireContentLoadedEvent
);
4119 document
.addEventListener("DOMContentLoaded",
4120 fireContentLoadedEvent
, false);
4124 document
.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
4125 $("__onDOMContentLoaded").onreadystatechange = function() {
4126 if (this.readyState
== "complete") {
4127 this.onreadystatechange
= null;
4128 fireContentLoadedEvent();
4133 /*------------------------------- DEPRECATED -------------------------------*/
4135 Hash
.toQueryString
= Object
.toQueryString
;
4137 var Toggle
= { display
: Element
.toggle
};
4139 Element
.Methods
.childOf
= Element
.Methods
.descendantOf
;
4142 Before: function(element
, content
) {
4143 return Element
.insert(element
, {before
:content
});
4146 Top: function(element
, content
) {
4147 return Element
.insert(element
, {top
:content
});
4150 Bottom: function(element
, content
) {
4151 return Element
.insert(element
, {bottom
:content
});
4154 After: function(element
, content
) {
4155 return Element
.insert(element
, {after
:content
});
4159 var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
4161 // This should be moved to script.aculo.us; notice the deprecated methods
4162 // further below, that map to the newer Element methods.
4164 // set to true if needed, warning: firefox performance problems
4165 // NOT neeeded for page scrolling, only if draggable contained in
4166 // scrollable elements
4167 includeScrollOffsets
: false,
4169 // must be called before calling withinIncludingScrolloffset, every time the
4171 prepare: function() {
4172 this.deltaX
= window
.pageXOffset
4173 || document
.documentElement
.scrollLeft
4174 || document
.body
.scrollLeft
4176 this.deltaY
= window
.pageYOffset
4177 || document
.documentElement
.scrollTop
4178 || document
.body
.scrollTop
4182 // caches x/y coordinate pair to use with overlap
4183 within: function(element
, x
, y
) {
4184 if (this.includeScrollOffsets
)
4185 return this.withinIncludingScrolloffsets(element
, x
, y
);
4188 this.offset
= Element
.cumulativeOffset(element
);
4190 return (y
>= this.offset
[1] &&
4191 y
< this.offset
[1] + element
.offsetHeight
&&
4192 x
>= this.offset
[0] &&
4193 x
< this.offset
[0] + element
.offsetWidth
);
4196 withinIncludingScrolloffsets: function(element
, x
, y
) {
4197 var offsetcache
= Element
.cumulativeScrollOffset(element
);
4199 this.xcomp
= x
+ offsetcache
[0] - this.deltaX
;
4200 this.ycomp
= y
+ offsetcache
[1] - this.deltaY
;
4201 this.offset
= Element
.cumulativeOffset(element
);
4203 return (this.ycomp
>= this.offset
[1] &&
4204 this.ycomp
< this.offset
[1] + element
.offsetHeight
&&
4205 this.xcomp
>= this.offset
[0] &&
4206 this.xcomp
< this.offset
[0] + element
.offsetWidth
);
4209 // within must be called directly before
4210 overlap: function(mode
, element
) {
4211 if (!mode
) return 0;
4212 if (mode
== 'vertical')
4213 return ((this.offset
[1] + element
.offsetHeight
) - this.ycomp
) /
4214 element
.offsetHeight
;
4215 if (mode
== 'horizontal')
4216 return ((this.offset
[0] + element
.offsetWidth
) - this.xcomp
) /
4217 element
.offsetWidth
;
4220 // Deprecation layer -- use newer Element methods now (1.5.2).
4222 cumulativeOffset
: Element
.Methods
.cumulativeOffset
,
4224 positionedOffset
: Element
.Methods
.positionedOffset
,
4226 absolutize: function(element
) {
4228 return Element
.absolutize(element
);
4231 relativize: function(element
) {
4233 return Element
.relativize(element
);
4236 realOffset
: Element
.Methods
.cumulativeScrollOffset
,
4238 offsetParent
: Element
.Methods
.getOffsetParent
,
4240 page
: Element
.Methods
.viewportOffset
,
4242 clone: function(source
, target
, options
) {
4243 options
= options
|| { };
4244 return Element
.clonePosition(target
, source
, options
);
4248 /*--------------------------------------------------------------------------*/
4250 if (!document
.getElementsByClassName
) document
.getElementsByClassName = function(instanceMethods
){
4251 function iter(name
) {
4252 return name
.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name
+ " ')]";
4255 instanceMethods
.getElementsByClassName
= Prototype
.BrowserFeatures
.XPath
?
4256 function(element
, className
) {
4257 className
= className
.toString().strip();
4258 var cond
= /\s/.test(className
) ? $w(className
).map(iter
).join('') : iter(className
);
4259 return cond
? document
._getElementsByXPath('.//*' + cond
, element
) : [];
4260 } : function(element
, className
) {
4261 className
= className
.toString().strip();
4262 var elements
= [], classNames
= (/\s/.test(className
) ? $w(className
) : null);
4263 if (!classNames
&& !className
) return elements
;
4265 var nodes
= $(element
).getElementsByTagName('*');
4266 className
= ' ' + className
+ ' ';
4268 for (var i
= 0, child
, cn
; child
= nodes
[i
]; i
++) {
4269 if (child
.className
&& (cn
= ' ' + child
.className
+ ' ') && (cn
.include(className
) ||
4270 (classNames
&& classNames
.all(function(name
) {
4271 return !name
.toString().blank() && cn
.include(' ' + name
+ ' ');
4273 elements
.push(Element
.extend(child
));
4278 return function(className
, parentElement
) {
4279 return $(parentElement
|| document
.body
).getElementsByClassName(className
);
4283 /*--------------------------------------------------------------------------*/
4285 Element
.ClassNames
= Class
.create();
4286 Element
.ClassNames
.prototype = {
4287 initialize: function(element
) {
4288 this.element
= $(element
);
4291 _each: function(iterator
) {
4292 this.element
.className
.split(/\s+/).select(function(name
) {
4293 return name
.length
> 0;
4297 set: function(className
) {
4298 this.element
.className
= className
;
4301 add: function(classNameToAdd
) {
4302 if (this.include(classNameToAdd
)) return;
4303 this.set($A(this).concat(classNameToAdd
).join(' '));
4306 remove: function(classNameToRemove
) {
4307 if (!this.include(classNameToRemove
)) return;
4308 this.set($A(this).without(classNameToRemove
).join(' '));
4311 toString: function() {
4312 return $A(this).join(' ');
4316 Object
.extend(Element
.ClassNames
.prototype, Enumerable
);
4318 /*--------------------------------------------------------------------------*/
4320 Element
.addMethods();