1 /* Prototype JavaScript framework, version 1.5.0_rc0
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 (var 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;
119 Object.extend(String.prototype, {
120 gsub: function(pattern, replacement) {
121 var result = '', source = this, match;
122 replacement = arguments.callee.prepareReplacement(replacement);
124 while (source.length > 0) {
125 if (match = source.match(pattern)) {
126 result += source.slice(0, match.index);
127 result += (replacement(match) || '').toString();
128 source = source.slice(match.index + match[0].length);
130 result += source, source = '';
136 sub: function(pattern, replacement, count) {
137 replacement = this.gsub.prepareReplacement(replacement);
138 count = count === undefined ? 1 : count;
140 return this.gsub(pattern, function(match) {
141 if (--count < 0) return match[0];
142 return replacement(match);
146 scan: function(pattern, iterator) {
147 this.gsub(pattern, iterator);
151 truncate: function(length, truncation) {
152 length = length || 30;
153 truncation = truncation === undefined ? '...' : truncation;
154 return this.length > length ?
155 this.slice(0, length - truncation.length) + truncation : this;
159 return this.replace(/^\s+/, '').replace(/\s+$/, '');
162 stripTags: function() {
163 return this.replace(/<\/?[^>]+>/gi, '');
166 stripScripts: function() {
167 return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
170 extractScripts: function() {
171 var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
172 var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
173 return (this.match(matchAll) || []).map(function(scriptTag) {
174 return (scriptTag.match(matchOne) || ['', ''])[1];
178 evalScripts: function() {
179 return this.extractScripts().map(function(script) { return eval(script) });
182 escapeHTML: function() {
183 var div = document.createElement('div');
184 var text = document.createTextNode(this);
185 div.appendChild(text);
186 return div.innerHTML;
189 unescapeHTML: function() {
190 var div = document.createElement('div');
191 div.innerHTML = this.stripTags();
192 return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
195 toQueryParams: function() {
196 var pairs = this.match(/^\??(.*)$/)[1].split('&');
197 return pairs.inject({}, function(params, pairString) {
198 var pair = pairString.split('=');
199 params[pair[0]] = pair[1];
204 toArray: function() {
205 return this.split('');
208 camelize: function() {
209 var oStringList = this.split('-');
210 if (oStringList.length == 1) return oStringList[0];
212 var camelizedString = this.indexOf('-') == 0
213 ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
216 for (var i = 1, len = oStringList.length; i < len; i++) {
217 var s = oStringList[i];
218 camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
221 return camelizedString;
224 inspect: function() {
225 return "'" + this.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + "'";
229 String.prototype.gsub.prepareReplacement = function(replacement) {
230 if (typeof replacement == 'function') return replacement;
231 var template = new Template(replacement);
232 return function(match) { return template.evaluate(match) };
235 String.prototype.parseQuery = String.prototype.toQueryParams;
237 var Template = Class.create();
238 Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
239 Template.prototype = {
240 initialize: function(template, pattern) {
241 this.template = template.toString();
242 this.pattern = pattern || Template.Pattern;
245 evaluate: function(object) {
246 return this.template.gsub(this.pattern, function(match) {
247 var before = match[1];
248 if (before == '\\') return match[2];
249 return before + (object[match[3]] || '').toString();
254 var $break = new Object();
255 var $continue = new Object();
258 each: function(iterator) {
261 this._each(function(value) {
263 iterator(value, index++);
265 if (e != $continue) throw e;
269 if (e != $break) throw e;
273 all: function(iterator) {
275 this.each(function(value, index) {
276 result = result && !!(iterator || Prototype.K)(value, index);
277 if (!result) throw $break;
282 any: function(iterator) {
284 this.each(function(value, index) {
285 if (result = !!(iterator || Prototype.K)(value, index))
291 collect: function(iterator) {
293 this.each(function(value, index) {
294 results.push(iterator(value, index));
299 detect: function (iterator) {
301 this.each(function(value, index) {
302 if (iterator(value, index)) {
310 findAll: function(iterator) {
312 this.each(function(value, index) {
313 if (iterator(value, index))
319 grep: function(pattern, iterator) {
321 this.each(function(value, index) {
322 var stringValue = value.toString();
323 if (stringValue.match(pattern))
324 results.push((iterator || Prototype.K)(value, index));
329 include: function(object) {
331 this.each(function(value) {
332 if (value == object) {
340 inject: function(memo, iterator) {
341 this.each(function(value, index) {
342 memo = iterator(memo, value, index);
347 invoke: function(method) {
348 var args = $A(arguments).slice(1);
349 return this.collect(function(value) {
350 return value[method].apply(value, args);
354 max: function(iterator) {
356 this.each(function(value, index) {
357 value = (iterator || Prototype.K)(value, index);
358 if (result == undefined || value >= result)
364 min: function(iterator) {
366 this.each(function(value, index) {
367 value = (iterator || Prototype.K)(value, index);
368 if (result == undefined || value < result)
374 partition: function(iterator) {
375 var trues = [], falses = [];
376 this.each(function(value, index) {
377 ((iterator || Prototype.K)(value, index) ?
378 trues : falses).push(value);
380 return [trues, falses];
383 pluck: function(property) {
385 this.each(function(value, index) {
386 results.push(value[property]);
391 reject: function(iterator) {
393 this.each(function(value, index) {
394 if (!iterator(value, index))
400 sortBy: function(iterator) {
401 return this.collect(function(value, index) {
402 return {value: value, criteria: iterator(value, index)};
403 }).sort(function(left, right) {
404 var a = left.criteria, b = right.criteria;
405 return a < b ? -1 : a > b ? 1 : 0;
409 toArray: function() {
410 return this.collect(Prototype.K);
414 var iterator = Prototype.K, args = $A(arguments);
415 if (typeof args.last() == 'function')
416 iterator = args.pop();
418 var collections = [this].concat(args).map($A);
419 return this.map(function(value, index) {
420 return iterator(collections.pluck(index));
424 inspect: function() {
425 return '#<Enumerable:' + this.toArray().inspect() + '>';
429 Object.extend(Enumerable, {
430 map: Enumerable.collect,
431 find: Enumerable.detect,
432 select: Enumerable.findAll,
433 member: Enumerable.include,
434 entries: Enumerable.toArray
436 var $A = Array.from = function(iterable) {
437 if (!iterable) return [];
438 if (iterable.toArray) {
439 return iterable.toArray();
442 for (var i = 0; i < iterable.length; i++)
443 results.push(iterable[i]);
448 Object.extend(Array.prototype, Enumerable);
450 if (!Array.prototype._reverse)
451 Array.prototype._reverse = Array.prototype.reverse;
453 Object.extend(Array.prototype, {
454 _each: function(iterator) {
455 for (var i = 0; i < this.length; i++)
469 return this[this.length - 1];
472 compact: function() {
473 return this.select(function(value) {
474 return value != undefined || value != null;
478 flatten: function() {
479 return this.inject([], function(array, value) {
480 return array.concat(value && value.constructor == Array ?
481 value.flatten() : [value]);
485 without: function() {
486 var values = $A(arguments);
487 return this.select(function(value) {
488 return !values.include(value);
492 indexOf: function(object) {
493 for (var i = 0; i < this.length; i++)
494 if (this[i] == object) return i;
498 reverse: function(inline) {
499 return (inline !== false ? this : this.toArray())._reverse();
502 inspect: function() {
503 return '[' + this.map(Object.inspect).join(', ') + ']';
507 _each: function(iterator) {
508 for (var key in this) {
509 var value = this[key];
510 if (typeof value == 'function') continue;
512 var pair = [key, value];
520 return this.pluck('key');
524 return this.pluck('value');
527 merge: function(hash) {
528 return $H(hash).inject($H(this), function(mergedHash, pair) {
529 mergedHash[pair.key] = pair.value;
534 toQueryString: function() {
535 return this.map(function(pair) {
536 return pair.map(encodeURIComponent).join('=');
540 inspect: function() {
541 return '#<Hash:{' + this.map(function(pair) {
542 return pair.map(Object.inspect).join(': ');
543 }).join(', ') + '}>';
547 function $H(object) {
548 var hash = Object.extend({}, object || {});
549 Object.extend(hash, Enumerable);
550 Object.extend(hash, Hash);
553 ObjectRange = Class.create();
554 Object.extend(ObjectRange.prototype, Enumerable);
555 Object.extend(ObjectRange.prototype, {
556 initialize: function(start, end, exclusive) {
559 this.exclusive = exclusive;
562 _each: function(iterator) {
563 var value = this.start;
566 value = value.succ();
567 } while (this.include(value));
570 include: function(value) {
571 if (value < this.start)
574 return value < this.end;
575 return value <= this.end;
579 var $R = function(start, end, exclusive) {
580 return new ObjectRange(start, end, exclusive);
584 getTransport: function() {
586 function() {return new XMLHttpRequest()},
587 function() {return new ActiveXObject('Msxml2.XMLHTTP')},
588 function() {return new ActiveXObject('Microsoft.XMLHTTP')}
592 activeRequestCount: 0
598 _each: function(iterator) {
599 this.responders._each(iterator);
602 register: function(responderToAdd) {
603 if (!this.include(responderToAdd))
604 this.responders.push(responderToAdd);
607 unregister: function(responderToRemove) {
608 this.responders = this.responders.without(responderToRemove);
611 dispatch: function(callback, request, transport, json) {
612 this.each(function(responder) {
613 if (responder[callback] && typeof responder[callback] == 'function') {
615 responder[callback].apply(responder, [request, transport, json]);
622 Object.extend(Ajax.Responders, Enumerable);
624 Ajax.Responders.register({
625 onCreate: function() {
626 Ajax.activeRequestCount++;
629 onComplete: function() {
630 Ajax.activeRequestCount--;
634 Ajax.Base = function() {};
635 Ajax.Base.prototype = {
636 setOptions: function(options) {
640 contentType: 'application/x-www-form-urlencoded',
643 Object.extend(this.options, options || {});
646 responseIsSuccess: function() {
647 return this.transport.status == undefined
648 || this.transport.status == 0
649 || (this.transport.status >= 200 && this.transport.status < 300);
652 responseIsFailure: function() {
653 return !this.responseIsSuccess();
657 Ajax.Request = Class.create();
658 Ajax.Request.Events =
659 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
661 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
662 initialize: function(url, options) {
663 this.transport = Ajax.getTransport();
664 this.setOptions(options);
668 request: function(url) {
669 var parameters = this.options.parameters || '';
670 if (parameters.length > 0) parameters += '&_=';
674 if (this.options.method == 'get' && parameters.length > 0)
675 this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
677 Ajax.Responders.dispatch('onCreate', this, this.transport);
679 this.transport.open(this.options.method, this.url,
680 this.options.asynchronous);
682 if (this.options.asynchronous) {
683 this.transport.onreadystatechange = this.onStateChange.bind(this);
684 setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
687 this.setRequestHeaders();
689 var body = this.options.postBody ? this.options.postBody : parameters;
690 this.transport.send(this.options.method == 'post' ? body : null);
693 this.dispatchException(e);
697 setRequestHeaders: function() {
699 ['X-Requested-With', 'XMLHttpRequest',
700 'X-Prototype-Version', Prototype.Version,
701 'Accept', 'text/javascript, text/html, application/xml, text/xml, */*'];
703 if (this.options.method == 'post') {
704 requestHeaders.push('Content-type', this.options.contentType);
706 /* Force "Connection: close" for Mozilla browsers to work around
707 * a bug where XMLHttpReqeuest sends an incorrect Content-length
708 * header. See Mozilla Bugzilla #246651.
710 if (this.transport.overrideMimeType)
711 requestHeaders.push('Connection', 'close');
714 if (this.options.requestHeaders)
715 requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
717 for (var i = 0; i < requestHeaders.length; i += 2)
718 this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
721 onStateChange: function() {
722 var readyState = this.transport.readyState;
724 this.respondToReadyState(this.transport.readyState);
727 header: function(name) {
729 return this.transport.getResponseHeader(name);
733 evalJSON: function() {
735 return eval('(' + this.header('X-JSON') + ')');
739 evalResponse: function() {
741 return eval(this.transport.responseText);
743 this.dispatchException(e);
747 respondToReadyState: function(readyState) {
748 var event = Ajax.Request.Events[readyState];
749 var transport = this.transport, json = this.evalJSON();
751 if (event == 'Complete') {
753 (this.options['on' + this.transport.status]
754 || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
755 || Prototype.emptyFunction)(transport, json);
757 this.dispatchException(e);
760 if ((this.header('Content-type') || '').match(/^text\/javascript/i))
765 (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
766 Ajax.Responders.dispatch('on' + event, this, transport, json);
768 this.dispatchException(e);
771 /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
772 if (event == 'Complete')
773 this.transport.onreadystatechange = Prototype.emptyFunction;
776 dispatchException: function(exception) {
777 (this.options.onException || Prototype.emptyFunction)(this, exception);
778 Ajax.Responders.dispatch('onException', this, exception);
782 Ajax.Updater = Class.create();
784 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
785 initialize: function(container, url, options) {
787 success: container.success ? $(container.success) : $(container),
788 failure: container.failure ? $(container.failure) :
789 (container.success ? null : $(container))
792 this.transport = Ajax.getTransport();
793 this.setOptions(options);
795 var onComplete = this.options.onComplete || Prototype.emptyFunction;
796 this.options.onComplete = (function(transport, object) {
797 this.updateContent();
798 onComplete(transport, object);
804 updateContent: function() {
805 var receiver = this.responseIsSuccess() ?
806 this.containers.success : this.containers.failure;
807 var response = this.transport.responseText;
809 if (!this.options.evalScripts)
810 response = response.stripScripts();
813 if (this.options.insertion) {
814 new this.options.insertion(receiver, response);
816 Element.update(receiver, response);
820 if (this.responseIsSuccess()) {
822 setTimeout(this.onComplete.bind(this), 10);
827 Ajax.PeriodicalUpdater = Class.create();
828 Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
829 initialize: function(container, url, options) {
830 this.setOptions(options);
831 this.onComplete = this.options.onComplete;
833 this.frequency = (this.options.frequency || 2);
834 this.decay = (this.options.decay || 1);
837 this.container = container;
844 this.options.onComplete = this.updateComplete.bind(this);
849 this.updater.onComplete = undefined;
850 clearTimeout(this.timer);
851 (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
854 updateComplete: function(request) {
855 if (this.options.decay) {
856 this.decay = (request.responseText == this.lastText ?
857 this.decay * this.options.decay : 1);
859 this.lastText = request.responseText;
861 this.timer = setTimeout(this.onTimerEvent.bind(this),
862 this.decay * this.frequency * 1000);
865 onTimerEvent: function() {
866 this.updater = new Ajax.Updater(this.container, this.url, this.options);
870 var results = [], element;
871 for (var i = 0; i < arguments.length; i++) {
872 element = arguments[i];
873 if (typeof element == 'string')
874 element = document.getElementById(element);
875 results.push(Element.extend(element));
877 return results.length < 2 ? results[0] : results;
880 document.getElementsByClassName = function(className, parentElement) {
881 var children = ($(parentElement) || document.body).getElementsByTagName('*');
882 return $A(children).inject([], function(elements, child) {
883 if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
884 elements.push(Element.extend(child));
889 /*--------------------------------------------------------------------------*/
892 var Element = new Object();
894 Element.extend = function(element) {
895 if (!element) return;
896 if (_nativeExtensions) return element;
898 if (!element._extended && element.tagName && element != window) {
899 var methods = Element.Methods, cache = Element.extend.cache;
900 for (property in methods) {
901 var value = methods[property];
902 if (typeof value == 'function')
903 element[property] = cache.findOrStore(value);
907 element._extended = true;
911 Element.extend.cache = {
912 findOrStore: function(value) {
913 return this[value] = this[value] || function() {
914 return value.apply(null, [this].concat($A(arguments)));
920 visible: function(element) {
921 return $(element).style.display != 'none';
925 for (var i = 0; i < arguments.length; i++) {
926 var element = $(arguments[i]);
927 Element[Element.visible(element) ? 'hide' : 'show'](element);
932 for (var i = 0; i < arguments.length; i++) {
933 var element = $(arguments[i]);
934 element.style.display = 'none';
939 for (var i = 0; i < arguments.length; i++) {
940 var element = $(arguments[i]);
941 element.style.display = '';
945 remove: function(element) {
946 element = $(element);
947 element.parentNode.removeChild(element);
950 update: function(element, html) {
951 $(element).innerHTML = html.stripScripts();
952 setTimeout(function() {html.evalScripts()}, 10);
955 replace: function(element, html) {
956 element = $(element);
957 if (element.outerHTML) {
958 element.outerHTML = html.stripScripts();
960 var range = element.ownerDocument.createRange();
961 range.selectNodeContents(element);
962 element.parentNode.replaceChild(
963 range.createContextualFragment(html.stripScripts()), element);
965 setTimeout(function() {html.evalScripts()}, 10);
968 getHeight: function(element) {
969 element = $(element);
970 return element.offsetHeight;
973 classNames: function(element) {
974 return new Element.ClassNames(element);
977 hasClassName: function(element, className) {
978 if (!(element = $(element))) return;
979 return Element.classNames(element).include(className);
982 addClassName: function(element, className) {
983 if (!(element = $(element))) return;
984 return Element.classNames(element).add(className);
987 removeClassName: function(element, className) {
988 if (!(element = $(element))) return;
989 return Element.classNames(element).remove(className);
992 // removes whitespace-only text node children
993 cleanWhitespace: function(element) {
994 element = $(element);
995 for (var i = 0; i < element.childNodes.length; i++) {
996 var node = element.childNodes[i];
997 if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
998 Element.remove(node);
1002 empty: function(element) {
1003 return $(element).innerHTML.match(/^\s*$/);
1006 childOf: function(element, ancestor) {
1007 element = $(element), ancestor = $(ancestor);
1008 while (element = element.parentNode)
1009 if (element == ancestor) return true;
1013 scrollTo: function(element) {
1014 element = $(element);
1015 var x = element.x ? element.x : element.offsetLeft,
1016 y = element.y ? element.y : element.offsetTop;
1017 window.scrollTo(x, y);
1020 getStyle: function(element, style) {
1021 element = $(element);
1022 var value = element.style[style.camelize()];
1024 if (document.defaultView && document.defaultView.getComputedStyle) {
1025 var css = document.defaultView.getComputedStyle(element, null);
1026 value = css ? css.getPropertyValue(style) : null;
1027 } else if (element.currentStyle) {
1028 value = element.currentStyle[style.camelize()];
1032 if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
1033 if (Element.getStyle(element, 'position') == 'static') value = 'auto';
1035 return value == 'auto' ? null : value;
1038 setStyle: function(element, style) {
1039 element = $(element);
1040 for (var name in style)
1041 element.style[name.camelize()] = style[name];
1044 getDimensions: function(element) {
1045 element = $(element);
1046 if (Element.getStyle(element, 'display') != 'none')
1047 return {width: element.offsetWidth, height: element.offsetHeight};
1049 // All *Width and *Height properties give 0 on elements with display none,
1050 // so enable the element temporarily
1051 var els = element.style;
1052 var originalVisibility = els.visibility;
1053 var originalPosition = els.position;
1054 els.visibility = 'hidden';
1055 els.position = 'absolute';
1057 var originalWidth = element.clientWidth;
1058 var originalHeight = element.clientHeight;
1059 els.display = 'none';
1060 els.position = originalPosition;
1061 els.visibility = originalVisibility;
1062 return {width: originalWidth, height: originalHeight};
1065 makePositioned: function(element) {
1066 element = $(element);
1067 var pos = Element.getStyle(element, 'position');
1068 if (pos == 'static' || !pos) {
1069 element._madePositioned = true;
1070 element.style.position = 'relative';
1071 // Opera returns the offset relative to the positioning context, when an
1072 // element is position relative but top and left have not been defined
1074 element.style.top = 0;
1075 element.style.left = 0;
1080 undoPositioned: function(element) {
1081 element = $(element);
1082 if (element._madePositioned) {
1083 element._madePositioned = undefined;
1084 element.style.position =
1086 element.style.left =
1087 element.style.bottom =
1088 element.style.right = '';
1092 makeClipping: function(element) {
1093 element = $(element);
1094 if (element._overflow) return;
1095 element._overflow = element.style.overflow;
1096 if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
1097 element.style.overflow = 'hidden';
1100 undoClipping: function(element) {
1101 element = $(element);
1102 if (element._overflow) return;
1103 element.style.overflow = element._overflow;
1104 element._overflow = undefined;
1108 Object.extend(Element, Element.Methods);
1110 var _nativeExtensions = false;
1112 if(!HTMLElement && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
1113 var HTMLElement = {}
1114 HTMLElement.prototype = document.createElement('div').__proto__;
1117 Element.addMethods = function(methods) {
1118 Object.extend(Element.Methods, methods || {});
1120 if(typeof HTMLElement != 'undefined') {
1121 var methods = Element.Methods, cache = Element.extend.cache;
1122 for (property in methods) {
1123 var value = methods[property];
1124 if (typeof value == 'function')
1125 HTMLElement.prototype[property] = cache.findOrStore(value);
1127 _nativeExtensions = true;
1131 Element.addMethods();
1133 var Toggle = new Object();
1134 Toggle.display = Element.toggle;
1136 /*--------------------------------------------------------------------------*/
1138 Abstract.Insertion = function(adjacency) {
1139 this.adjacency = adjacency;
1142 Abstract.Insertion.prototype = {
1143 initialize: function(element, content) {
1144 this.element = $(element);
1145 this.content = content.stripScripts();
1147 if (this.adjacency && this.element.insertAdjacentHTML) {
1149 this.element.insertAdjacentHTML(this.adjacency, this.content);
1151 var tagName = this.element.tagName.toLowerCase();
1152 if (tagName == 'tbody' || tagName == 'tr') {
1153 this.insertContent(this.contentFromAnonymousTable());
1159 this.range = this.element.ownerDocument.createRange();
1160 if (this.initializeRange) this.initializeRange();
1161 this.insertContent([this.range.createContextualFragment(this.content)]);
1164 setTimeout(function() {content.evalScripts()}, 10);
1167 contentFromAnonymousTable: function() {
1168 var div = document.createElement('div');
1169 div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
1170 return $A(div.childNodes[0].childNodes[0].childNodes);
1174 var Insertion = new Object();
1176 Insertion.Before = Class.create();
1177 Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
1178 initializeRange: function() {
1179 this.range.setStartBefore(this.element);
1182 insertContent: function(fragments) {
1183 fragments.each((function(fragment) {
1184 this.element.parentNode.insertBefore(fragment, this.element);
1189 Insertion.Top = Class.create();
1190 Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
1191 initializeRange: function() {
1192 this.range.selectNodeContents(this.element);
1193 this.range.collapse(true);
1196 insertContent: function(fragments) {
1197 fragments.reverse(false).each((function(fragment) {
1198 this.element.insertBefore(fragment, this.element.firstChild);
1203 Insertion.Bottom = Class.create();
1204 Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
1205 initializeRange: function() {
1206 this.range.selectNodeContents(this.element);
1207 this.range.collapse(this.element);
1210 insertContent: function(fragments) {
1211 fragments.each((function(fragment) {
1212 this.element.appendChild(fragment);
1217 Insertion.After = Class.create();
1218 Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
1219 initializeRange: function() {
1220 this.range.setStartAfter(this.element);
1223 insertContent: function(fragments) {
1224 fragments.each((function(fragment) {
1225 this.element.parentNode.insertBefore(fragment,
1226 this.element.nextSibling);
1231 /*--------------------------------------------------------------------------*/
1233 Element.ClassNames = Class.create();
1234 Element.ClassNames.prototype = {
1235 initialize: function(element) {
1236 this.element = $(element);
1239 _each: function(iterator) {
1240 this.element.className.split(/\s+/).select(function(name) {
1241 return name.length > 0;
1245 set: function(className) {
1246 this.element.className = className;
1249 add: function(classNameToAdd) {
1250 if (this.include(classNameToAdd)) return;
1251 this.set(this.toArray().concat(classNameToAdd).join(' '));
1254 remove: function(classNameToRemove) {
1255 if (!this.include(classNameToRemove)) return;
1256 this.set(this.select(function(className) {
1257 return className != classNameToRemove;
1261 toString: function() {
1262 return this.toArray().join(' ');
1266 Object.extend(Element.ClassNames.prototype, Enumerable);
1267 var Selector = Class.create();
1268 Selector.prototype = {
1269 initialize: function(expression) {
1270 this.params = {classNames: []};
1271 this.expression = expression.toString().strip();
1272 this.parseExpression();
1273 this.compileMatcher();
1276 parseExpression: function() {
1277 function abort(message) { throw 'Parse error in selector: ' + message; }
1279 if (this.expression == '') abort('empty expression');
1281 var params = this.params, expr = this.expression, match, modifier, clause, rest;
1282 while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
1283 params.attributes = params.attributes || [];
1284 params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
1288 if (expr == '*') return this.params.wildcard = true;
1290 while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
1291 modifier = match[1], clause = match[2], rest = match[3];
1293 case '#': params.id = clause; break;
1294 case '.': params.classNames.push(clause); break;
1296 case undefined: params.tagName = clause.toUpperCase(); break;
1297 default: abort(expr.inspect());
1302 if (expr.length > 0) abort(expr.inspect());
1305 buildMatchExpression: function() {
1306 var params = this.params, conditions = [], clause;
1308 if (params.wildcard)
1309 conditions.push('true');
1310 if (clause = params.id)
1311 conditions.push('element.id == ' + clause.inspect());
1312 if (clause = params.tagName)
1313 conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
1314 if ((clause = params.classNames).length > 0)
1315 for (var i = 0; i < clause.length; i++)
1316 conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')');
1317 if (clause = params.attributes) {
1318 clause.each(function(attribute) {
1319 var value = 'element.getAttribute(' + attribute.name.inspect() + ')';
1320 var splitValueBy = function(delimiter) {
1321 return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
1324 switch (attribute.operator) {
1325 case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break;
1326 case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
1327 case '|=': conditions.push(
1328 splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
1330 case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break;
1332 case undefined: conditions.push(value + ' != null'); break;
1333 default: throw 'Unknown operator ' + attribute.operator + ' in selector';
1338 return conditions.join(' && ');
1341 compileMatcher: function() {
1342 this.match = new Function('element', 'if (!element.tagName) return false; \
1343 return ' + this.buildMatchExpression());
1346 findElements: function(scope) {
1349 if (element = $(this.params.id))
1350 if (this.match(element))
1351 if (!scope || Element.childOf(element, scope))
1354 scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
1357 for (var i = 0; i < scope.length; i++)
1358 if (this.match(element = scope[i]))
1359 results.push(Element.extend(element));
1364 toString: function() {
1365 return this.expression;
1370 return $A(arguments).map(function(expression) {
1371 return expression.strip().split(/\s+/).inject([null], function(results, expr) {
1372 var selector = new Selector(expr);
1373 return results.map(selector.findElements.bind(selector)).flatten();
1379 for (var i = 0; i < arguments.length; i++)
1380 $(arguments[i]).value = '';
1383 focus: function(element) {
1387 present: function() {
1388 for (var i = 0; i < arguments.length; i++)
1389 if ($(arguments[i]).value == '') return false;
1393 select: function(element) {
1394 $(element).select();
1397 activate: function(element) {
1398 element = $(element);
1405 /*--------------------------------------------------------------------------*/
1408 serialize: function(form) {
1409 var elements = Form.getElements($(form));
1410 var queryComponents = new Array();
1412 for (var i = 0; i < elements.length; i++) {
1413 var queryComponent = Form.Element.serialize(elements[i]);
1415 queryComponents.push(queryComponent);
1418 return queryComponents.join('&');
1421 getElements: function(form) {
1423 var elements = new Array();
1425 for (var tagName in Form.Element.Serializers) {
1426 var tagElements = form.getElementsByTagName(tagName);
1427 for (var j = 0; j < tagElements.length; j++)
1428 elements.push(tagElements[j]);
1433 getInputs: function(form, typeName, name) {
1435 var inputs = form.getElementsByTagName('input');
1437 if (!typeName && !name)
1440 var matchingInputs = new Array();
1441 for (var i = 0; i < inputs.length; i++) {
1442 var input = inputs[i];
1443 if ((typeName && input.type != typeName) ||
1444 (name && input.name != name))
1446 matchingInputs.push(input);
1449 return matchingInputs;
1452 disable: function(form) {
1453 var elements = Form.getElements(form);
1454 for (var i = 0; i < elements.length; i++) {
1455 var element = elements[i];
1457 element.disabled = 'true';
1461 enable: function(form) {
1462 var elements = Form.getElements(form);
1463 for (var i = 0; i < elements.length; i++) {
1464 var element = elements[i];
1465 element.disabled = '';
1469 findFirstElement: function(form) {
1470 return Form.getElements(form).find(function(element) {
1471 return element.type != 'hidden' && !element.disabled &&
1472 ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
1476 focusFirstElement: function(form) {
1477 Field.activate(Form.findFirstElement(form));
1480 reset: function(form) {
1486 serialize: function(element) {
1487 element = $(element);
1488 var method = element.tagName.toLowerCase();
1489 var parameter = Form.Element.Serializers[method](element);
1492 var key = encodeURIComponent(parameter[0]);
1493 if (key.length == 0) return;
1495 if (parameter[1].constructor != Array)
1496 parameter[1] = [parameter[1]];
1498 return parameter[1].map(function(value) {
1499 return key + '=' + encodeURIComponent(value);
1504 getValue: function(element) {
1505 element = $(element);
1506 var method = element.tagName.toLowerCase();
1507 var parameter = Form.Element.Serializers[method](element);
1510 return parameter[1];
1514 Form.Element.Serializers = {
1515 input: function(element) {
1516 switch (element.type.toLowerCase()) {
1521 return Form.Element.Serializers.textarea(element);
1524 return Form.Element.Serializers.inputSelector(element);
1529 inputSelector: function(element) {
1530 if (element.checked)
1531 return [element.name, element.value];
1534 textarea: function(element) {
1535 return [element.name, element.value];
1538 select: function(element) {
1539 return Form.Element.Serializers[element.type == 'select-one' ?
1540 'selectOne' : 'selectMany'](element);
1543 selectOne: function(element) {
1544 var value = '', opt, index = element.selectedIndex;
1546 opt = element.options[index];
1547 value = opt.value || opt.text;
1549 return [element.name, value];
1552 selectMany: function(element) {
1554 for (var i = 0; i < element.length; i++) {
1555 var opt = element.options[i];
1557 value.push(opt.value || opt.text);
1559 return [element.name, value];
1563 /*--------------------------------------------------------------------------*/
1565 var $F = Form.Element.getValue;
1567 /*--------------------------------------------------------------------------*/
1569 Abstract.TimedObserver = function() {}
1570 Abstract.TimedObserver.prototype = {
1571 initialize: function(element, frequency, callback) {
1572 this.frequency = frequency;
1573 this.element = $(element);
1574 this.callback = callback;
1576 this.lastValue = this.getValue();
1577 this.registerCallback();
1580 registerCallback: function() {
1581 setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
1584 onTimerEvent: function() {
1585 var value = this.getValue();
1586 if (this.lastValue != value) {
1587 this.callback(this.element, value);
1588 this.lastValue = value;
1593 Form.Element.Observer = Class.create();
1594 Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
1595 getValue: function() {
1596 return Form.Element.getValue(this.element);
1600 Form.Observer = Class.create();
1601 Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
1602 getValue: function() {
1603 return Form.serialize(this.element);
1607 /*--------------------------------------------------------------------------*/
1609 Abstract.EventObserver = function() {}
1610 Abstract.EventObserver.prototype = {
1611 initialize: function(element, callback) {
1612 this.element = $(element);
1613 this.callback = callback;
1615 this.lastValue = this.getValue();
1616 if (this.element.tagName.toLowerCase() == 'form')
1617 this.registerFormCallbacks();
1619 this.registerCallback(this.element);
1622 onElementEvent: function() {
1623 var value = this.getValue();
1624 if (this.lastValue != value) {
1625 this.callback(this.element, value);
1626 this.lastValue = value;
1630 registerFormCallbacks: function() {
1631 var elements = Form.getElements(this.element);
1632 for (var i = 0; i < elements.length; i++)
1633 this.registerCallback(elements[i]);
1636 registerCallback: function(element) {
1638 switch (element.type.toLowerCase()) {
1641 Event.observe(element, 'click', this.onElementEvent.bind(this));
1647 case 'select-multiple':
1648 Event.observe(element, 'change', this.onElementEvent.bind(this));
1655 Form.Element.EventObserver = Class.create();
1656 Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
1657 getValue: function() {
1658 return Form.Element.getValue(this.element);
1662 Form.EventObserver = Class.create();
1663 Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
1664 getValue: function() {
1665 return Form.serialize(this.element);
1668 if (!window.Event) {
1669 var Event = new Object();
1672 Object.extend(Event, {
1683 element: function(event) {
1684 return event.target || event.srcElement;
1687 isLeftClick: function(event) {
1688 return (((event.which) && (event.which == 1)) ||
1689 ((event.button) && (event.button == 1)));
1692 pointerX: function(event) {
1693 return event.pageX || (event.clientX +
1694 (document.documentElement.scrollLeft || document.body.scrollLeft));
1697 pointerY: function(event) {
1698 return event.pageY || (event.clientY +
1699 (document.documentElement.scrollTop || document.body.scrollTop));
1702 stop: function(event) {
1703 if (event.preventDefault) {
1704 event.preventDefault();
1705 event.stopPropagation();
1707 event.returnValue = false;
1708 event.cancelBubble = true;
1712 // find the first node with the given tagName, starting from the
1713 // node the event was triggered on; traverses the DOM upwards
1714 findElement: function(event, tagName) {
1715 var element = Event.element(event);
1716 while (element.parentNode && (!element.tagName ||
1717 (element.tagName.toUpperCase() != tagName.toUpperCase())))
1718 element = element.parentNode;
1724 _observeAndCache: function(element, name, observer, useCapture) {
1725 if (!this.observers) this.observers = [];
1726 if (element.addEventListener) {
1727 this.observers.push([element, name, observer, useCapture]);
1728 element.addEventListener(name, observer, useCapture);
1729 } else if (element.attachEvent) {
1730 this.observers.push([element, name, observer, useCapture]);
1731 element.attachEvent('on' + name, observer);
1735 unloadCache: function() {
1736 if (!Event.observers) return;
1737 for (var i = 0; i < Event.observers.length; i++) {
1738 Event.stopObserving.apply(this, Event.observers[i]);
1739 Event.observers[i][0] = null;
1741 Event.observers = false;
1744 observe: function(element, name, observer, useCapture) {
1745 var element = $(element);
1746 useCapture = useCapture || false;
1748 if (name == 'keypress' &&
1749 (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
1750 || element.attachEvent))
1753 this._observeAndCache(element, name, observer, useCapture);
1756 stopObserving: function(element, name, observer, useCapture) {
1757 var element = $(element);
1758 useCapture = useCapture || false;
1760 if (name == 'keypress' &&
1761 (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
1762 || element.detachEvent))
1765 if (element.removeEventListener) {
1766 element.removeEventListener(name, observer, useCapture);
1767 } else if (element.detachEvent) {
1768 element.detachEvent('on' + name, observer);
1773 /* prevent memory leaks in IE */
1774 if (navigator.appVersion.match(/\bMSIE\b/))
1775 Event.observe(window, 'unload', Event.unloadCache, false);
1777 // set to true if needed, warning: firefox performance problems
1778 // NOT neeeded for page scrolling, only if draggable contained in
1779 // scrollable elements
1780 includeScrollOffsets: false,
1782 // must be called before calling withinIncludingScrolloffset, every time the
1784 prepare: function() {
1785 this.deltaX = window.pageXOffset
1786 || document.documentElement.scrollLeft
1787 || document.body.scrollLeft
1789 this.deltaY = window.pageYOffset
1790 || document.documentElement.scrollTop
1791 || document.body.scrollTop
1795 realOffset: function(element) {
1796 var valueT = 0, valueL = 0;
1798 valueT += element.scrollTop || 0;
1799 valueL += element.scrollLeft || 0;
1800 element = element.parentNode;
1802 return [valueL, valueT];
1805 cumulativeOffset: function(element) {
1806 var valueT = 0, valueL = 0;
1808 valueT += element.offsetTop || 0;
1809 valueL += element.offsetLeft || 0;
1810 element = element.offsetParent;
1812 return [valueL, valueT];
1815 positionedOffset: function(element) {
1816 var valueT = 0, valueL = 0;
1818 valueT += element.offsetTop || 0;
1819 valueL += element.offsetLeft || 0;
1820 element = element.offsetParent;
1822 p = Element.getStyle(element, 'position');
1823 if (p == 'relative' || p == 'absolute') break;
1826 return [valueL, valueT];
1829 offsetParent: function(element) {
1830 if (element.offsetParent) return element.offsetParent;
1831 if (element == document.body) return element;
1833 while ((element = element.parentNode) && element != document.body)
1834 if (Element.getStyle(element, 'position') != 'static')
1837 return document.body;
1840 // caches x/y coordinate pair to use with overlap
1841 within: function(element, x, y) {
1842 if (this.includeScrollOffsets)
1843 return this.withinIncludingScrolloffsets(element, x, y);
1846 this.offset = this.cumulativeOffset(element);
1848 return (y >= this.offset[1] &&
1849 y < this.offset[1] + element.offsetHeight &&
1850 x >= this.offset[0] &&
1851 x < this.offset[0] + element.offsetWidth);
1854 withinIncludingScrolloffsets: function(element, x, y) {
1855 var offsetcache = this.realOffset(element);
1857 this.xcomp = x + offsetcache[0] - this.deltaX;
1858 this.ycomp = y + offsetcache[1] - this.deltaY;
1859 this.offset = this.cumulativeOffset(element);
1861 return (this.ycomp >= this.offset[1] &&
1862 this.ycomp < this.offset[1] + element.offsetHeight &&
1863 this.xcomp >= this.offset[0] &&
1864 this.xcomp < this.offset[0] + element.offsetWidth);
1867 // within must be called directly before
1868 overlap: function(mode, element) {
1869 if (!mode) return 0;
1870 if (mode == 'vertical')
1871 return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
1872 element.offsetHeight;
1873 if (mode == 'horizontal')
1874 return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
1875 element.offsetWidth;
1878 clone: function(source, target) {
1881 target.style.position = 'absolute';
1882 var offsets = this.cumulativeOffset(source);
1883 target.style.top = offsets[1] + 'px';
1884 target.style.left = offsets[0] + 'px';
1885 target.style.width = source.offsetWidth + 'px';
1886 target.style.height = source.offsetHeight + 'px';
1889 page: function(forElement) {
1890 var valueT = 0, valueL = 0;
1892 var element = forElement;
1894 valueT += element.offsetTop || 0;
1895 valueL += element.offsetLeft || 0;
1898 if (element.offsetParent==document.body)
1899 if (Element.getStyle(element,'position')=='absolute') break;
1901 } while (element = element.offsetParent);
1903 element = forElement;
1905 valueT -= element.scrollTop || 0;
1906 valueL -= element.scrollLeft || 0;
1907 } while (element = element.parentNode);
1909 return [valueL, valueT];
1912 clone: function(source, target) {
1913 var options = Object.extend({
1920 }, arguments[2] || {})
1922 // find page position of source
1924 var p = Position.page(source);
1926 // find coordinate system to use
1930 // delta [0,0] will do fine with position: fixed elements,
1931 // position:absolute needs offsetParent deltas
1932 if (Element.getStyle(target,'position') == 'absolute') {
1933 parent = Position.offsetParent(target);
1934 delta = Position.page(parent);
1937 // correct by body offsets (fixes Safari)
1938 if (parent == document.body) {
1939 delta[0] -= document.body.offsetLeft;
1940 delta[1] -= document.body.offsetTop;
1944 if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
1945 if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
1946 if(options.setWidth) target.style.width = source.offsetWidth + 'px';
1947 if(options.setHeight) target.style.height = source.offsetHeight + 'px';
1950 absolutize: function(element) {
1951 element = $(element);
1952 if (element.style.position == 'absolute') return;
1955 var offsets = Position.positionedOffset(element);
1956 var top = offsets[1];
1957 var left = offsets[0];
1958 var width = element.clientWidth;
1959 var height = element.clientHeight;
1961 element._originalLeft = left - parseFloat(element.style.left || 0);
1962 element._originalTop = top - parseFloat(element.style.top || 0);
1963 element._originalWidth = element.style.width;
1964 element._originalHeight = element.style.height;
1966 element.style.position = 'absolute';
1967 element.style.top = top + 'px';;
1968 element.style.left = left + 'px';;
1969 element.style.width = width + 'px';;
1970 element.style.height = height + 'px';;
1973 relativize: function(element) {
1974 element = $(element);
1975 if (element.style.position == 'relative') return;
1978 element.style.position = 'relative';
1979 var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
1980 var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
1982 element.style.top = top + 'px';
1983 element.style.left = left + 'px';
1984 element.style.height = element._originalHeight;
1985 element.style.width = element._originalWidth;
1989 // Safari returns margins on body which is incorrect if the child is absolutely
1990 // positioned. For performance reasons, redefine Position.cumulativeOffset for
1991 // KHTML/WebKit only.
1992 if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
1993 Position.cumulativeOffset = function(element) {
1994 var valueT = 0, valueL = 0;
1996 valueT += element.offsetTop || 0;
1997 valueL += element.offsetLeft || 0;
1998 if (element.offsetParent == document.body)
1999 if (Element.getStyle(element, 'position') == 'absolute') break;
2001 element = element.offsetParent;
2004 return [valueL, valueT];