]>
git.wh0rd.org - tt-rss.git/blob - prototype.js
1 /* Prototype JavaScript framework, version 1.4.0
2 * (c) 2005 Sam Stephenson <sam@conio.net>
4 * Prototype is freely distributable under the terms of an MIT-style license.
5 * For details, see the Prototype web site: http://prototype.conio.net/
7 /*--------------------------------------------------------------------------*/
11 ScriptFragment
: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
13 emptyFunction: function() {},
14 K: function(x
) {return x
}
20 this.initialize
.apply(this, arguments
);
25 var Abstract
= new Object();
27 Object
.extend = function(destination
, source
) {
28 for (property
in source
) {
29 destination
[property
] = source
[property
];
34 Object
.inspect = function(object
) {
36 if (object
== undefined) return 'undefined';
37 if (object
== null) return 'null';
38 return object
.inspect
? object
.inspect() : object
.toString();
40 if (e
instanceof RangeError
) return '...';
45 Function
.prototype.bind = function() {
46 var __method
= this, args
= $A(arguments
), object
= args
.shift();
48 return __method
.apply(object
, args
.concat($A(arguments
)));
52 Function
.prototype.bindAsEventListener = function(object
) {
54 return function(event
) {
55 return __method
.call(object
, event
|| window
.event
);
59 Object
.extend(Number
.prototype, {
60 toColorPart: function() {
61 var digits
= this.toString(16);
62 if (this < 16) return '0' + digits
;
70 times: function(iterator
) {
71 $R(0, this, true).each(iterator
);
80 for (var i
= 0; i
< arguments
.length
; i
++) {
81 var lambda
= arguments
[i
];
83 returnValue
= lambda();
92 /*--------------------------------------------------------------------------*/
94 var PeriodicalExecuter
= Class
.create();
95 PeriodicalExecuter
.prototype = {
96 initialize: function(callback
, frequency
) {
97 this.callback
= callback
;
98 this.frequency
= frequency
;
99 this.currentlyExecuting
= false;
101 this.registerCallback();
104 registerCallback: function() {
105 setInterval(this.onTimerEvent
.bind(this), this.frequency
* 1000);
108 onTimerEvent: function() {
109 if (!this.currentlyExecuting
) {
111 this.currentlyExecuting
= true;
114 this.currentlyExecuting
= false;
120 /*--------------------------------------------------------------------------*/
123 var elements
= new Array();
125 for (var i
= 0; i
< arguments
.length
; i
++) {
126 var element
= arguments
[i
];
127 if (typeof element
== 'string')
128 element
= document
.getElementById(element
);
130 if (arguments
.length
== 1)
133 elements
.push(element
);
138 Object
.extend(String
.prototype, {
139 stripTags: function() {
140 return this.replace(/<\/?[^>]+>/gi, '');
143 stripScripts: function() {
144 return this.replace(new RegExp(Prototype
.ScriptFragment
, 'img'), '');
147 extractScripts: function() {
148 var matchAll
= new RegExp(Prototype
.ScriptFragment
, 'img');
149 var matchOne
= new RegExp(Prototype
.ScriptFragment
, 'im');
150 return (this.match(matchAll
) || []).map(function(scriptTag
) {
151 return (scriptTag
.match(matchOne
) || ['', ''])[1];
155 evalScripts: function() {
156 return this.extractScripts().map(eval
);
159 escapeHTML: function() {
160 var div
= document
.createElement('div');
161 var text
= document
.createTextNode(this);
162 div
.appendChild(text
);
163 return div
.innerHTML
;
166 unescapeHTML: function() {
167 var div
= document
.createElement('div');
168 div
.innerHTML
= this.stripTags();
169 return div
.childNodes
[0] ? div
.childNodes
[0].nodeValue
: '';
172 toQueryParams: function() {
173 var pairs
= this.match(/^\??(.*)$/)[1].split('&');
174 return pairs
.inject({}, function(params
, pairString
) {
175 var pair
= pairString
.split('=');
176 params
[pair
[0]] = pair
[1];
181 toArray: function() {
182 return this.split('');
185 camelize: function() {
186 var oStringList
= this.split('-');
187 if (oStringList
.length
== 1) return oStringList
[0];
189 var camelizedString
= this.indexOf('-') == 0
190 ? oStringList
[0].charAt(0).toUpperCase() + oStringList
[0].substring(1)
193 for (var i
= 1, len
= oStringList
.length
; i
< len
; i
++) {
194 var s
= oStringList
[i
];
195 camelizedString
+= s
.charAt(0).toUpperCase() + s
.substring(1);
198 return camelizedString
;
201 inspect: function() {
202 return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'";
206 String
.prototype.parseQuery
= String
.prototype.toQueryParams
;
208 var $break = new Object();
209 var $continue = new Object();
212 each: function(iterator
) {
215 this._each(function(value
) {
217 iterator(value
, index
++);
219 if (e
!= $continue) throw e
;
223 if (e
!= $break) throw e
;
227 all: function(iterator
) {
229 this.each(function(value
, index
) {
230 result
= result
&& !!(iterator
|| Prototype
.K
)(value
, index
);
231 if (!result
) throw $break;
236 any: function(iterator
) {
238 this.each(function(value
, index
) {
239 if (result
= !!(iterator
|| Prototype
.K
)(value
, index
))
245 collect: function(iterator
) {
247 this.each(function(value
, index
) {
248 results
.push(iterator(value
, index
));
253 detect: function (iterator
) {
255 this.each(function(value
, index
) {
256 if (iterator(value
, index
)) {
264 findAll: function(iterator
) {
266 this.each(function(value
, index
) {
267 if (iterator(value
, index
))
273 grep: function(pattern
, iterator
) {
275 this.each(function(value
, index
) {
276 var stringValue
= value
.toString();
277 if (stringValue
.match(pattern
))
278 results
.push((iterator
|| Prototype
.K
)(value
, index
));
283 include: function(object
) {
285 this.each(function(value
) {
286 if (value
== object
) {
294 inject: function(memo
, iterator
) {
295 this.each(function(value
, index
) {
296 memo
= iterator(memo
, value
, index
);
301 invoke: function(method
) {
302 var args
= $A(arguments
).slice(1);
303 return this.collect(function(value
) {
304 return value
[method
].apply(value
, args
);
308 max: function(iterator
) {
310 this.each(function(value
, index
) {
311 value
= (iterator
|| Prototype
.K
)(value
, index
);
312 if (value
>= (result
|| value
))
318 min: function(iterator
) {
320 this.each(function(value
, index
) {
321 value
= (iterator
|| Prototype
.K
)(value
, index
);
322 if (value
<= (result
|| value
))
328 partition: function(iterator
) {
329 var trues
= [], falses
= [];
330 this.each(function(value
, index
) {
331 ((iterator
|| Prototype
.K
)(value
, index
) ?
332 trues
: falses
).push(value
);
334 return [trues
, falses
];
337 pluck: function(property
) {
339 this.each(function(value
, index
) {
340 results
.push(value
[property
]);
345 reject: function(iterator
) {
347 this.each(function(value
, index
) {
348 if (!iterator(value
, index
))
354 sortBy: function(iterator
) {
355 return this.collect(function(value
, index
) {
356 return {value
: value
, criteria
: iterator(value
, index
)};
357 }).sort(function(left
, right
) {
358 var a
= left
.criteria
, b
= right
.criteria
;
359 return a
< b
? -1 : a
> b
? 1 : 0;
363 toArray: function() {
364 return this.collect(Prototype
.K
);
368 var iterator
= Prototype
.K
, args
= $A(arguments
);
369 if (typeof args
.last() == 'function')
370 iterator
= args
.pop();
372 var collections
= [this].concat(args
).map($A
);
373 return this.map(function(value
, index
) {
374 iterator(value
= collections
.pluck(index
));
379 inspect: function() {
380 return '#<Enumerable:' + this.toArray().inspect() + '>';
384 Object
.extend(Enumerable
, {
385 map
: Enumerable
.collect
,
386 find
: Enumerable
.detect
,
387 select
: Enumerable
.findAll
,
388 member
: Enumerable
.include
,
389 entries
: Enumerable
.toArray
391 var $A
= Array
.from = function(iterable
) {
392 if (!iterable
) return [];
393 if (iterable
.toArray
) {
394 return iterable
.toArray();
397 for (var i
= 0; i
< iterable
.length
; i
++)
398 results
.push(iterable
[i
]);
403 Object
.extend(Array
.prototype, Enumerable
);
405 Array
.prototype._reverse
= Array
.prototype.reverse
;
407 Object
.extend(Array
.prototype, {
408 _each: function(iterator
) {
409 for (var i
= 0; i
< this.length
; i
++)
423 return this[this.length
- 1];
426 compact: function() {
427 return this.select(function(value
) {
428 return value
!= undefined || value
!= null;
432 flatten: function() {
433 return this.inject([], function(array
, value
) {
434 return array
.concat(value
.constructor == Array
?
435 value
.flatten() : [value
]);
439 without: function() {
440 var values
= $A(arguments
);
441 return this.select(function(value
) {
442 return !values
.include(value
);
446 indexOf: function(object
) {
447 for (var i
= 0; i
< this.length
; i
++)
448 if (this[i
] == object
) return i
;
452 reverse: function(inline
) {
453 return (inline
!== false ? this : this.toArray())._reverse();
457 var result
= this[0];
458 for (var i
= 0; i
< this.length
- 1; i
++)
459 this[i
] = this[i
+ 1];
464 inspect: function() {
465 return '[' + this.map(Object
.inspect
).join(', ') + ']';
469 _each: function(iterator
) {
471 var value
= this[key
];
472 if (typeof value
== 'function') continue;
474 var pair
= [key
, value
];
482 return this.pluck('key');
486 return this.pluck('value');
489 merge: function(hash
) {
490 return $H(hash
).inject($H(this), function(mergedHash
, pair
) {
491 mergedHash
[pair
.key
] = pair
.value
;
496 toQueryString: function() {
497 return this.map(function(pair
) {
498 return pair
.map(encodeURIComponent
).join('=');
502 inspect: function() {
503 return '#<Hash:{' + this.map(function(pair
) {
504 return pair
.map(Object
.inspect
).join(': ');
505 }).join(', ') + '}>';
509 function $H(object
) {
510 var hash
= Object
.extend({}, object
|| {});
511 Object
.extend(hash
, Enumerable
);
512 Object
.extend(hash
, Hash
);
515 ObjectRange
= Class
.create();
516 Object
.extend(ObjectRange
.prototype, Enumerable
);
517 Object
.extend(ObjectRange
.prototype, {
518 initialize: function(start
, end
, exclusive
) {
521 this.exclusive
= exclusive
;
524 _each: function(iterator
) {
525 var value
= this.start
;
528 value
= value
.succ();
529 } while (this.include(value
));
532 include: function(value
) {
533 if (value
< this.start
)
536 return value
< this.end
;
537 return value
<= this.end
;
541 var $R = function(start
, end
, exclusive
) {
542 return new ObjectRange(start
, end
, exclusive
);
546 getTransport: function() {
548 function() {return new ActiveXObject('Msxml2.XMLHTTP')},
549 function() {return new ActiveXObject('Microsoft.XMLHTTP')},
550 function() {return new XMLHttpRequest()}
554 activeRequestCount
: 0
560 _each: function(iterator
) {
561 this.responders
._each(iterator
);
564 register: function(responderToAdd
) {
565 if (!this.include(responderToAdd
))
566 this.responders
.push(responderToAdd
);
569 unregister: function(responderToRemove
) {
570 this.responders
= this.responders
.without(responderToRemove
);
573 dispatch: function(callback
, request
, transport
, json
) {
574 this.each(function(responder
) {
575 if (responder
[callback
] && typeof responder
[callback
] == 'function') {
577 responder
[callback
].apply(responder
, [request
, transport
, json
]);
584 Object
.extend(Ajax
.Responders
, Enumerable
);
586 Ajax
.Responders
.register({
587 onCreate: function() {
588 Ajax
.activeRequestCount
++;
591 onComplete: function() {
592 Ajax
.activeRequestCount
--;
596 Ajax
.Base = function() {};
597 Ajax
.Base
.prototype = {
598 setOptions: function(options
) {
604 Object
.extend(this.options
, options
|| {});
607 responseIsSuccess: function() {
608 return this.transport
.status
== undefined
609 || this.transport
.status
== 0
610 || (this.transport
.status
>= 200 && this.transport
.status
< 300);
613 responseIsFailure: function() {
614 return !this.responseIsSuccess();
618 Ajax
.Request
= Class
.create();
619 Ajax
.Request
.Events
=
620 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
622 Ajax
.Request
.prototype = Object
.extend(new Ajax
.Base(), {
623 initialize: function(url
, options
) {
624 this.transport
= Ajax
.getTransport();
625 this.setOptions(options
);
629 request: function(url
) {
630 var parameters
= this.options
.parameters
|| '';
631 if (parameters
.length
> 0) parameters
+= '&_=';
635 if (this.options
.method
== 'get' && parameters
.length
> 0)
636 this.url
+= (this.url
.match(/\?/) ? '&' : '?') + parameters
;
638 Ajax
.Responders
.dispatch('onCreate', this, this.transport
);
640 this.transport
.open(this.options
.method
, this.url
,
641 this.options
.asynchronous
);
643 if (this.options
.asynchronous
) {
644 this.transport
.onreadystatechange
= this.onStateChange
.bind(this);
645 setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
648 this.setRequestHeaders();
650 var body
= this.options
.postBody
? this.options
.postBody
: parameters
;
651 this.transport
.send(this.options
.method
== 'post' ? body
: null);
654 this.dispatchException(e
);
658 setRequestHeaders: function() {
660 ['X-Requested-With', 'XMLHttpRequest',
661 'X-Prototype-Version', Prototype
.Version
];
663 if (this.options
.method
== 'post') {
664 requestHeaders
.push('Content-type',
665 'application/x-www-form-urlencoded');
667 /* Force "Connection: close" for Mozilla browsers to work around
668 * a bug where XMLHttpReqeuest sends an incorrect Content-length
669 * header. See Mozilla Bugzilla #246651.
671 if (this.transport
.overrideMimeType
)
672 requestHeaders
.push('Connection', 'close');
675 if (this.options
.requestHeaders
)
676 requestHeaders
.push
.apply(requestHeaders
, this.options
.requestHeaders
);
678 for (var i
= 0; i
< requestHeaders
.length
; i
+= 2)
679 this.transport
.setRequestHeader(requestHeaders
[i
], requestHeaders
[i
+1]);
682 onStateChange: function() {
683 var readyState
= this.transport
.readyState
;
685 this.respondToReadyState(this.transport
.readyState
);
688 header: function(name
) {
690 return this.transport
.getResponseHeader(name
);
694 evalJSON: function() {
696 return eval(this.header('X-JSON'));
700 evalResponse: function() {
702 return eval(this.transport
.responseText
);
704 this.dispatchException(e
);
708 respondToReadyState: function(readyState
) {
709 var event
= Ajax
.Request
.Events
[readyState
];
710 var transport
= this.transport
, json
= this.evalJSON();
712 if (event
== 'Complete') {
714 (this.options
['on' + this.transport
.status
]
715 || this.options
['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
716 || Prototype
.emptyFunction
)(transport
, json
);
718 this.dispatchException(e
);
721 if ((this.header('Content-type') || '').match(/^text\/javascript/i))
726 (this.options
['on' + event
] || Prototype
.emptyFunction
)(transport
, json
);
727 Ajax
.Responders
.dispatch('on' + event
, this, transport
, json
);
729 this.dispatchException(e
);
732 /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
733 if (event
== 'Complete')
734 this.transport
.onreadystatechange
= Prototype
.emptyFunction
;
737 dispatchException: function(exception
) {
738 (this.options
.onException
|| Prototype
.emptyFunction
)(this, exception
);
739 Ajax
.Responders
.dispatch('onException', this, exception
);
743 Ajax
.Updater
= Class
.create();
745 Object
.extend(Object
.extend(Ajax
.Updater
.prototype, Ajax
.Request
.prototype), {
746 initialize: function(container
, url
, options
) {
748 success
: container
.success
? $(container
.success
) : $(container
),
749 failure
: container
.failure
? $(container
.failure
) :
750 (container
.success
? null : $(container
))
753 this.transport
= Ajax
.getTransport();
754 this.setOptions(options
);
756 var onComplete
= this.options
.onComplete
|| Prototype
.emptyFunction
;
757 this.options
.onComplete
= (function(transport
, object
) {
758 this.updateContent();
759 onComplete(transport
, object
);
765 updateContent: function() {
766 var receiver
= this.responseIsSuccess() ?
767 this.containers
.success
: this.containers
.failure
;
768 var response
= this.transport
.responseText
;
770 if (!this.options
.evalScripts
)
771 response
= response
.stripScripts();
774 if (this.options
.insertion
) {
775 new this.options
.insertion(receiver
, response
);
777 Element
.update(receiver
, response
);
781 if (this.responseIsSuccess()) {
783 setTimeout(this.onComplete
.bind(this), 10);
788 Ajax
.PeriodicalUpdater
= Class
.create();
789 Ajax
.PeriodicalUpdater
.prototype = Object
.extend(new Ajax
.Base(), {
790 initialize: function(container
, url
, options
) {
791 this.setOptions(options
);
792 this.onComplete
= this.options
.onComplete
;
794 this.frequency
= (this.options
.frequency
|| 2);
795 this.decay
= (this.options
.decay
|| 1);
798 this.container
= container
;
805 this.options
.onComplete
= this.updateComplete
.bind(this);
810 this.updater
.onComplete
= undefined;
811 clearTimeout(this.timer
);
812 (this.onComplete
|| Prototype
.emptyFunction
).apply(this, arguments
);
815 updateComplete: function(request
) {
816 if (this.options
.decay
) {
817 this.decay
= (request
.responseText
== this.lastText
?
818 this.decay
* this.options
.decay
: 1);
820 this.lastText
= request
.responseText
;
822 this.timer
= setTimeout(this.onTimerEvent
.bind(this),
823 this.decay
* this.frequency
* 1000);
826 onTimerEvent: function() {
827 this.updater
= new Ajax
.Updater(this.container
, this.url
, this.options
);
830 document
.getElementsByClassName = function(className
, parentElement
) {
831 var children
= ($(parentElement
) || document
.body
).getElementsByTagName('*');
832 return $A(children
).inject([], function(elements
, child
) {
833 if (child
.className
.match(new RegExp("(^|\\s)" + className
+ "(\\s|$)")))
834 elements
.push(child
);
839 /*--------------------------------------------------------------------------*/
841 if (!window
.Element
) {
842 var Element
= new Object();
845 Object
.extend(Element
, {
846 visible: function(element
) {
847 return $(element
).style
.display
!= 'none';
851 for (var i
= 0; i
< arguments
.length
; i
++) {
852 var element
= $(arguments
[i
]);
853 Element
[Element
.visible(element
) ? 'hide' : 'show'](element
);
858 for (var i
= 0; i
< arguments
.length
; i
++) {
859 var element
= $(arguments
[i
]);
860 element
.style
.display
= 'none';
865 for (var i
= 0; i
< arguments
.length
; i
++) {
866 var element
= $(arguments
[i
]);
867 element
.style
.display
= '';
871 remove: function(element
) {
872 element
= $(element
);
873 element
.parentNode
.removeChild(element
);
876 update: function(element
, html
) {
877 $(element
).innerHTML
= html
.stripScripts();
878 setTimeout(function() {html
.evalScripts()}, 10);
881 getHeight: function(element
) {
882 element
= $(element
);
883 return element
.offsetHeight
;
886 classNames: function(element
) {
887 return new Element
.ClassNames(element
);
890 hasClassName: function(element
, className
) {
891 if (!(element
= $(element
))) return;
892 return Element
.classNames(element
).include(className
);
895 addClassName: function(element
, className
) {
896 if (!(element
= $(element
))) return;
897 return Element
.classNames(element
).add(className
);
900 removeClassName: function(element
, className
) {
901 if (!(element
= $(element
))) return;
902 return Element
.classNames(element
).remove(className
);
905 // removes whitespace-only text node children
906 cleanWhitespace: function(element
) {
907 element
= $(element
);
908 for (var i
= 0; i
< element
.childNodes
.length
; i
++) {
909 var node
= element
.childNodes
[i
];
910 if (node
.nodeType
== 3 && !/\S/.test(node
.nodeValue
))
911 Element
.remove(node
);
915 empty: function(element
) {
916 return $(element
).innerHTML
.match(/^\s*$/);
919 scrollTo: function(element
) {
920 element
= $(element
);
921 var x
= element
.x
? element
.x
: element
.offsetLeft
,
922 y
= element
.y
? element
.y
: element
.offsetTop
;
923 window
.scrollTo(x
, y
);
926 getStyle: function(element
, style
) {
927 element
= $(element
);
928 var value
= element
.style
[style
.camelize()];
930 if (document
.defaultView
&& document
.defaultView
.getComputedStyle
) {
931 var css
= document
.defaultView
.getComputedStyle(element
, null);
932 value
= css
? css
.getPropertyValue(style
) : null;
933 } else if (element
.currentStyle
) {
934 value
= element
.currentStyle
[style
.camelize()];
938 if (window
.opera
&& ['left', 'top', 'right', 'bottom'].include(style
))
939 if (Element
.getStyle(element
, 'position') == 'static') value
= 'auto';
941 return value
== 'auto' ? null : value
;
944 setStyle: function(element
, style
) {
945 element
= $(element
);
947 element
.style
[name
.camelize()] = style
[name
];
950 getDimensions: function(element
) {
951 element
= $(element
);
952 if (Element
.getStyle(element
, 'display') != 'none')
953 return {width
: element
.offsetWidth
, height
: element
.offsetHeight
};
955 // All *Width and *Height properties give 0 on elements with display none,
956 // so enable the element temporarily
957 var els
= element
.style
;
958 var originalVisibility
= els
.visibility
;
959 var originalPosition
= els
.position
;
960 els
.visibility
= 'hidden';
961 els
.position
= 'absolute';
963 var originalWidth
= element
.clientWidth
;
964 var originalHeight
= element
.clientHeight
;
965 els
.display
= 'none';
966 els
.position
= originalPosition
;
967 els
.visibility
= originalVisibility
;
968 return {width
: originalWidth
, height
: originalHeight
};
971 makePositioned: function(element
) {
972 element
= $(element
);
973 var pos
= Element
.getStyle(element
, 'position');
974 if (pos
== 'static' || !pos
) {
975 element
._madePositioned
= true;
976 element
.style
.position
= 'relative';
977 // Opera returns the offset relative to the positioning context, when an
978 // element is position relative but top and left have not been defined
980 element
.style
.top
= 0;
981 element
.style
.left
= 0;
986 undoPositioned: function(element
) {
987 element
= $(element
);
988 if (element
._madePositioned
) {
989 element
._madePositioned
= undefined;
990 element
.style
.position
=
993 element
.style
.bottom
=
994 element
.style
.right
= '';
998 makeClipping: function(element
) {
999 element
= $(element
);
1000 if (element
._overflow
) return;
1001 element
._overflow
= element
.style
.overflow
;
1002 if ((Element
.getStyle(element
, 'overflow') || 'visible') != 'hidden')
1003 element
.style
.overflow
= 'hidden';
1006 undoClipping: function(element
) {
1007 element
= $(element
);
1008 if (element
._overflow
) return;
1009 element
.style
.overflow
= element
._overflow
;
1010 element
._overflow
= undefined;
1014 var Toggle
= new Object();
1015 Toggle
.display
= Element
.toggle
;
1017 /*--------------------------------------------------------------------------*/
1019 Abstract
.Insertion = function(adjacency
) {
1020 this.adjacency
= adjacency
;
1023 Abstract
.Insertion
.prototype = {
1024 initialize: function(element
, content
) {
1025 this.element
= $(element
);
1026 this.content
= content
.stripScripts();
1028 if (this.adjacency
&& this.element
.insertAdjacentHTML
) {
1030 this.element
.insertAdjacentHTML(this.adjacency
, this.content
);
1032 if (this.element
.tagName
.toLowerCase() == 'tbody') {
1033 this.insertContent(this.contentFromAnonymousTable());
1039 this.range
= this.element
.ownerDocument
.createRange();
1040 if (this.initializeRange
) this.initializeRange();
1041 this.insertContent([this.range
.createContextualFragment(this.content
)]);
1044 setTimeout(function() {content
.evalScripts()}, 10);
1047 contentFromAnonymousTable: function() {
1048 var div
= document
.createElement('div');
1049 div
.innerHTML
= '<table><tbody>' + this.content
+ '</tbody></table>';
1050 return $A(div
.childNodes
[0].childNodes
[0].childNodes
);
1054 var Insertion
= new Object();
1056 Insertion
.Before
= Class
.create();
1057 Insertion
.Before
.prototype = Object
.extend(new Abstract
.Insertion('beforeBegin'), {
1058 initializeRange: function() {
1059 this.range
.setStartBefore(this.element
);
1062 insertContent: function(fragments
) {
1063 fragments
.each((function(fragment
) {
1064 this.element
.parentNode
.insertBefore(fragment
, this.element
);
1069 Insertion
.Top
= Class
.create();
1070 Insertion
.Top
.prototype = Object
.extend(new Abstract
.Insertion('afterBegin'), {
1071 initializeRange: function() {
1072 this.range
.selectNodeContents(this.element
);
1073 this.range
.collapse(true);
1076 insertContent: function(fragments
) {
1077 fragments
.reverse(false).each((function(fragment
) {
1078 this.element
.insertBefore(fragment
, this.element
.firstChild
);
1083 Insertion
.Bottom
= Class
.create();
1084 Insertion
.Bottom
.prototype = Object
.extend(new Abstract
.Insertion('beforeEnd'), {
1085 initializeRange: function() {
1086 this.range
.selectNodeContents(this.element
);
1087 this.range
.collapse(this.element
);
1090 insertContent: function(fragments
) {
1091 fragments
.each((function(fragment
) {
1092 this.element
.appendChild(fragment
);
1097 Insertion
.After
= Class
.create();
1098 Insertion
.After
.prototype = Object
.extend(new Abstract
.Insertion('afterEnd'), {
1099 initializeRange: function() {
1100 this.range
.setStartAfter(this.element
);
1103 insertContent: function(fragments
) {
1104 fragments
.each((function(fragment
) {
1105 this.element
.parentNode
.insertBefore(fragment
,
1106 this.element
.nextSibling
);
1111 /*--------------------------------------------------------------------------*/
1113 Element
.ClassNames
= Class
.create();
1114 Element
.ClassNames
.prototype = {
1115 initialize: function(element
) {
1116 this.element
= $(element
);
1119 _each: function(iterator
) {
1120 this.element
.className
.split(/\s+/).select(function(name
) {
1121 return name
.length
> 0;
1125 set: function(className
) {
1126 this.element
.className
= className
;
1129 add: function(classNameToAdd
) {
1130 if (this.include(classNameToAdd
)) return;
1131 this.set(this.toArray().concat(classNameToAdd
).join(' '));
1134 remove: function(classNameToRemove
) {
1135 if (!this.include(classNameToRemove
)) return;
1136 this.set(this.select(function(className
) {
1137 return className
!= classNameToRemove
;
1141 toString: function() {
1142 return this.toArray().join(' ');
1146 Object
.extend(Element
.ClassNames
.prototype, Enumerable
);
1149 for (var i
= 0; i
< arguments
.length
; i
++)
1150 $(arguments
[i
]).value
= '';
1153 focus: function(element
) {
1157 present: function() {
1158 for (var i
= 0; i
< arguments
.length
; i
++)
1159 if ($(arguments
[i
]).value
== '') return false;
1163 select: function(element
) {
1164 $(element
).select();
1167 activate: function(element
) {
1168 element
= $(element
);
1175 /*--------------------------------------------------------------------------*/
1178 serialize: function(form
) {
1179 var elements
= Form
.getElements($(form
));
1180 var queryComponents
= new Array();
1182 for (var i
= 0; i
< elements
.length
; i
++) {
1183 var queryComponent
= Form
.Element
.serialize(elements
[i
]);
1185 queryComponents
.push(queryComponent
);
1188 return queryComponents
.join('&');
1191 getElements: function(form
) {
1193 var elements
= new Array();
1195 for (tagName
in Form
.Element
.Serializers
) {
1196 var tagElements
= form
.getElementsByTagName(tagName
);
1197 for (var j
= 0; j
< tagElements
.length
; j
++)
1198 elements
.push(tagElements
[j
]);
1203 getInputs: function(form
, typeName
, name
) {
1205 var inputs
= form
.getElementsByTagName('input');
1207 if (!typeName
&& !name
)
1210 var matchingInputs
= new Array();
1211 for (var i
= 0; i
< inputs
.length
; i
++) {
1212 var input
= inputs
[i
];
1213 if ((typeName
&& input
.type
!= typeName
) ||
1214 (name
&& input
.name
!= name
))
1216 matchingInputs
.push(input
);
1219 return matchingInputs
;
1222 disable: function(form
) {
1223 var elements
= Form
.getElements(form
);
1224 for (var i
= 0; i
< elements
.length
; i
++) {
1225 var element
= elements
[i
];
1227 element
.disabled
= 'true';
1231 enable: function(form
) {
1232 var elements
= Form
.getElements(form
);
1233 for (var i
= 0; i
< elements
.length
; i
++) {
1234 var element
= elements
[i
];
1235 element
.disabled
= '';
1239 findFirstElement: function(form
) {
1240 return Form
.getElements(form
).find(function(element
) {
1241 return element
.type
!= 'hidden' && !element
.disabled
&&
1242 ['input', 'select', 'textarea'].include(element
.tagName
.toLowerCase());
1246 focusFirstElement: function(form
) {
1247 Field
.activate(Form
.findFirstElement(form
));
1250 reset: function(form
) {
1256 serialize: function(element
) {
1257 element
= $(element
);
1258 var method
= element
.tagName
.toLowerCase();
1259 var parameter
= Form
.Element
.Serializers
[method
](element
);
1262 var key
= encodeURIComponent(parameter
[0]);
1263 if (key
.length
== 0) return;
1265 if (parameter
[1].constructor != Array
)
1266 parameter
[1] = [parameter
[1]];
1268 return parameter
[1].map(function(value
) {
1269 return key
+ '=' + encodeURIComponent(value
);
1274 getValue: function(element
) {
1275 element
= $(element
);
1276 var method
= element
.tagName
.toLowerCase();
1277 var parameter
= Form
.Element
.Serializers
[method
](element
);
1280 return parameter
[1];
1284 Form
.Element
.Serializers
= {
1285 input: function(element
) {
1286 switch (element
.type
.toLowerCase()) {
1291 return Form
.Element
.Serializers
.textarea(element
);
1294 return Form
.Element
.Serializers
.inputSelector(element
);
1299 inputSelector: function(element
) {
1300 if (element
.checked
)
1301 return [element
.name
, element
.value
];
1304 textarea: function(element
) {
1305 return [element
.name
, element
.value
];
1308 select: function(element
) {
1309 return Form
.Element
.Serializers
[element
.type
== 'select-one' ?
1310 'selectOne' : 'selectMany'](element
);
1313 selectOne: function(element
) {
1314 var value
= '', opt
, index
= element
.selectedIndex
;
1316 opt
= element
.options
[index
];
1318 if (!value
&& !('value' in opt
))
1321 return [element
.name
, value
];
1324 selectMany: function(element
) {
1325 var value
= new Array();
1326 for (var i
= 0; i
< element
.length
; i
++) {
1327 var opt
= element
.options
[i
];
1329 var optValue
= opt
.value
;
1330 if (!optValue
&& !('value' in opt
))
1331 optValue
= opt
.text
;
1332 value
.push(optValue
);
1335 return [element
.name
, value
];
1339 /*--------------------------------------------------------------------------*/
1341 var $F
= Form
.Element
.getValue
;
1343 /*--------------------------------------------------------------------------*/
1345 Abstract
.TimedObserver = function() {}
1346 Abstract
.TimedObserver
.prototype = {
1347 initialize: function(element
, frequency
, callback
) {
1348 this.frequency
= frequency
;
1349 this.element
= $(element
);
1350 this.callback
= callback
;
1352 this.lastValue
= this.getValue();
1353 this.registerCallback();
1356 registerCallback: function() {
1357 setInterval(this.onTimerEvent
.bind(this), this.frequency
* 1000);
1360 onTimerEvent: function() {
1361 var value
= this.getValue();
1362 if (this.lastValue
!= value
) {
1363 this.callback(this.element
, value
);
1364 this.lastValue
= value
;
1369 Form
.Element
.Observer
= Class
.create();
1370 Form
.Element
.Observer
.prototype = Object
.extend(new Abstract
.TimedObserver(), {
1371 getValue: function() {
1372 return Form
.Element
.getValue(this.element
);
1376 Form
.Observer
= Class
.create();
1377 Form
.Observer
.prototype = Object
.extend(new Abstract
.TimedObserver(), {
1378 getValue: function() {
1379 return Form
.serialize(this.element
);
1383 /*--------------------------------------------------------------------------*/
1385 Abstract
.EventObserver = function() {}
1386 Abstract
.EventObserver
.prototype = {
1387 initialize: function(element
, callback
) {
1388 this.element
= $(element
);
1389 this.callback
= callback
;
1391 this.lastValue
= this.getValue();
1392 if (this.element
.tagName
.toLowerCase() == 'form')
1393 this.registerFormCallbacks();
1395 this.registerCallback(this.element
);
1398 onElementEvent: function() {
1399 var value
= this.getValue();
1400 if (this.lastValue
!= value
) {
1401 this.callback(this.element
, value
);
1402 this.lastValue
= value
;
1406 registerFormCallbacks: function() {
1407 var elements
= Form
.getElements(this.element
);
1408 for (var i
= 0; i
< elements
.length
; i
++)
1409 this.registerCallback(elements
[i
]);
1412 registerCallback: function(element
) {
1414 switch (element
.type
.toLowerCase()) {
1417 Event
.observe(element
, 'click', this.onElementEvent
.bind(this));
1423 case 'select-multiple':
1424 Event
.observe(element
, 'change', this.onElementEvent
.bind(this));
1431 Form
.Element
.EventObserver
= Class
.create();
1432 Form
.Element
.EventObserver
.prototype = Object
.extend(new Abstract
.EventObserver(), {
1433 getValue: function() {
1434 return Form
.Element
.getValue(this.element
);
1438 Form
.EventObserver
= Class
.create();
1439 Form
.EventObserver
.prototype = Object
.extend(new Abstract
.EventObserver(), {
1440 getValue: function() {
1441 return Form
.serialize(this.element
);
1444 if (!window
.Event
) {
1445 var Event
= new Object();
1448 Object
.extend(Event
, {
1459 element: function(event
) {
1460 return event
.target
|| event
.srcElement
;
1463 isLeftClick: function(event
) {
1464 return (((event
.which
) && (event
.which
== 1)) ||
1465 ((event
.button
) && (event
.button
== 1)));
1468 pointerX: function(event
) {
1469 return event
.pageX
|| (event
.clientX
+
1470 (document
.documentElement
.scrollLeft
|| document
.body
.scrollLeft
));
1473 pointerY: function(event
) {
1474 return event
.pageY
|| (event
.clientY
+
1475 (document
.documentElement
.scrollTop
|| document
.body
.scrollTop
));
1478 stop: function(event
) {
1479 if (event
.preventDefault
) {
1480 event
.preventDefault();
1481 event
.stopPropagation();
1483 event
.returnValue
= false;
1484 event
.cancelBubble
= true;
1488 // find the first node with the given tagName, starting from the
1489 // node the event was triggered on; traverses the DOM upwards
1490 findElement: function(event
, tagName
) {
1491 var element
= Event
.element(event
);
1492 while (element
.parentNode
&& (!element
.tagName
||
1493 (element
.tagName
.toUpperCase() != tagName
.toUpperCase())))
1494 element
= element
.parentNode
;
1500 _observeAndCache: function(element
, name
, observer
, useCapture
) {
1501 if (!this.observers
) this.observers
= [];
1502 if (element
.addEventListener
) {
1503 this.observers
.push([element
, name
, observer
, useCapture
]);
1504 element
.addEventListener(name
, observer
, useCapture
);
1505 } else if (element
.attachEvent
) {
1506 this.observers
.push([element
, name
, observer
, useCapture
]);
1507 element
.attachEvent('on' + name
, observer
);
1511 unloadCache: function() {
1512 if (!Event
.observers
) return;
1513 for (var i
= 0; i
< Event
.observers
.length
; i
++) {
1514 Event
.stopObserving
.apply(this, Event
.observers
[i
]);
1515 Event
.observers
[i
][0] = null;
1517 Event
.observers
= false;
1520 observe: function(element
, name
, observer
, useCapture
) {
1521 var element
= $(element
);
1522 useCapture
= useCapture
|| false;
1524 if (name
== 'keypress' &&
1525 (navigator
.appVersion
.match(/Konqueror|Safari|KHTML/)
1526 || element
.attachEvent
))
1529 this._observeAndCache(element
, name
, observer
, useCapture
);
1532 stopObserving: function(element
, name
, observer
, useCapture
) {
1533 var element
= $(element
);
1534 useCapture
= useCapture
|| false;
1536 if (name
== 'keypress' &&
1537 (navigator
.appVersion
.match(/Konqueror|Safari|KHTML/)
1538 || element
.detachEvent
))
1541 if (element
.removeEventListener
) {
1542 element
.removeEventListener(name
, observer
, useCapture
);
1543 } else if (element
.detachEvent
) {
1544 element
.detachEvent('on' + name
, observer
);
1549 /* prevent memory leaks in IE */
1550 Event
.observe(window
, 'unload', Event
.unloadCache
, false);
1552 // set to true if needed, warning: firefox performance problems
1553 // NOT neeeded for page scrolling, only if draggable contained in
1554 // scrollable elements
1555 includeScrollOffsets
: false,
1557 // must be called before calling withinIncludingScrolloffset, every time the
1559 prepare: function() {
1560 this.deltaX
= window
.pageXOffset
1561 || document
.documentElement
.scrollLeft
1562 || document
.body
.scrollLeft
1564 this.deltaY
= window
.pageYOffset
1565 || document
.documentElement
.scrollTop
1566 || document
.body
.scrollTop
1570 realOffset: function(element
) {
1571 var valueT
= 0, valueL
= 0;
1573 valueT
+= element
.scrollTop
|| 0;
1574 valueL
+= element
.scrollLeft
|| 0;
1575 element
= element
.parentNode
;
1577 return [valueL
, valueT
];
1580 cumulativeOffset: function(element
) {
1581 var valueT
= 0, valueL
= 0;
1583 valueT
+= element
.offsetTop
|| 0;
1584 valueL
+= element
.offsetLeft
|| 0;
1585 element
= element
.offsetParent
;
1587 return [valueL
, valueT
];
1590 positionedOffset: function(element
) {
1591 var valueT
= 0, valueL
= 0;
1593 valueT
+= element
.offsetTop
|| 0;
1594 valueL
+= element
.offsetLeft
|| 0;
1595 element
= element
.offsetParent
;
1597 p
= Element
.getStyle(element
, 'position');
1598 if (p
== 'relative' || p
== 'absolute') break;
1601 return [valueL
, valueT
];
1604 offsetParent: function(element
) {
1605 if (element
.offsetParent
) return element
.offsetParent
;
1606 if (element
== document
.body
) return element
;
1608 while ((element
= element
.parentNode
) && element
!= document
.body
)
1609 if (Element
.getStyle(element
, 'position') != 'static')
1612 return document
.body
;
1615 // caches x/y coordinate pair to use with overlap
1616 within: function(element
, x
, y
) {
1617 if (this.includeScrollOffsets
)
1618 return this.withinIncludingScrolloffsets(element
, x
, y
);
1621 this.offset
= this.cumulativeOffset(element
);
1623 return (y
>= this.offset
[1] &&
1624 y
< this.offset
[1] + element
.offsetHeight
&&
1625 x
>= this.offset
[0] &&
1626 x
< this.offset
[0] + element
.offsetWidth
);
1629 withinIncludingScrolloffsets: function(element
, x
, y
) {
1630 var offsetcache
= this.realOffset(element
);
1632 this.xcomp
= x
+ offsetcache
[0] - this.deltaX
;
1633 this.ycomp
= y
+ offsetcache
[1] - this.deltaY
;
1634 this.offset
= this.cumulativeOffset(element
);
1636 return (this.ycomp
>= this.offset
[1] &&
1637 this.ycomp
< this.offset
[1] + element
.offsetHeight
&&
1638 this.xcomp
>= this.offset
[0] &&
1639 this.xcomp
< this.offset
[0] + element
.offsetWidth
);
1642 // within must be called directly before
1643 overlap: function(mode
, element
) {
1644 if (!mode
) return 0;
1645 if (mode
== 'vertical')
1646 return ((this.offset
[1] + element
.offsetHeight
) - this.ycomp
) /
1647 element
.offsetHeight
;
1648 if (mode
== 'horizontal')
1649 return ((this.offset
[0] + element
.offsetWidth
) - this.xcomp
) /
1650 element
.offsetWidth
;
1653 clone: function(source
, target
) {
1656 target
.style
.position
= 'absolute';
1657 var offsets
= this.cumulativeOffset(source
);
1658 target
.style
.top
= offsets
[1] + 'px';
1659 target
.style
.left
= offsets
[0] + 'px';
1660 target
.style
.width
= source
.offsetWidth
+ 'px';
1661 target
.style
.height
= source
.offsetHeight
+ 'px';
1664 page: function(forElement
) {
1665 var valueT
= 0, valueL
= 0;
1667 var element
= forElement
;
1669 valueT
+= element
.offsetTop
|| 0;
1670 valueL
+= element
.offsetLeft
|| 0;
1673 if (element
.offsetParent
==document
.body
)
1674 if (Element
.getStyle(element
,'position')=='absolute') break;
1676 } while (element
= element
.offsetParent
);
1678 element
= forElement
;
1680 valueT
-= element
.scrollTop
|| 0;
1681 valueL
-= element
.scrollLeft
|| 0;
1682 } while (element
= element
.parentNode
);
1684 return [valueL
, valueT
];
1687 clone: function(source
, target
) {
1688 var options
= Object
.extend({
1695 }, arguments
[2] || {})
1697 // find page position of source
1699 var p
= Position
.page(source
);
1701 // find coordinate system to use
1705 // delta [0,0] will do fine with position: fixed elements,
1706 // position:absolute needs offsetParent deltas
1707 if (Element
.getStyle(target
,'position') == 'absolute') {
1708 parent
= Position
.offsetParent(target
);
1709 delta
= Position
.page(parent
);
1712 // correct by body offsets (fixes Safari)
1713 if (parent
== document
.body
) {
1714 delta
[0] -= document
.body
.offsetLeft
;
1715 delta
[1] -= document
.body
.offsetTop
;
1719 if(options
.setLeft
) target
.style
.left
= (p
[0] - delta
[0] + options
.offsetLeft
) + 'px';
1720 if(options
.setTop
) target
.style
.top
= (p
[1] - delta
[1] + options
.offsetTop
) + 'px';
1721 if(options
.setWidth
) target
.style
.width
= source
.offsetWidth
+ 'px';
1722 if(options
.setHeight
) target
.style
.height
= source
.offsetHeight
+ 'px';
1725 absolutize: function(element
) {
1726 element
= $(element
);
1727 if (element
.style
.position
== 'absolute') return;
1730 var offsets
= Position
.positionedOffset(element
);
1731 var top
= offsets
[1];
1732 var left
= offsets
[0];
1733 var width
= element
.clientWidth
;
1734 var height
= element
.clientHeight
;
1736 element
._originalLeft
= left
- parseFloat(element
.style
.left
|| 0);
1737 element
._originalTop
= top
- parseFloat(element
.style
.top
|| 0);
1738 element
._originalWidth
= element
.style
.width
;
1739 element
._originalHeight
= element
.style
.height
;
1741 element
.style
.position
= 'absolute';
1742 element
.style
.top
= top
+ 'px';;
1743 element
.style
.left
= left
+ 'px';;
1744 element
.style
.width
= width
+ 'px';;
1745 element
.style
.height
= height
+ 'px';;
1748 relativize: function(element
) {
1749 element
= $(element
);
1750 if (element
.style
.position
== 'relative') return;
1753 element
.style
.position
= 'relative';
1754 var top
= parseFloat(element
.style
.top
|| 0) - (element
._originalTop
|| 0);
1755 var left
= parseFloat(element
.style
.left
|| 0) - (element
._originalLeft
|| 0);
1757 element
.style
.top
= top
+ 'px';
1758 element
.style
.left
= left
+ 'px';
1759 element
.style
.height
= element
._originalHeight
;
1760 element
.style
.width
= element
._originalWidth
;
1764 // Safari returns margins on body which is incorrect if the child is absolutely
1765 // positioned. For performance reasons, redefine Position.cumulativeOffset for
1766 // KHTML/WebKit only.
1767 if (/Konqueror|Safari|KHTML/.test(navigator
.userAgent
)) {
1768 Position
.cumulativeOffset = function(element
) {
1769 var valueT
= 0, valueL
= 0;
1771 valueT
+= element
.offsetTop
|| 0;
1772 valueL
+= element
.offsetLeft
|| 0;
1773 if (element
.offsetParent
== document
.body
)
1774 if (Element
.getStyle(element
, 'position') == 'absolute') break;
1776 element
= element
.offsetParent
;
1779 return [valueL
, valueT
];