1 /* Prototype JavaScript framework, version 1.5.0_rc1
\r
2 * (c) 2005 Sam Stephenson <sam@conio.net>
\r
4 * Prototype is freely distributable under the terms of an MIT-style license.
\r
5 * For details, see the Prototype web site: http://prototype.conio.net/
\r
7 /*--------------------------------------------------------------------------*/
\r
10 Version: '1.5.0_rc1',
\r
11 ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
\r
13 emptyFunction: function() {},
\r
14 K: function(x) {return x}
\r
18 create: function() {
\r
20 this.initialize.apply(this, arguments);
\r
25 var Abstract = new Object();
\r
27 Object.extend = function(destination, source) {
\r
28 for (var property in source) {
\r
29 destination[property] = source[property];
\r
34 Object.extend(Object, {
\r
35 inspect: function(object) {
\r
37 if (object == undefined) return 'undefined';
\r
38 if (object == null) return 'null';
\r
39 return object.inspect ? object.inspect() : object.toString();
\r
41 if (e instanceof RangeError) return '...';
\r
46 keys: function(object) {
\r
48 for (var property in object)
\r
49 keys.push(property);
\r
53 values: function(object) {
\r
55 for (var property in object)
\r
56 values.push(object[property]);
\r
60 clone: function(object) {
\r
61 return Object.extend({}, object);
\r
65 Function.prototype.bind = function() {
\r
66 var __method = this, args = $A(arguments), object = args.shift();
\r
68 return __method.apply(object, args.concat($A(arguments)));
\r
72 Function.prototype.bindAsEventListener = function(object) {
\r
73 var __method = this, args = $A(arguments), object = args.shift();
\r
74 return function(event) {
\r
75 return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments)));
\r
79 Object.extend(Number.prototype, {
\r
80 toColorPart: function() {
\r
81 var digits = this.toString(16);
\r
82 if (this < 16) return '0' + digits;
\r
90 times: function(iterator) {
\r
91 $R(0, this, true).each(iterator);
\r
100 for (var i = 0; i < arguments.length; i++) {
\r
101 var lambda = arguments[i];
\r
103 returnValue = lambda();
\r
108 return returnValue;
\r
112 /*--------------------------------------------------------------------------*/
\r
114 var PeriodicalExecuter = Class.create();
\r
115 PeriodicalExecuter.prototype = {
\r
116 initialize: function(callback, frequency) {
\r
117 this.callback = callback;
\r
118 this.frequency = frequency;
\r
119 this.currentlyExecuting = false;
\r
121 this.registerCallback();
\r
124 registerCallback: function() {
\r
125 this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
\r
129 if (!this.timer) return;
\r
130 clearInterval(this.timer);
\r
134 onTimerEvent: function() {
\r
135 if (!this.currentlyExecuting) {
\r
137 this.currentlyExecuting = true;
\r
138 this.callback(this);
\r
140 this.currentlyExecuting = false;
\r
145 Object.extend(String.prototype, {
\r
146 gsub: function(pattern, replacement) {
\r
147 var result = '', source = this, match;
\r
148 replacement = arguments.callee.prepareReplacement(replacement);
\r
150 while (source.length > 0) {
\r
151 if (match = source.match(pattern)) {
\r
152 result += source.slice(0, match.index);
\r
153 result += (replacement(match) || '').toString();
\r
154 source = source.slice(match.index + match[0].length);
\r
156 result += source, source = '';
\r
162 sub: function(pattern, replacement, count) {
\r
163 replacement = this.gsub.prepareReplacement(replacement);
\r
164 count = count === undefined ? 1 : count;
\r
166 return this.gsub(pattern, function(match) {
\r
167 if (--count < 0) return match[0];
\r
168 return replacement(match);
\r
172 scan: function(pattern, iterator) {
\r
173 this.gsub(pattern, iterator);
\r
177 truncate: function(length, truncation) {
\r
178 length = length || 30;
\r
179 truncation = truncation === undefined ? '...' : truncation;
\r
180 return this.length > length ?
\r
181 this.slice(0, length - truncation.length) + truncation : this;
\r
184 strip: function() {
\r
185 return this.replace(/^\s+/, '').replace(/\s+$/, '');
\r
188 stripTags: function() {
\r
189 return this.replace(/<\/?[^>]+>/gi, '');
\r
192 stripScripts: function() {
\r
193 return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
\r
196 extractScripts: function() {
\r
197 var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
\r
198 var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
\r
199 return (this.match(matchAll) || []).map(function(scriptTag) {
\r
200 return (scriptTag.match(matchOne) || ['', ''])[1];
\r
204 evalScripts: function() {
\r
205 return this.extractScripts().map(function(script) { return eval(script) });
\r
208 escapeHTML: function() {
\r
209 var div = document.createElement('div');
\r
210 var text = document.createTextNode(this);
\r
211 div.appendChild(text);
\r
212 return div.innerHTML;
\r
215 unescapeHTML: function() {
\r
216 var div = document.createElement('div');
\r
217 div.innerHTML = this.stripTags();
\r
218 return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
\r
221 toQueryParams: function() {
\r
222 var pairs = this.match(/^\??(.*)$/)[1].split('&');
\r
223 return pairs.inject({}, function(params, pairString) {
\r
224 var pair = pairString.split('=');
\r
225 var value = pair[1] ? decodeURIComponent(pair[1]) : undefined;
\r
226 params[decodeURIComponent(pair[0])] = value;
\r
231 toArray: function() {
\r
232 return this.split('');
\r
235 camelize: function() {
\r
236 var oStringList = this.split('-');
\r
237 if (oStringList.length == 1) return oStringList[0];
\r
239 var camelizedString = this.indexOf('-') == 0
\r
240 ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
\r
243 for (var i = 1, len = oStringList.length; i < len; i++) {
\r
244 var s = oStringList[i];
\r
245 camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
\r
248 return camelizedString;
\r
251 inspect: function(useDoubleQuotes) {
\r
252 var escapedString = this.replace(/\\/g, '\\\\');
\r
253 if (useDoubleQuotes)
\r
254 return '"' + escapedString.replace(/"/g, '\\"') + '"';
\r
256 return "'" + escapedString.replace(/'/g, '\\\'') + "'";
\r
260 String.prototype.gsub.prepareReplacement = function(replacement) {
\r
261 if (typeof replacement == 'function') return replacement;
\r
262 var template = new Template(replacement);
\r
263 return function(match) { return template.evaluate(match) };
\r
266 String.prototype.parseQuery = String.prototype.toQueryParams;
\r
268 var Template = Class.create();
\r
269 Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
\r
270 Template.prototype = {
\r
271 initialize: function(template, pattern) {
\r
272 this.template = template.toString();
\r
273 this.pattern = pattern || Template.Pattern;
\r
276 evaluate: function(object) {
\r
277 return this.template.gsub(this.pattern, function(match) {
\r
278 var before = match[1];
\r
279 if (before == '\\') return match[2];
\r
280 return before + (object[match[3]] || '').toString();
\r
285 var $break = new Object();
\r
286 var $continue = new Object();
\r
289 each: function(iterator) {
\r
292 this._each(function(value) {
\r
294 iterator(value, index++);
\r
296 if (e != $continue) throw e;
\r
300 if (e != $break) throw e;
\r
304 all: function(iterator) {
\r
306 this.each(function(value, index) {
\r
307 result = result && !!(iterator || Prototype.K)(value, index);
\r
308 if (!result) throw $break;
\r
313 any: function(iterator) {
\r
314 var result = false;
\r
315 this.each(function(value, index) {
\r
316 if (result = !!(iterator || Prototype.K)(value, index))
\r
322 collect: function(iterator) {
\r
324 this.each(function(value, index) {
\r
325 results.push(iterator(value, index));
\r
330 detect: function (iterator) {
\r
332 this.each(function(value, index) {
\r
333 if (iterator(value, index)) {
\r
341 findAll: function(iterator) {
\r
343 this.each(function(value, index) {
\r
344 if (iterator(value, index))
\r
345 results.push(value);
\r
350 grep: function(pattern, iterator) {
\r
352 this.each(function(value, index) {
\r
353 var stringValue = value.toString();
\r
354 if (stringValue.match(pattern))
\r
355 results.push((iterator || Prototype.K)(value, index));
\r
360 include: function(object) {
\r
362 this.each(function(value) {
\r
363 if (value == object) {
\r
371 inject: function(memo, iterator) {
\r
372 this.each(function(value, index) {
\r
373 memo = iterator(memo, value, index);
\r
378 invoke: function(method) {
\r
379 var args = $A(arguments).slice(1);
\r
380 return this.collect(function(value) {
\r
381 return value[method].apply(value, args);
\r
385 max: function(iterator) {
\r
387 this.each(function(value, index) {
\r
388 value = (iterator || Prototype.K)(value, index);
\r
389 if (result == undefined || value >= result)
\r
395 min: function(iterator) {
\r
397 this.each(function(value, index) {
\r
398 value = (iterator || Prototype.K)(value, index);
\r
399 if (result == undefined || value < result)
\r
405 partition: function(iterator) {
\r
406 var trues = [], falses = [];
\r
407 this.each(function(value, index) {
\r
408 ((iterator || Prototype.K)(value, index) ?
\r
409 trues : falses).push(value);
\r
411 return [trues, falses];
\r
414 pluck: function(property) {
\r
416 this.each(function(value, index) {
\r
417 results.push(value[property]);
\r
422 reject: function(iterator) {
\r
424 this.each(function(value, index) {
\r
425 if (!iterator(value, index))
\r
426 results.push(value);
\r
431 sortBy: function(iterator) {
\r
432 return this.collect(function(value, index) {
\r
433 return {value: value, criteria: iterator(value, index)};
\r
434 }).sort(function(left, right) {
\r
435 var a = left.criteria, b = right.criteria;
\r
436 return a < b ? -1 : a > b ? 1 : 0;
\r
440 toArray: function() {
\r
441 return this.collect(Prototype.K);
\r
445 var iterator = Prototype.K, args = $A(arguments);
\r
446 if (typeof args.last() == 'function')
\r
447 iterator = args.pop();
\r
449 var collections = [this].concat(args).map($A);
\r
450 return this.map(function(value, index) {
\r
451 return iterator(collections.pluck(index));
\r
455 inspect: function() {
\r
456 return '#<Enumerable:' + this.toArray().inspect() + '>';
\r
460 Object.extend(Enumerable, {
\r
461 map: Enumerable.collect,
\r
462 find: Enumerable.detect,
\r
463 select: Enumerable.findAll,
\r
464 member: Enumerable.include,
\r
465 entries: Enumerable.toArray
\r
467 var $A = Array.from = function(iterable) {
\r
468 if (!iterable) return [];
\r
469 if (iterable.toArray) {
\r
470 return iterable.toArray();
\r
473 for (var i = 0; i < iterable.length; i++)
\r
474 results.push(iterable[i]);
\r
479 Object.extend(Array.prototype, Enumerable);
\r
481 if (!Array.prototype._reverse)
\r
482 Array.prototype._reverse = Array.prototype.reverse;
\r
484 Object.extend(Array.prototype, {
\r
485 _each: function(iterator) {
\r
486 for (var i = 0; i < this.length; i++)
\r
490 clear: function() {
\r
495 first: function() {
\r
500 return this[this.length - 1];
\r
503 compact: function() {
\r
504 return this.select(function(value) {
\r
505 return value != undefined || value != null;
\r
509 flatten: function() {
\r
510 return this.inject([], function(array, value) {
\r
511 return array.concat(value && value.constructor == Array ?
\r
512 value.flatten() : [value]);
\r
516 without: function() {
\r
517 var values = $A(arguments);
\r
518 return this.select(function(value) {
\r
519 return !values.include(value);
\r
523 indexOf: function(object) {
\r
524 for (var i = 0; i < this.length; i++)
\r
525 if (this[i] == object) return i;
\r
529 reverse: function(inline) {
\r
530 return (inline !== false ? this : this.toArray())._reverse();
\r
533 reduce: function() {
\r
534 return this.length > 1 ? this : this[0];
\r
538 return this.inject([], function(array, value) {
\r
539 return array.include(value) ? array : array.concat([value]);
\r
543 inspect: function() {
\r
544 return '[' + this.map(Object.inspect).join(', ') + ']';
\r
548 _each: function(iterator) {
\r
549 for (var key in this) {
\r
550 var value = this[key];
\r
551 if (typeof value == 'function') continue;
\r
553 var pair = [key, value];
\r
555 pair.value = value;
\r
561 return this.pluck('key');
\r
564 values: function() {
\r
565 return this.pluck('value');
\r
568 merge: function(hash) {
\r
569 return $H(hash).inject($H(this), function(mergedHash, pair) {
\r
570 mergedHash[pair.key] = pair.value;
\r
575 toQueryString: function() {
\r
576 return this.map(function(pair) {
\r
577 return pair.map(encodeURIComponent).join('=');
\r
581 inspect: function() {
\r
582 return '#<Hash:{' + this.map(function(pair) {
\r
583 return pair.map(Object.inspect).join(': ');
\r
584 }).join(', ') + '}>';
\r
588 function $H(object) {
\r
589 var hash = Object.extend({}, object || {});
\r
590 Object.extend(hash, Enumerable);
\r
591 Object.extend(hash, Hash);
\r
594 ObjectRange = Class.create();
\r
595 Object.extend(ObjectRange.prototype, Enumerable);
\r
596 Object.extend(ObjectRange.prototype, {
\r
597 initialize: function(start, end, exclusive) {
\r
598 this.start = start;
\r
600 this.exclusive = exclusive;
\r
603 _each: function(iterator) {
\r
604 var value = this.start;
\r
605 while (this.include(value)) {
\r
607 value = value.succ();
\r
611 include: function(value) {
\r
612 if (value < this.start)
\r
614 if (this.exclusive)
\r
615 return value < this.end;
\r
616 return value <= this.end;
\r
620 var $R = function(start, end, exclusive) {
\r
621 return new ObjectRange(start, end, exclusive);
\r
625 getTransport: function() {
\r
627 function() {return new XMLHttpRequest()},
\r
628 function() {return new ActiveXObject('Msxml2.XMLHTTP')},
\r
629 function() {return new ActiveXObject('Microsoft.XMLHTTP')}
\r
633 activeRequestCount: 0
\r
636 Ajax.Responders = {
\r
639 _each: function(iterator) {
\r
640 this.responders._each(iterator);
\r
643 register: function(responderToAdd) {
\r
644 if (!this.include(responderToAdd))
\r
645 this.responders.push(responderToAdd);
\r
648 unregister: function(responderToRemove) {
\r
649 this.responders = this.responders.without(responderToRemove);
\r
652 dispatch: function(callback, request, transport, json) {
\r
653 this.each(function(responder) {
\r
654 if (responder[callback] && typeof responder[callback] == 'function') {
\r
656 responder[callback].apply(responder, [request, transport, json]);
\r
663 Object.extend(Ajax.Responders, Enumerable);
\r
665 Ajax.Responders.register({
\r
666 onCreate: function() {
\r
667 Ajax.activeRequestCount++;
\r
670 onComplete: function() {
\r
671 Ajax.activeRequestCount--;
\r
675 Ajax.Base = function() {};
\r
676 Ajax.Base.prototype = {
\r
677 setOptions: function(options) {
\r
680 asynchronous: true,
\r
681 contentType: 'application/x-www-form-urlencoded',
\r
684 Object.extend(this.options, options || {});
\r
687 responseIsSuccess: function() {
\r
688 return this.transport.status == undefined
\r
689 || this.transport.status == 0
\r
690 || (this.transport.status >= 200 && this.transport.status < 300);
\r
693 responseIsFailure: function() {
\r
694 return !this.responseIsSuccess();
\r
698 Ajax.Request = Class.create();
\r
699 Ajax.Request.Events =
\r
700 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
\r
702 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
\r
703 initialize: function(url, options) {
\r
704 this.transport = Ajax.getTransport();
\r
705 this.setOptions(options);
\r
709 request: function(url) {
\r
710 var parameters = this.options.parameters || '';
\r
711 if (parameters.length > 0) parameters += '&_=';
\r
713 /* Simulate other verbs over post */
\r
714 if (this.options.method != 'get' && this.options.method != 'post') {
\r
715 parameters += (parameters.length > 0 ? '&' : '') + '_method=' + this.options.method;
\r
716 this.options.method = 'post';
\r
721 if (this.options.method == 'get' && parameters.length > 0)
\r
722 this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
\r
724 Ajax.Responders.dispatch('onCreate', this, this.transport);
\r
726 this.transport.open(this.options.method, this.url,
\r
727 this.options.asynchronous);
\r
729 if (this.options.asynchronous)
\r
730 setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);
\r
732 this.transport.onreadystatechange = this.onStateChange.bind(this);
\r
733 this.setRequestHeaders();
\r
735 var body = this.options.postBody ? this.options.postBody : parameters;
\r
736 this.transport.send(this.options.method == 'post' ? body : null);
\r
738 /* Force Firefox to handle ready state 4 for synchronous requests */
\r
739 if (!this.options.asynchronous && this.transport.overrideMimeType)
\r
740 this.onStateChange();
\r
743 this.dispatchException(e);
\r
747 setRequestHeaders: function() {
\r
748 var requestHeaders =
\r
749 ['X-Requested-With', 'XMLHttpRequest',
\r
750 'X-Prototype-Version', Prototype.Version,
\r
751 'Accept', 'text/javascript, text/html, application/xml, text/xml, */*'];
\r
753 if (this.options.method == 'post') {
\r
754 requestHeaders.push('Content-type', this.options.contentType);
\r
756 /* Force "Connection: close" for Mozilla browsers to work around
\r
757 * a bug where XMLHttpReqeuest sends an incorrect Content-length
\r
758 * header. See Mozilla Bugzilla #246651.
\r
760 if (this.transport.overrideMimeType)
\r
761 requestHeaders.push('Connection', 'close');
\r
764 if (this.options.requestHeaders)
\r
765 requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
\r
767 for (var i = 0; i < requestHeaders.length; i += 2)
\r
768 this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
\r
771 onStateChange: function() {
\r
772 var readyState = this.transport.readyState;
\r
773 if (readyState != 1)
\r
774 this.respondToReadyState(this.transport.readyState);
\r
777 header: function(name) {
\r
779 return this.transport.getResponseHeader(name);
\r
783 evalJSON: function() {
\r
785 return eval('(' + this.header('X-JSON') + ')');
\r
789 evalResponse: function() {
\r
791 return eval(this.transport.responseText);
\r
793 this.dispatchException(e);
\r
797 respondToReadyState: function(readyState) {
\r
798 var event = Ajax.Request.Events[readyState];
\r
799 var transport = this.transport, json = this.evalJSON();
\r
801 if (event == 'Complete') {
\r
803 (this.options['on' + this.transport.status]
\r
804 || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
\r
805 || Prototype.emptyFunction)(transport, json);
\r
807 this.dispatchException(e);
\r
810 if ((this.header('Content-type') || '').match(/^text\/javascript/i))
\r
811 this.evalResponse();
\r
815 (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
\r
816 Ajax.Responders.dispatch('on' + event, this, transport, json);
\r
818 this.dispatchException(e);
\r
821 /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
\r
822 if (event == 'Complete')
\r
823 this.transport.onreadystatechange = Prototype.emptyFunction;
\r
826 dispatchException: function(exception) {
\r
827 (this.options.onException || Prototype.emptyFunction)(this, exception);
\r
828 Ajax.Responders.dispatch('onException', this, exception);
\r
832 Ajax.Updater = Class.create();
\r
834 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
\r
835 initialize: function(container, url, options) {
\r
836 this.containers = {
\r
837 success: container.success ? $(container.success) : $(container),
\r
838 failure: container.failure ? $(container.failure) :
\r
839 (container.success ? null : $(container))
\r
842 this.transport = Ajax.getTransport();
\r
843 this.setOptions(options);
\r
845 var onComplete = this.options.onComplete || Prototype.emptyFunction;
\r
846 this.options.onComplete = (function(transport, object) {
\r
847 this.updateContent();
\r
848 onComplete(transport, object);
\r
854 updateContent: function() {
\r
855 var receiver = this.responseIsSuccess() ?
\r
856 this.containers.success : this.containers.failure;
\r
857 var response = this.transport.responseText;
\r
859 if (!this.options.evalScripts)
\r
860 response = response.stripScripts();
\r
863 if (this.options.insertion) {
\r
864 new this.options.insertion(receiver, response);
\r
866 Element.update(receiver, response);
\r
870 if (this.responseIsSuccess()) {
\r
871 if (this.onComplete)
\r
872 setTimeout(this.onComplete.bind(this), 10);
\r
877 Ajax.PeriodicalUpdater = Class.create();
\r
878 Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
\r
879 initialize: function(container, url, options) {
\r
880 this.setOptions(options);
\r
881 this.onComplete = this.options.onComplete;
\r
883 this.frequency = (this.options.frequency || 2);
\r
884 this.decay = (this.options.decay || 1);
\r
887 this.container = container;
\r
893 start: function() {
\r
894 this.options.onComplete = this.updateComplete.bind(this);
\r
895 this.onTimerEvent();
\r
899 this.updater.options.onComplete = undefined;
\r
900 clearTimeout(this.timer);
\r
901 (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
\r
904 updateComplete: function(request) {
\r
905 if (this.options.decay) {
\r
906 this.decay = (request.responseText == this.lastText ?
\r
907 this.decay * this.options.decay : 1);
\r
909 this.lastText = request.responseText;
\r
911 this.timer = setTimeout(this.onTimerEvent.bind(this),
\r
912 this.decay * this.frequency * 1000);
\r
915 onTimerEvent: function() {
\r
916 this.updater = new Ajax.Updater(this.container, this.url, this.options);
\r
920 var results = [], element;
\r
921 for (var i = 0; i < arguments.length; i++) {
\r
922 element = arguments[i];
\r
923 if (typeof element == 'string')
\r
924 element = document.getElementById(element);
\r
925 results.push(Element.extend(element));
\r
927 return results.reduce();
\r
930 document.getElementsByClassName = function(className, parentElement) {
\r
931 var children = ($(parentElement) || document.body).getElementsByTagName('*');
\r
932 return $A(children).inject([], function(elements, child) {
\r
933 if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
\r
934 elements.push(Element.extend(child));
\r
939 /*--------------------------------------------------------------------------*/
\r
941 if (!window.Element)
\r
942 var Element = new Object();
\r
944 Element.extend = function(element) {
\r
945 if (!element) return;
\r
946 if (_nativeExtensions || element.nodeType == 3) return element;
\r
948 if (!element._extended && element.tagName && element != window) {
\r
949 var methods = Object.clone(Element.Methods), cache = Element.extend.cache;
\r
951 if (element.tagName == 'FORM')
\r
952 Object.extend(methods, Form.Methods);
\r
953 if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName))
\r
954 Object.extend(methods, Form.Element.Methods);
\r
956 for (var property in methods) {
\r
957 var value = methods[property];
\r
958 if (typeof value == 'function')
\r
959 element[property] = cache.findOrStore(value);
\r
963 element._extended = true;
\r
967 Element.extend.cache = {
\r
968 findOrStore: function(value) {
\r
969 return this[value] = this[value] || function() {
\r
970 return value.apply(null, [this].concat($A(arguments)));
\r
975 Element.Methods = {
\r
976 visible: function(element) {
\r
977 return $(element).style.display != 'none';
\r
980 toggle: function(element) {
\r
981 element = $(element);
\r
982 Element[Element.visible(element) ? 'hide' : 'show'](element);
\r
986 hide: function(element) {
\r
987 $(element).style.display = 'none';
\r
991 show: function(element) {
\r
992 $(element).style.display = '';
\r
996 remove: function(element) {
\r
997 element = $(element);
\r
998 element.parentNode.removeChild(element);
\r
1002 update: function(element, html) {
\r
1003 $(element).innerHTML = html.stripScripts();
\r
1004 setTimeout(function() {html.evalScripts()}, 10);
\r
1008 replace: function(element, html) {
\r
1009 element = $(element);
\r
1010 if (element.outerHTML) {
\r
1011 element.outerHTML = html.stripScripts();
\r
1013 var range = element.ownerDocument.createRange();
\r
1014 range.selectNodeContents(element);
\r
1015 element.parentNode.replaceChild(
\r
1016 range.createContextualFragment(html.stripScripts()), element);
\r
1018 setTimeout(function() {html.evalScripts()}, 10);
\r
1022 inspect: function(element) {
\r
1023 element = $(element);
\r
1024 var result = '<' + element.tagName.toLowerCase();
\r
1025 $H({'id': 'id', 'className': 'class'}).each(function(pair) {
\r
1026 var property = pair.first(), attribute = pair.last();
\r
1027 var value = (element[property] || '').toString();
\r
1028 if (value) result += ' ' + attribute + '=' + value.inspect(true);
\r
1030 return result + '>';
\r
1033 recursivelyCollect: function(element, property) {
\r
1034 element = $(element);
\r
1035 var elements = [];
\r
1036 while (element = element[property])
\r
1037 if (element.nodeType == 1)
\r
1038 elements.push(Element.extend(element));
\r
1042 ancestors: function(element) {
\r
1043 return $(element).recursivelyCollect('parentNode');
\r
1046 descendants: function(element) {
\r
1047 element = $(element);
\r
1048 return $A(element.getElementsByTagName('*'));
\r
1051 previousSiblings: function(element) {
\r
1052 return $(element).recursivelyCollect('previousSibling');
\r
1055 nextSiblings: function(element) {
\r
1056 return $(element).recursivelyCollect('nextSibling');
\r
1059 siblings: function(element) {
\r
1060 element = $(element);
\r
1061 return element.previousSiblings().reverse().concat(element.nextSiblings());
\r
1064 match: function(element, selector) {
\r
1065 element = $(element);
\r
1066 if (typeof selector == 'string')
\r
1067 selector = new Selector(selector);
\r
1068 return selector.match(element);
\r
1071 up: function(element, expression, index) {
\r
1072 return Selector.findElement($(element).ancestors(), expression, index);
\r
1075 down: function(element, expression, index) {
\r
1076 return Selector.findElement($(element).descendants(), expression, index);
\r
1079 previous: function(element, expression, index) {
\r
1080 return Selector.findElement($(element).previousSiblings(), expression, index);
\r
1083 next: function(element, expression, index) {
\r
1084 return Selector.findElement($(element).nextSiblings(), expression, index);
\r
1087 getElementsBySelector: function() {
\r
1088 var args = $A(arguments), element = $(args.shift());
\r
1089 return Selector.findChildElements(element, args);
\r
1092 getElementsByClassName: function(element, className) {
\r
1093 element = $(element);
\r
1094 return document.getElementsByClassName(className, element);
\r
1097 getHeight: function(element) {
\r
1098 element = $(element);
\r
1099 return element.offsetHeight;
\r
1102 classNames: function(element) {
\r
1103 return new Element.ClassNames(element);
\r
1106 hasClassName: function(element, className) {
\r
1107 if (!(element = $(element))) return;
\r
1108 return Element.classNames(element).include(className);
\r
1111 addClassName: function(element, className) {
\r
1112 if (!(element = $(element))) return;
\r
1113 Element.classNames(element).add(className);
\r
1117 removeClassName: function(element, className) {
\r
1118 if (!(element = $(element))) return;
\r
1119 Element.classNames(element).remove(className);
\r
1123 observe: function() {
\r
1124 Event.observe.apply(Event, arguments);
\r
1125 return $A(arguments).first();
\r
1128 stopObserving: function() {
\r
1129 Event.stopObserving.apply(Event, arguments);
\r
1130 return $A(arguments).first();
\r
1133 // removes whitespace-only text node children
\r
1134 cleanWhitespace: function(element) {
\r
1135 element = $(element);
\r
1136 var node = element.firstChild;
\r
1138 var nextNode = node.nextSibling;
\r
1139 if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
\r
1140 element.removeChild(node);
\r
1146 empty: function(element) {
\r
1147 return $(element).innerHTML.match(/^\s*$/);
\r
1150 childOf: function(element, ancestor) {
\r
1151 element = $(element), ancestor = $(ancestor);
\r
1152 while (element = element.parentNode)
\r
1153 if (element == ancestor) return true;
\r
1157 scrollTo: function(element) {
\r
1158 element = $(element);
\r
1159 var x = element.x ? element.x : element.offsetLeft,
\r
1160 y = element.y ? element.y : element.offsetTop;
\r
1161 window.scrollTo(x, y);
\r
1165 getStyle: function(element, style) {
\r
1166 element = $(element);
\r
1167 var value = element.style[style.camelize()];
\r
1169 if (document.defaultView && document.defaultView.getComputedStyle) {
\r
1170 var css = document.defaultView.getComputedStyle(element, null);
\r
1171 value = css ? css.getPropertyValue(style) : null;
\r
1172 } else if (element.currentStyle) {
\r
1173 value = element.currentStyle[style.camelize()];
\r
1177 if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
\r
1178 if (Element.getStyle(element, 'position') == 'static') value = 'auto';
\r
1180 return value == 'auto' ? null : value;
\r
1183 setStyle: function(element, style) {
\r
1184 element = $(element);
\r
1185 for (var name in style)
\r
1186 element.style[name.camelize()] = style[name];
\r
1190 getDimensions: function(element) {
\r
1191 element = $(element);
\r
1192 if (Element.getStyle(element, 'display') != 'none')
\r
1193 return {width: element.offsetWidth, height: element.offsetHeight};
\r
1195 // All *Width and *Height properties give 0 on elements with display none,
\r
1196 // so enable the element temporarily
\r
1197 var els = element.style;
\r
1198 var originalVisibility = els.visibility;
\r
1199 var originalPosition = els.position;
\r
1200 els.visibility = 'hidden';
\r
1201 els.position = 'absolute';
\r
1203 var originalWidth = element.clientWidth;
\r
1204 var originalHeight = element.clientHeight;
\r
1205 els.display = 'none';
\r
1206 els.position = originalPosition;
\r
1207 els.visibility = originalVisibility;
\r
1208 return {width: originalWidth, height: originalHeight};
\r
1211 makePositioned: function(element) {
\r
1212 element = $(element);
\r
1213 var pos = Element.getStyle(element, 'position');
\r
1214 if (pos == 'static' || !pos) {
\r
1215 element._madePositioned = true;
\r
1216 element.style.position = 'relative';
\r
1217 // Opera returns the offset relative to the positioning context, when an
\r
1218 // element is position relative but top and left have not been defined
\r
1219 if (window.opera) {
\r
1220 element.style.top = 0;
\r
1221 element.style.left = 0;
\r
1227 undoPositioned: function(element) {
\r
1228 element = $(element);
\r
1229 if (element._madePositioned) {
\r
1230 element._madePositioned = undefined;
\r
1231 element.style.position =
\r
1232 element.style.top =
\r
1233 element.style.left =
\r
1234 element.style.bottom =
\r
1235 element.style.right = '';
\r
1240 makeClipping: function(element) {
\r
1241 element = $(element);
\r
1242 if (element._overflow) return;
\r
1243 element._overflow = element.style.overflow || 'auto';
\r
1244 if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
\r
1245 element.style.overflow = 'hidden';
\r
1249 undoClipping: function(element) {
\r
1250 element = $(element);
\r
1251 if (!element._overflow) return;
\r
1252 element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
\r
1253 element._overflow = null;
\r
1258 // IE is missing .innerHTML support for TABLE-related elements
\r
1260 Element.Methods.update = function(element, html) {
\r
1261 element = $(element);
\r
1262 var tagName = element.tagName.toUpperCase();
\r
1263 if (['THEAD','TBODY','TR','TD'].indexOf(tagName) > -1) {
\r
1264 var div = document.createElement('div');
\r
1265 switch (tagName) {
\r
1268 div.innerHTML = '<table><tbody>' + html.stripScripts() + '</tbody></table>';
\r
1272 div.innerHTML = '<table><tbody><tr>' + html.stripScripts() + '</tr></tbody></table>';
\r
1276 div.innerHTML = '<table><tbody><tr><td>' + html.stripScripts() + '</td></tr></tbody></table>';
\r
1279 $A(element.childNodes).each(function(node){
\r
1280 element.removeChild(node)
\r
1282 depth.times(function(){ div = div.firstChild });
\r
1284 $A(div.childNodes).each(
\r
1285 function(node){ element.appendChild(node) });
\r
1287 element.innerHTML = html.stripScripts();
\r
1289 setTimeout(function() {html.evalScripts()}, 10);
\r
1294 Object.extend(Element, Element.Methods);
\r
1296 var _nativeExtensions = false;
\r
1298 if (!window.HTMLElement && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
\r
1299 /* Emulate HTMLElement, HTMLFormElement, HTMLInputElement, HTMLTextAreaElement,
\r
1300 and HTMLSelectElement in Safari */
\r
1301 ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) {
\r
1302 var klass = window['HTML' + tag + 'Element'] = {};
\r
1303 klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__;
\r
1307 Element.addMethods = function(methods) {
\r
1308 Object.extend(Element.Methods, methods || {});
\r
1310 function copy(methods, destination) {
\r
1311 var cache = Element.extend.cache;
\r
1312 for (var property in methods) {
\r
1313 var value = methods[property];
\r
1314 destination[property] = cache.findOrStore(value);
\r
1318 if (typeof HTMLElement != 'undefined') {
\r
1319 copy(Element.Methods, HTMLElement.prototype);
\r
1320 copy(Form.Methods, HTMLFormElement.prototype);
\r
1321 [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) {
\r
1322 copy(Form.Element.Methods, klass.prototype);
\r
1324 _nativeExtensions = true;
\r
1328 var Toggle = new Object();
\r
1329 Toggle.display = Element.toggle;
\r
1331 /*--------------------------------------------------------------------------*/
\r
1333 Abstract.Insertion = function(adjacency) {
\r
1334 this.adjacency = adjacency;
\r
1337 Abstract.Insertion.prototype = {
\r
1338 initialize: function(element, content) {
\r
1339 this.element = $(element);
\r
1340 this.content = content.stripScripts();
\r
1342 if (this.adjacency && this.element.insertAdjacentHTML) {
\r
1344 this.element.insertAdjacentHTML(this.adjacency, this.content);
\r
1346 var tagName = this.element.tagName.toLowerCase();
\r
1347 if (tagName == 'tbody' || tagName == 'tr') {
\r
1348 this.insertContent(this.contentFromAnonymousTable());
\r
1354 this.range = this.element.ownerDocument.createRange();
\r
1355 if (this.initializeRange) this.initializeRange();
\r
1356 this.insertContent([this.range.createContextualFragment(this.content)]);
\r
1359 setTimeout(function() {content.evalScripts()}, 10);
\r
1362 contentFromAnonymousTable: function() {
\r
1363 var div = document.createElement('div');
\r
1364 div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
\r
1365 return $A(div.childNodes[0].childNodes[0].childNodes);
\r
1369 var Insertion = new Object();
\r
1371 Insertion.Before = Class.create();
\r
1372 Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
\r
1373 initializeRange: function() {
\r
1374 this.range.setStartBefore(this.element);
\r
1377 insertContent: function(fragments) {
\r
1378 fragments.each((function(fragment) {
\r
1379 this.element.parentNode.insertBefore(fragment, this.element);
\r
1384 Insertion.Top = Class.create();
\r
1385 Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
\r
1386 initializeRange: function() {
\r
1387 this.range.selectNodeContents(this.element);
\r
1388 this.range.collapse(true);
\r
1391 insertContent: function(fragments) {
\r
1392 fragments.reverse(false).each((function(fragment) {
\r
1393 this.element.insertBefore(fragment, this.element.firstChild);
\r
1398 Insertion.Bottom = Class.create();
\r
1399 Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
\r
1400 initializeRange: function() {
\r
1401 this.range.selectNodeContents(this.element);
\r
1402 this.range.collapse(this.element);
\r
1405 insertContent: function(fragments) {
\r
1406 fragments.each((function(fragment) {
\r
1407 this.element.appendChild(fragment);
\r
1412 Insertion.After = Class.create();
\r
1413 Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
\r
1414 initializeRange: function() {
\r
1415 this.range.setStartAfter(this.element);
\r
1418 insertContent: function(fragments) {
\r
1419 fragments.each((function(fragment) {
\r
1420 this.element.parentNode.insertBefore(fragment,
\r
1421 this.element.nextSibling);
\r
1426 /*--------------------------------------------------------------------------*/
\r
1428 Element.ClassNames = Class.create();
\r
1429 Element.ClassNames.prototype = {
\r
1430 initialize: function(element) {
\r
1431 this.element = $(element);
\r
1434 _each: function(iterator) {
\r
1435 this.element.className.split(/\s+/).select(function(name) {
\r
1436 return name.length > 0;
\r
1437 })._each(iterator);
\r
1440 set: function(className) {
\r
1441 this.element.className = className;
\r
1444 add: function(classNameToAdd) {
\r
1445 if (this.include(classNameToAdd)) return;
\r
1446 this.set(this.toArray().concat(classNameToAdd).join(' '));
\r
1449 remove: function(classNameToRemove) {
\r
1450 if (!this.include(classNameToRemove)) return;
\r
1451 this.set(this.select(function(className) {
\r
1452 return className != classNameToRemove;
\r
1456 toString: function() {
\r
1457 return this.toArray().join(' ');
\r
1461 Object.extend(Element.ClassNames.prototype, Enumerable);
\r
1462 var Selector = Class.create();
\r
1463 Selector.prototype = {
\r
1464 initialize: function(expression) {
\r
1465 this.params = {classNames: []};
\r
1466 this.expression = expression.toString().strip();
\r
1467 this.parseExpression();
\r
1468 this.compileMatcher();
\r
1471 parseExpression: function() {
\r
1472 function abort(message) { throw 'Parse error in selector: ' + message; }
\r
1474 if (this.expression == '') abort('empty expression');
\r
1476 var params = this.params, expr = this.expression, match, modifier, clause, rest;
\r
1477 while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
\r
1478 params.attributes = params.attributes || [];
\r
1479 params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
\r
1483 if (expr == '*') return this.params.wildcard = true;
\r
1485 while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
\r
1486 modifier = match[1], clause = match[2], rest = match[3];
\r
1487 switch (modifier) {
\r
1488 case '#': params.id = clause; break;
\r
1489 case '.': params.classNames.push(clause); break;
\r
1491 case undefined: params.tagName = clause.toUpperCase(); break;
\r
1492 default: abort(expr.inspect());
\r
1497 if (expr.length > 0) abort(expr.inspect());
\r
1500 buildMatchExpression: function() {
\r
1501 var params = this.params, conditions = [], clause;
\r
1503 if (params.wildcard)
\r
1504 conditions.push('true');
\r
1505 if (clause = params.id)
\r
1506 conditions.push('element.id == ' + clause.inspect());
\r
1507 if (clause = params.tagName)
\r
1508 conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
\r
1509 if ((clause = params.classNames).length > 0)
\r
1510 for (var i = 0; i < clause.length; i++)
\r
1511 conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')');
\r
1512 if (clause = params.attributes) {
\r
1513 clause.each(function(attribute) {
\r
1514 var value = 'element.getAttribute(' + attribute.name.inspect() + ')';
\r
1515 var splitValueBy = function(delimiter) {
\r
1516 return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
\r
1519 switch (attribute.operator) {
\r
1520 case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break;
\r
1521 case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
\r
1522 case '|=': conditions.push(
\r
1523 splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
\r
1525 case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break;
\r
1527 case undefined: conditions.push(value + ' != null'); break;
\r
1528 default: throw 'Unknown operator ' + attribute.operator + ' in selector';
\r
1533 return conditions.join(' && ');
\r
1536 compileMatcher: function() {
\r
1537 this.match = new Function('element', 'if (!element.tagName) return false; \
\r
1538 return ' + this.buildMatchExpression());
\r
1541 findElements: function(scope) {
\r
1544 if (element = $(this.params.id))
\r
1545 if (this.match(element))
\r
1546 if (!scope || Element.childOf(element, scope))
\r
1549 scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
\r
1552 for (var i = 0; i < scope.length; i++)
\r
1553 if (this.match(element = scope[i]))
\r
1554 results.push(Element.extend(element));
\r
1559 toString: function() {
\r
1560 return this.expression;
\r
1564 Object.extend(Selector, {
\r
1565 matchElements: function(elements, expression) {
\r
1566 var selector = new Selector(expression);
\r
1567 return elements.select(selector.match.bind(selector));
\r
1570 findElement: function(elements, expression, index) {
\r
1571 if (typeof expression == 'number') index = expression, expression = false;
\r
1572 return Selector.matchElements(elements, expression || '*')[index || 0];
\r
1575 findChildElements: function(element, expressions) {
\r
1576 return expressions.map(function(expression) {
\r
1577 return expression.strip().split(/\s+/).inject([null], function(results, expr) {
\r
1578 var selector = new Selector(expr);
\r
1579 return results.inject([], function(elements, result) {
\r
1580 return elements.concat(selector.findElements(result || element));
\r
1588 return Selector.findChildElements(document, $A(arguments));
\r
1591 reset: function(form) {
\r
1598 serialize: function(form) {
\r
1599 var elements = Form.getElements($(form));
\r
1600 var queryComponents = new Array();
\r
1602 for (var i = 0; i < elements.length; i++) {
\r
1603 var queryComponent = Form.Element.serialize(elements[i]);
\r
1604 if (queryComponent)
\r
1605 queryComponents.push(queryComponent);
\r
1608 return queryComponents.join('&');
\r
1611 getElements: function(form) {
\r
1613 var elements = new Array();
\r
1615 for (var tagName in Form.Element.Serializers) {
\r
1616 var tagElements = form.getElementsByTagName(tagName);
\r
1617 for (var j = 0; j < tagElements.length; j++)
\r
1618 elements.push(tagElements[j]);
\r
1623 getInputs: function(form, typeName, name) {
\r
1625 var inputs = form.getElementsByTagName('input');
\r
1627 if (!typeName && !name)
\r
1630 var matchingInputs = new Array();
\r
1631 for (var i = 0; i < inputs.length; i++) {
\r
1632 var input = inputs[i];
\r
1633 if ((typeName && input.type != typeName) ||
\r
1634 (name && input.name != name))
\r
1636 matchingInputs.push(input);
\r
1639 return matchingInputs;
\r
1642 disable: function(form) {
\r
1644 var elements = Form.getElements(form);
\r
1645 for (var i = 0; i < elements.length; i++) {
\r
1646 var element = elements[i];
\r
1648 element.disabled = 'true';
\r
1653 enable: function(form) {
\r
1655 var elements = Form.getElements(form);
\r
1656 for (var i = 0; i < elements.length; i++) {
\r
1657 var element = elements[i];
\r
1658 element.disabled = '';
\r
1663 findFirstElement: function(form) {
\r
1664 return Form.getElements(form).find(function(element) {
\r
1665 return element.type != 'hidden' && !element.disabled &&
\r
1666 ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
\r
1670 focusFirstElement: function(form) {
\r
1672 Field.activate(Form.findFirstElement(form));
\r
1677 Object.extend(Form, Form.Methods);
\r
1679 /*--------------------------------------------------------------------------*/
\r
1682 focus: function(element) {
\r
1683 $(element).focus();
\r
1687 select: function(element) {
\r
1688 $(element).select();
\r
1693 Form.Element.Methods = {
\r
1694 serialize: function(element) {
\r
1695 element = $(element);
\r
1696 var method = element.tagName.toLowerCase();
\r
1697 var parameter = Form.Element.Serializers[method](element);
\r
1700 var key = encodeURIComponent(parameter[0]);
\r
1701 if (key.length == 0) return;
\r
1703 if (parameter[1].constructor != Array)
\r
1704 parameter[1] = [parameter[1]];
\r
1706 return parameter[1].map(function(value) {
\r
1707 return key + '=' + encodeURIComponent(value);
\r
1712 getValue: function(element) {
\r
1713 element = $(element);
\r
1714 var method = element.tagName.toLowerCase();
\r
1715 var parameter = Form.Element.Serializers[method](element);
\r
1718 return parameter[1];
\r
1721 clear: function(element) {
\r
1722 $(element).value = '';
\r
1726 present: function(element) {
\r
1727 return $(element).value != '';
\r
1730 activate: function(element) {
\r
1731 element = $(element);
\r
1733 if (element.select)
\r
1738 disable: function(element) {
\r
1739 element = $(element);
\r
1740 element.disabled = '';
\r
1744 enable: function(element) {
\r
1745 element = $(element);
\r
1747 element.disabled = 'true';
\r
1752 Object.extend(Form.Element, Form.Element.Methods);
\r
1753 var Field = Form.Element;
\r
1755 /*--------------------------------------------------------------------------*/
\r
1757 Form.Element.Serializers = {
\r
1758 input: function(element) {
\r
1759 switch (element.type.toLowerCase()) {
\r
1762 return Form.Element.Serializers.inputSelector(element);
\r
1764 return Form.Element.Serializers.textarea(element);
\r
1769 inputSelector: function(element) {
\r
1770 if (element.checked)
\r
1771 return [element.name, element.value];
\r
1774 textarea: function(element) {
\r
1775 return [element.name, element.value];
\r
1778 select: function(element) {
\r
1779 return Form.Element.Serializers[element.type == 'select-one' ?
\r
1780 'selectOne' : 'selectMany'](element);
\r
1783 selectOne: function(element) {
\r
1784 var value = '', opt, index = element.selectedIndex;
\r
1786 opt = element.options[index];
\r
1787 value = opt.value || opt.text;
\r
1789 return [element.name, value];
\r
1792 selectMany: function(element) {
\r
1794 for (var i = 0; i < element.length; i++) {
\r
1795 var opt = element.options[i];
\r
1797 value.push(opt.value || opt.text);
\r
1799 return [element.name, value];
\r
1803 /*--------------------------------------------------------------------------*/
\r
1805 var $F = Form.Element.getValue;
\r
1807 /*--------------------------------------------------------------------------*/
\r
1809 Abstract.TimedObserver = function() {}
\r
1810 Abstract.TimedObserver.prototype = {
\r
1811 initialize: function(element, frequency, callback) {
\r
1812 this.frequency = frequency;
\r
1813 this.element = $(element);
\r
1814 this.callback = callback;
\r
1816 this.lastValue = this.getValue();
\r
1817 this.registerCallback();
\r
1820 registerCallback: function() {
\r
1821 setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
\r
1824 onTimerEvent: function() {
\r
1825 var value = this.getValue();
\r
1826 if (this.lastValue != value) {
\r
1827 this.callback(this.element, value);
\r
1828 this.lastValue = value;
\r
1833 Form.Element.Observer = Class.create();
\r
1834 Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
\r
1835 getValue: function() {
\r
1836 return Form.Element.getValue(this.element);
\r
1840 Form.Observer = Class.create();
\r
1841 Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
\r
1842 getValue: function() {
\r
1843 return Form.serialize(this.element);
\r
1847 /*--------------------------------------------------------------------------*/
\r
1849 Abstract.EventObserver = function() {}
\r
1850 Abstract.EventObserver.prototype = {
\r
1851 initialize: function(element, callback) {
\r
1852 this.element = $(element);
\r
1853 this.callback = callback;
\r
1855 this.lastValue = this.getValue();
\r
1856 if (this.element.tagName.toLowerCase() == 'form')
\r
1857 this.registerFormCallbacks();
\r
1859 this.registerCallback(this.element);
\r
1862 onElementEvent: function() {
\r
1863 var value = this.getValue();
\r
1864 if (this.lastValue != value) {
\r
1865 this.callback(this.element, value);
\r
1866 this.lastValue = value;
\r
1870 registerFormCallbacks: function() {
\r
1871 var elements = Form.getElements(this.element);
\r
1872 for (var i = 0; i < elements.length; i++)
\r
1873 this.registerCallback(elements[i]);
\r
1876 registerCallback: function(element) {
\r
1877 if (element.type) {
\r
1878 switch (element.type.toLowerCase()) {
\r
1881 Event.observe(element, 'click', this.onElementEvent.bind(this));
\r
1884 Event.observe(element, 'change', this.onElementEvent.bind(this));
\r
1891 Form.Element.EventObserver = Class.create();
\r
1892 Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
\r
1893 getValue: function() {
\r
1894 return Form.Element.getValue(this.element);
\r
1898 Form.EventObserver = Class.create();
\r
1899 Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
\r
1900 getValue: function() {
\r
1901 return Form.serialize(this.element);
\r
1904 if (!window.Event) {
\r
1905 var Event = new Object();
\r
1908 Object.extend(Event, {
\r
1923 element: function(event) {
\r
1924 return event.target || event.srcElement;
\r
1927 isLeftClick: function(event) {
\r
1928 return (((event.which) && (event.which == 1)) ||
\r
1929 ((event.button) && (event.button == 1)));
\r
1932 pointerX: function(event) {
\r
1933 return event.pageX || (event.clientX +
\r
1934 (document.documentElement.scrollLeft || document.body.scrollLeft));
\r
1937 pointerY: function(event) {
\r
1938 return event.pageY || (event.clientY +
\r
1939 (document.documentElement.scrollTop || document.body.scrollTop));
\r
1942 stop: function(event) {
\r
1943 if (event.preventDefault) {
\r
1944 event.preventDefault();
\r
1945 event.stopPropagation();
\r
1947 event.returnValue = false;
\r
1948 event.cancelBubble = true;
\r
1952 // find the first node with the given tagName, starting from the
\r
1953 // node the event was triggered on; traverses the DOM upwards
\r
1954 findElement: function(event, tagName) {
\r
1955 var element = Event.element(event);
\r
1956 while (element.parentNode && (!element.tagName ||
\r
1957 (element.tagName.toUpperCase() != tagName.toUpperCase())))
\r
1958 element = element.parentNode;
\r
1964 _observeAndCache: function(element, name, observer, useCapture) {
\r
1965 if (!this.observers) this.observers = [];
\r
1966 if (element.addEventListener) {
\r
1967 this.observers.push([element, name, observer, useCapture]);
\r
1968 element.addEventListener(name, observer, useCapture);
\r
1969 } else if (element.attachEvent) {
\r
1970 this.observers.push([element, name, observer, useCapture]);
\r
1971 element.attachEvent('on' + name, observer);
\r
1975 unloadCache: function() {
\r
1976 if (!Event.observers) return;
\r
1977 for (var i = 0; i < Event.observers.length; i++) {
\r
1978 Event.stopObserving.apply(this, Event.observers[i]);
\r
1979 Event.observers[i][0] = null;
\r
1981 Event.observers = false;
\r
1984 observe: function(element, name, observer, useCapture) {
\r
1985 element = $(element);
\r
1986 useCapture = useCapture || false;
\r
1988 if (name == 'keypress' &&
\r
1989 (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
\r
1990 || element.attachEvent))
\r
1993 Event._observeAndCache(element, name, observer, useCapture);
\r
1996 stopObserving: function(element, name, observer, useCapture) {
\r
1997 element = $(element);
\r
1998 useCapture = useCapture || false;
\r
2000 if (name == 'keypress' &&
\r
2001 (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
\r
2002 || element.detachEvent))
\r
2005 if (element.removeEventListener) {
\r
2006 element.removeEventListener(name, observer, useCapture);
\r
2007 } else if (element.detachEvent) {
\r
2009 element.detachEvent('on' + name, observer);
\r
2015 /* prevent memory leaks in IE */
\r
2016 if (navigator.appVersion.match(/\bMSIE\b/))
\r
2017 Event.observe(window, 'unload', Event.unloadCache, false);
\r
2019 // set to true if needed, warning: firefox performance problems
\r
2020 // NOT neeeded for page scrolling, only if draggable contained in
\r
2021 // scrollable elements
\r
2022 includeScrollOffsets: false,
\r
2024 // must be called before calling withinIncludingScrolloffset, every time the
\r
2025 // page is scrolled
\r
2026 prepare: function() {
\r
2027 this.deltaX = window.pageXOffset
\r
2028 || document.documentElement.scrollLeft
\r
2029 || document.body.scrollLeft
\r
2031 this.deltaY = window.pageYOffset
\r
2032 || document.documentElement.scrollTop
\r
2033 || document.body.scrollTop
\r
2037 realOffset: function(element) {
\r
2038 var valueT = 0, valueL = 0;
\r
2040 valueT += element.scrollTop || 0;
\r
2041 valueL += element.scrollLeft || 0;
\r
2042 element = element.parentNode;
\r
2043 } while (element);
\r
2044 return [valueL, valueT];
\r
2047 cumulativeOffset: function(element) {
\r
2048 var valueT = 0, valueL = 0;
\r
2050 valueT += element.offsetTop || 0;
\r
2051 valueL += element.offsetLeft || 0;
\r
2052 element = element.offsetParent;
\r
2053 } while (element);
\r
2054 return [valueL, valueT];
\r
2057 positionedOffset: function(element) {
\r
2058 var valueT = 0, valueL = 0;
\r
2060 valueT += element.offsetTop || 0;
\r
2061 valueL += element.offsetLeft || 0;
\r
2062 element = element.offsetParent;
\r
2064 p = Element.getStyle(element, 'position');
\r
2065 if (p == 'relative' || p == 'absolute') break;
\r
2067 } while (element);
\r
2068 return [valueL, valueT];
\r
2071 offsetParent: function(element) {
\r
2072 if (element.offsetParent) return element.offsetParent;
\r
2073 if (element == document.body) return element;
\r
2075 while ((element = element.parentNode) && element != document.body)
\r
2076 if (Element.getStyle(element, 'position') != 'static')
\r
2079 return document.body;
\r
2082 // caches x/y coordinate pair to use with overlap
\r
2083 within: function(element, x, y) {
\r
2084 if (this.includeScrollOffsets)
\r
2085 return this.withinIncludingScrolloffsets(element, x, y);
\r
2088 this.offset = this.cumulativeOffset(element);
\r
2090 return (y >= this.offset[1] &&
\r
2091 y < this.offset[1] + element.offsetHeight &&
\r
2092 x >= this.offset[0] &&
\r
2093 x < this.offset[0] + element.offsetWidth);
\r
2096 withinIncludingScrolloffsets: function(element, x, y) {
\r
2097 var offsetcache = this.realOffset(element);
\r
2099 this.xcomp = x + offsetcache[0] - this.deltaX;
\r
2100 this.ycomp = y + offsetcache[1] - this.deltaY;
\r
2101 this.offset = this.cumulativeOffset(element);
\r
2103 return (this.ycomp >= this.offset[1] &&
\r
2104 this.ycomp < this.offset[1] + element.offsetHeight &&
\r
2105 this.xcomp >= this.offset[0] &&
\r
2106 this.xcomp < this.offset[0] + element.offsetWidth);
\r
2109 // within must be called directly before
\r
2110 overlap: function(mode, element) {
\r
2111 if (!mode) return 0;
\r
2112 if (mode == 'vertical')
\r
2113 return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
\r
2114 element.offsetHeight;
\r
2115 if (mode == 'horizontal')
\r
2116 return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
\r
2117 element.offsetWidth;
\r
2120 page: function(forElement) {
\r
2121 var valueT = 0, valueL = 0;
\r
2123 var element = forElement;
\r
2125 valueT += element.offsetTop || 0;
\r
2126 valueL += element.offsetLeft || 0;
\r
2129 if (element.offsetParent==document.body)
\r
2130 if (Element.getStyle(element,'position')=='absolute') break;
\r
2132 } while (element = element.offsetParent);
\r
2134 element = forElement;
\r
2136 if (!window.opera || element.tagName=='BODY') {
\r
2137 valueT -= element.scrollTop || 0;
\r
2138 valueL -= element.scrollLeft || 0;
\r
2140 } while (element = element.parentNode);
\r
2142 return [valueL, valueT];
\r
2145 clone: function(source, target) {
\r
2146 var options = Object.extend({
\r
2153 }, arguments[2] || {})
\r
2155 // find page position of source
\r
2156 source = $(source);
\r
2157 var p = Position.page(source);
\r
2159 // find coordinate system to use
\r
2160 target = $(target);
\r
2161 var delta = [0, 0];
\r
2162 var parent = null;
\r
2163 // delta [0,0] will do fine with position: fixed elements,
\r
2164 // position:absolute needs offsetParent deltas
\r
2165 if (Element.getStyle(target,'position') == 'absolute') {
\r
2166 parent = Position.offsetParent(target);
\r
2167 delta = Position.page(parent);
\r
2170 // correct by body offsets (fixes Safari)
\r
2171 if (parent == document.body) {
\r
2172 delta[0] -= document.body.offsetLeft;
\r
2173 delta[1] -= document.body.offsetTop;
\r
2177 if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
\r
2178 if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
\r
2179 if(options.setWidth) target.style.width = source.offsetWidth + 'px';
\r
2180 if(options.setHeight) target.style.height = source.offsetHeight + 'px';
\r
2183 absolutize: function(element) {
\r
2184 element = $(element);
\r
2185 if (element.style.position == 'absolute') return;
\r
2186 Position.prepare();
\r
2188 var offsets = Position.positionedOffset(element);
\r
2189 var top = offsets[1];
\r
2190 var left = offsets[0];
\r
2191 var width = element.clientWidth;
\r
2192 var height = element.clientHeight;
\r
2194 element._originalLeft = left - parseFloat(element.style.left || 0);
\r
2195 element._originalTop = top - parseFloat(element.style.top || 0);
\r
2196 element._originalWidth = element.style.width;
\r
2197 element._originalHeight = element.style.height;
\r
2199 element.style.position = 'absolute';
\r
2200 element.style.top = top + 'px';;
\r
2201 element.style.left = left + 'px';;
\r
2202 element.style.width = width + 'px';;
\r
2203 element.style.height = height + 'px';;
\r
2206 relativize: function(element) {
\r
2207 element = $(element);
\r
2208 if (element.style.position == 'relative') return;
\r
2209 Position.prepare();
\r
2211 element.style.position = 'relative';
\r
2212 var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
\r
2213 var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
\r
2215 element.style.top = top + 'px';
\r
2216 element.style.left = left + 'px';
\r
2217 element.style.height = element._originalHeight;
\r
2218 element.style.width = element._originalWidth;
\r
2222 // Safari returns margins on body which is incorrect if the child is absolutely
\r
2223 // positioned. For performance reasons, redefine Position.cumulativeOffset for
\r
2224 // KHTML/WebKit only.
\r
2225 if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
\r
2226 Position.cumulativeOffset = function(element) {
\r
2227 var valueT = 0, valueL = 0;
\r
2229 valueT += element.offsetTop || 0;
\r
2230 valueL += element.offsetLeft || 0;
\r
2231 if (element.offsetParent == document.body)
\r
2232 if (Element.getStyle(element, 'position') == 'absolute') break;
\r
2234 element = element.offsetParent;
\r
2235 } while (element);
\r
2237 return [valueL, valueT];
\r
2241 Element.addMethods();