##// END OF EJS Templates
Upgraded to Prototype 1.6.0.1....
Jean-Philippe Lang -
r1580:75a5dbd01dd4
parent child
Show More
@@ -28,11 +28,11 ContextMenu.prototype = {
28 28 RightClick: function(e) {
29 29 this.hideMenu();
30 30 // do not show the context menu on links
31 if (Event.findElement(e, 'a') != document) { return; }
31 if (Event.findElement(e, 'a') != document && Event.findElement(e, 'a') != undefined) { return; }
32 32 // right-click simulated by Alt+Click with Opera
33 33 if (window.opera && !e.altKey) { return; }
34 34 var tr = Event.findElement(e, 'tr');
35 if ((tr == document) || !tr.hasClassName('hascontextmenu')) { return; }
35 if (tr == document || tr == undefined || !tr.hasClassName('hascontextmenu')) { return; }
36 36 Event.stop(e);
37 37 if (!this.isSelected(tr)) {
38 38 this.unselectAll();
@@ -44,14 +44,14 ContextMenu.prototype = {
44 44
45 45 Click: function(e) {
46 46 this.hideMenu();
47 if (Event.findElement(e, 'a') != document) { return; }
47 if (Event.findElement(e, 'a') != document && Event.findElement(e, 'a') != undefined ) { return; }
48 48 if (window.opera && e.altKey) { return; }
49 49 if (Event.isLeftClick(e) || (navigator.appVersion.match(/\bMSIE\b/))) {
50 50 var tr = Event.findElement(e, 'tr');
51 51 if (tr!=document && tr.hasClassName('hascontextmenu')) {
52 52 // a row was clicked, check if the click was on checkbox
53 53 var box = Event.findElement(e, 'input');
54 if (box!=document) {
54 if (box!=document && box!=undefined) {
55 55 // a checkbox may be clicked
56 56 if (box.checked) {
57 57 tr.addClassName('context-menu-selection');
This diff has been collapsed as it changes many lines, (838 lines changed) Show them Hide them
@@ -1,6 +1,6
1 // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 // (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3 // (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com)
1 // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 // (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3 // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
4 4 // Contributors:
5 5 // Richard Livsey
6 6 // Rahul Bhargava
@@ -37,22 +37,23
37 37 if(typeof Effect == 'undefined')
38 38 throw("controls.js requires including script.aculo.us' effects.js library");
39 39
40 var Autocompleter = {}
41 Autocompleter.Base = function() {};
42 Autocompleter.Base.prototype = {
40 var Autocompleter = { }
41 Autocompleter.Base = Class.create({
43 42 baseInitialize: function(element, update, options) {
44 this.element = $(element);
43 element = $(element)
44 this.element = element;
45 45 this.update = $(update);
46 46 this.hasFocus = false;
47 47 this.changed = false;
48 48 this.active = false;
49 49 this.index = 0;
50 50 this.entryCount = 0;
51 this.oldElementValue = this.element.value;
51 52
52 53 if(this.setOptions)
53 54 this.setOptions(options);
54 55 else
55 this.options = options || {};
56 this.options = options || { };
56 57
57 58 this.options.paramName = this.options.paramName || this.element.name;
58 59 this.options.tokens = this.options.tokens || [];
@@ -74,6 +75,9 Autocompleter.Base.prototype = {
74 75
75 76 if(typeof(this.options.tokens) == 'string')
76 77 this.options.tokens = new Array(this.options.tokens);
78 // Force carriage returns as token delimiters anyway
79 if (!this.options.tokens.include('\n'))
80 this.options.tokens.push('\n');
77 81
78 82 this.observer = null;
79 83
@@ -81,15 +85,14 Autocompleter.Base.prototype = {
81 85
82 86 Element.hide(this.update);
83 87
84 Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
85 Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
88 Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
89 Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
86 90 },
87 91
88 92 show: function() {
89 93 if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
90 94 if(!this.iefix &&
91 (navigator.appVersion.indexOf('MSIE')>0) &&
92 (navigator.userAgent.indexOf('Opera')<0) &&
95 (Prototype.Browser.IE) &&
93 96 (Element.getStyle(this.update, 'position')=='absolute')) {
94 97 new Insertion.After(this.update,
95 98 '<iframe id="' + this.update.id + '_iefix" '+
@@ -139,17 +142,17 Autocompleter.Base.prototype = {
139 142 case Event.KEY_UP:
140 143 this.markPrevious();
141 144 this.render();
142 if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
145 Event.stop(event);
143 146 return;
144 147 case Event.KEY_DOWN:
145 148 this.markNext();
146 149 this.render();
147 if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
150 Event.stop(event);
148 151 return;
149 152 }
150 153 else
151 154 if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
152 (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
155 (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
153 156
154 157 this.changed = true;
155 158 this.hasFocus = true;
@@ -195,7 +198,6 Autocompleter.Base.prototype = {
195 198 this.index==i ?
196 199 Element.addClassName(this.getEntry(i),"selected") :
197 200 Element.removeClassName(this.getEntry(i),"selected");
198
199 201 if(this.hasFocus) {
200 202 this.show();
201 203 this.active = true;
@@ -238,21 +240,22 Autocompleter.Base.prototype = {
238 240 }
239 241 var value = '';
240 242 if (this.options.select) {
241 var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
243 var nodes = $(selectedElement).select('.' + this.options.select) || [];
242 244 if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
243 245 } else
244 246 value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
245 247
246 var lastTokenPos = this.findLastToken();
247 if (lastTokenPos != -1) {
248 var newValue = this.element.value.substr(0, lastTokenPos + 1);
249 var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
248 var bounds = this.getTokenBounds();
249 if (bounds[0] != -1) {
250 var newValue = this.element.value.substr(0, bounds[0]);
251 var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
250 252 if (whitespace)
251 253 newValue += whitespace[0];
252 this.element.value = newValue + value;
254 this.element.value = newValue + value + this.element.value.substr(bounds[1]);
253 255 } else {
254 256 this.element.value = value;
255 257 }
258 this.oldElementValue = this.element.value;
256 259 this.element.focus();
257 260
258 261 if (this.options.afterUpdateElement)
@@ -296,39 +299,48 Autocompleter.Base.prototype = {
296 299
297 300 onObserverEvent: function() {
298 301 this.changed = false;
302 this.tokenBounds = null;
299 303 if(this.getToken().length>=this.options.minChars) {
300 this.startIndicator();
301 304 this.getUpdatedChoices();
302 305 } else {
303 306 this.active = false;
304 307 this.hide();
305 308 }
309 this.oldElementValue = this.element.value;
306 310 },
307 311
308 312 getToken: function() {
309 var tokenPos = this.findLastToken();
310 if (tokenPos != -1)
311 var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
312 else
313 var ret = this.element.value;
314
315 return /\n/.test(ret) ? '' : ret;
316 },
317
318 findLastToken: function() {
319 var lastTokenPos = -1;
320
321 for (var i=0; i<this.options.tokens.length; i++) {
322 var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
323 if (thisTokenPos > lastTokenPos)
324 lastTokenPos = thisTokenPos;
313 var bounds = this.getTokenBounds();
314 return this.element.value.substring(bounds[0], bounds[1]).strip();
315 },
316
317 getTokenBounds: function() {
318 if (null != this.tokenBounds) return this.tokenBounds;
319 var value = this.element.value;
320 if (value.strip().empty()) return [-1, 0];
321 var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
322 var offset = (diff == this.oldElementValue.length ? 1 : 0);
323 var prevTokenPos = -1, nextTokenPos = value.length;
324 var tp;
325 for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
326 tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
327 if (tp > prevTokenPos) prevTokenPos = tp;
328 tp = value.indexOf(this.options.tokens[index], diff + offset);
329 if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
325 330 }
326 return lastTokenPos;
331 return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
327 332 }
328 }
333 });
334
335 Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
336 var boundary = Math.min(newS.length, oldS.length);
337 for (var index = 0; index < boundary; ++index)
338 if (newS[index] != oldS[index])
339 return index;
340 return boundary;
341 };
329 342
330 Ajax.Autocompleter = Class.create();
331 Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
343 Ajax.Autocompleter = Class.create(Autocompleter.Base, {
332 344 initialize: function(element, update, url, options) {
333 345 this.baseInitialize(element, update, options);
334 346 this.options.asynchronous = true;
@@ -338,7 +350,9 Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
338 350 },
339 351
340 352 getUpdatedChoices: function() {
341 entry = encodeURIComponent(this.options.paramName) + '=' +
353 this.startIndicator();
354
355 var entry = encodeURIComponent(this.options.paramName) + '=' +
342 356 encodeURIComponent(this.getToken());
343 357
344 358 this.options.parameters = this.options.callback ?
@@ -346,14 +360,13 Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
346 360
347 361 if(this.options.defaultParams)
348 362 this.options.parameters += '&' + this.options.defaultParams;
349
363
350 364 new Ajax.Request(this.url, this.options);
351 365 },
352 366
353 367 onComplete: function(request) {
354 368 this.updateChoices(request.responseText);
355 369 }
356
357 370 });
358 371
359 372 // The local array autocompleter. Used when you'd prefer to
@@ -391,8 +404,7 Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
391 404 // In that case, the other options above will not apply unless
392 405 // you support them.
393 406
394 Autocompleter.Local = Class.create();
395 Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
407 Autocompleter.Local = Class.create(Autocompleter.Base, {
396 408 initialize: function(element, update, array, options) {
397 409 this.baseInitialize(element, update, options);
398 410 this.options.array = array;
@@ -448,13 +460,12 Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
448 460 ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
449 461 return "<ul>" + ret.join('') + "</ul>";
450 462 }
451 }, options || {});
463 }, options || { });
452 464 }
453 465 });
454 466
455 // AJAX in-place editor
456 //
457 // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
467 // AJAX in-place editor and collection editor
468 // Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
458 469
459 470 // Use this if you notice weird scrolling problems on some browsers,
460 471 // the DOM might be a bit confused when this gets called so do this
@@ -465,353 +476,472 Field.scrollFreeActivate = function(field) {
465 476 }, 1);
466 477 }
467 478
468 Ajax.InPlaceEditor = Class.create();
469 Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
470 Ajax.InPlaceEditor.prototype = {
479 Ajax.InPlaceEditor = Class.create({
471 480 initialize: function(element, url, options) {
472 481 this.url = url;
473 this.element = $(element);
474
475 this.options = Object.extend({
476 paramName: "value",
477 okButton: true,
478 okText: "ok",
479 cancelLink: true,
480 cancelText: "cancel",
481 savingText: "Saving...",
482 clickToEditText: "Click to edit",
483 okText: "ok",
484 rows: 1,
485 onComplete: function(transport, element) {
486 new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
487 },
488 onFailure: function(transport) {
489 alert("Error communicating with the server: " + transport.responseText.stripTags());
490 },
491 callback: function(form) {
492 return Form.serialize(form);
493 },
494 handleLineBreaks: true,
495 loadingText: 'Loading...',
496 savingClassName: 'inplaceeditor-saving',
497 loadingClassName: 'inplaceeditor-loading',
498 formClassName: 'inplaceeditor-form',
499 highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
500 highlightendcolor: "#FFFFFF",
501 externalControl: null,
502 submitOnBlur: false,
503 ajaxOptions: {},
504 evalScripts: false
505 }, options || {});
506
507 if(!this.options.formId && this.element.id) {
508 this.options.formId = this.element.id + "-inplaceeditor";
509 if ($(this.options.formId)) {
510 // there's already a form with that name, don't specify an id
511 this.options.formId = null;
512 }
482 this.element = element = $(element);
483 this.prepareOptions();
484 this._controls = { };
485 arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
486 Object.extend(this.options, options || { });
487 if (!this.options.formId && this.element.id) {
488 this.options.formId = this.element.id + '-inplaceeditor';
489 if ($(this.options.formId))
490 this.options.formId = '';
513 491 }
514
515 if (this.options.externalControl) {
492 if (this.options.externalControl)
516 493 this.options.externalControl = $(this.options.externalControl);
517 }
518
519 this.originalBackground = Element.getStyle(this.element, 'background-color');
520 if (!this.originalBackground) {
521 this.originalBackground = "transparent";
522 }
523
494 if (!this.options.externalControl)
495 this.options.externalControlOnly = false;
496 this._originalBackground = this.element.getStyle('background-color') || 'transparent';
524 497 this.element.title = this.options.clickToEditText;
525
526 this.onclickListener = this.enterEditMode.bindAsEventListener(this);
527 this.mouseoverListener = this.enterHover.bindAsEventListener(this);
528 this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
529 Event.observe(this.element, 'click', this.onclickListener);
530 Event.observe(this.element, 'mouseover', this.mouseoverListener);
531 Event.observe(this.element, 'mouseout', this.mouseoutListener);
532 if (this.options.externalControl) {
533 Event.observe(this.options.externalControl, 'click', this.onclickListener);
534 Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
535 Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
498 this._boundCancelHandler = this.handleFormCancellation.bind(this);
499 this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
500 this._boundFailureHandler = this.handleAJAXFailure.bind(this);
501 this._boundSubmitHandler = this.handleFormSubmission.bind(this);
502 this._boundWrapperHandler = this.wrapUp.bind(this);
503 this.registerListeners();
504 },
505 checkForEscapeOrReturn: function(e) {
506 if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
507 if (Event.KEY_ESC == e.keyCode)
508 this.handleFormCancellation(e);
509 else if (Event.KEY_RETURN == e.keyCode)
510 this.handleFormSubmission(e);
511 },
512 createControl: function(mode, handler, extraClasses) {
513 var control = this.options[mode + 'Control'];
514 var text = this.options[mode + 'Text'];
515 if ('button' == control) {
516 var btn = document.createElement('input');
517 btn.type = 'submit';
518 btn.value = text;
519 btn.className = 'editor_' + mode + '_button';
520 if ('cancel' == mode)
521 btn.onclick = this._boundCancelHandler;
522 this._form.appendChild(btn);
523 this._controls[mode] = btn;
524 } else if ('link' == control) {
525 var link = document.createElement('a');
526 link.href = '#';
527 link.appendChild(document.createTextNode(text));
528 link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
529 link.className = 'editor_' + mode + '_link';
530 if (extraClasses)
531 link.className += ' ' + extraClasses;
532 this._form.appendChild(link);
533 this._controls[mode] = link;
536 534 }
537 535 },
538 enterEditMode: function(evt) {
539 if (this.saving) return;
540 if (this.editing) return;
541 this.editing = true;
542 this.onEnterEditMode();
543 if (this.options.externalControl) {
544 Element.hide(this.options.externalControl);
545 }
546 Element.hide(this.element);
547 this.createForm();
548 this.element.parentNode.insertBefore(this.form, this.element);
549 if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
550 // stop the event to avoid a page refresh in Safari
551 if (evt) {
552 Event.stop(evt);
553 }
554 return false;
555 },
556 createForm: function() {
557 this.form = document.createElement("form");
558 this.form.id = this.options.formId;
559 Element.addClassName(this.form, this.options.formClassName)
560 this.form.onsubmit = this.onSubmit.bind(this);
561
562 this.createEditField();
563
564 if (this.options.textarea) {
565 var br = document.createElement("br");
566 this.form.appendChild(br);
567 }
568
569 if (this.options.okButton) {
570 okButton = document.createElement("input");
571 okButton.type = "submit";
572 okButton.value = this.options.okText;
573 okButton.className = 'editor_ok_button';
574 this.form.appendChild(okButton);
575 }
576
577 if (this.options.cancelLink) {
578 cancelLink = document.createElement("a");
579 cancelLink.href = "#";
580 cancelLink.appendChild(document.createTextNode(this.options.cancelText));
581 cancelLink.onclick = this.onclickCancel.bind(this);
582 cancelLink.className = 'editor_cancel';
583 this.form.appendChild(cancelLink);
584 }
585 },
586 hasHTMLLineBreaks: function(string) {
587 if (!this.options.handleLineBreaks) return false;
588 return string.match(/<br/i) || string.match(/<p>/i);
589 },
590 convertHTMLLineBreaks: function(string) {
591 return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
592 },
593 536 createEditField: function() {
594 var text;
595 if(this.options.loadTextURL) {
596 text = this.options.loadingText;
597 } else {
598 text = this.getText();
599 }
600
601 var obj = this;
602
603 if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
604 this.options.textarea = false;
605 var textField = document.createElement("input");
606 textField.obj = this;
607 textField.type = "text";
608 textField.name = this.options.paramName;
609 textField.value = text;
610 textField.style.backgroundColor = this.options.highlightcolor;
611 textField.className = 'editor_field';
537 var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
538 var fld;
539 if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
540 fld = document.createElement('input');
541 fld.type = 'text';
612 542 var size = this.options.size || this.options.cols || 0;
613 if (size != 0) textField.size = size;
614 if (this.options.submitOnBlur)
615 textField.onblur = this.onSubmit.bind(this);
616 this.editField = textField;
543 if (0 < size) fld.size = size;
617 544 } else {
618 this.options.textarea = true;
619 var textArea = document.createElement("textarea");
620 textArea.obj = this;
621 textArea.name = this.options.paramName;
622 textArea.value = this.convertHTMLLineBreaks(text);
623 textArea.rows = this.options.rows;
624 textArea.cols = this.options.cols || 40;
625 textArea.className = 'editor_field';
626 if (this.options.submitOnBlur)
627 textArea.onblur = this.onSubmit.bind(this);
628 this.editField = textArea;
545 fld = document.createElement('textarea');
546 fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
547 fld.cols = this.options.cols || 40;
629 548 }
630
631 if(this.options.loadTextURL) {
549 fld.name = this.options.paramName;
550 fld.value = text; // No HTML breaks conversion anymore
551 fld.className = 'editor_field';
552 if (this.options.submitOnBlur)
553 fld.onblur = this._boundSubmitHandler;
554 this._controls.editor = fld;
555 if (this.options.loadTextURL)
632 556 this.loadExternalText();
633 }
634 this.form.appendChild(this.editField);
557 this._form.appendChild(this._controls.editor);
558 },
559 createForm: function() {
560 var ipe = this;
561 function addText(mode, condition) {
562 var text = ipe.options['text' + mode + 'Controls'];
563 if (!text || condition === false) return;
564 ipe._form.appendChild(document.createTextNode(text));
565 };
566 this._form = $(document.createElement('form'));
567 this._form.id = this.options.formId;
568 this._form.addClassName(this.options.formClassName);
569 this._form.onsubmit = this._boundSubmitHandler;
570 this.createEditField();
571 if ('textarea' == this._controls.editor.tagName.toLowerCase())
572 this._form.appendChild(document.createElement('br'));
573 if (this.options.onFormCustomization)
574 this.options.onFormCustomization(this, this._form);
575 addText('Before', this.options.okControl || this.options.cancelControl);
576 this.createControl('ok', this._boundSubmitHandler);
577 addText('Between', this.options.okControl && this.options.cancelControl);
578 this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
579 addText('After', this.options.okControl || this.options.cancelControl);
580 },
581 destroy: function() {
582 if (this._oldInnerHTML)
583 this.element.innerHTML = this._oldInnerHTML;
584 this.leaveEditMode();
585 this.unregisterListeners();
586 },
587 enterEditMode: function(e) {
588 if (this._saving || this._editing) return;
589 this._editing = true;
590 this.triggerCallback('onEnterEditMode');
591 if (this.options.externalControl)
592 this.options.externalControl.hide();
593 this.element.hide();
594 this.createForm();
595 this.element.parentNode.insertBefore(this._form, this.element);
596 if (!this.options.loadTextURL)
597 this.postProcessEditField();
598 if (e) Event.stop(e);
599 },
600 enterHover: function(e) {
601 if (this.options.hoverClassName)
602 this.element.addClassName(this.options.hoverClassName);
603 if (this._saving) return;
604 this.triggerCallback('onEnterHover');
635 605 },
636 606 getText: function() {
637 607 return this.element.innerHTML;
638 608 },
639 loadExternalText: function() {
640 Element.addClassName(this.form, this.options.loadingClassName);
641 this.editField.disabled = true;
642 new Ajax.Request(
643 this.options.loadTextURL,
644 Object.extend({
645 asynchronous: true,
646 onComplete: this.onLoadedExternalText.bind(this)
647 }, this.options.ajaxOptions)
648 );
649 },
650 onLoadedExternalText: function(transport) {
651 Element.removeClassName(this.form, this.options.loadingClassName);
652 this.editField.disabled = false;
653 this.editField.value = transport.responseText.stripTags();
654 Field.scrollFreeActivate(this.editField);
655 },
656 onclickCancel: function() {
657 this.onComplete();
658 this.leaveEditMode();
659 return false;
660 },
661 onFailure: function(transport) {
662 this.options.onFailure(transport);
663 if (this.oldInnerHTML) {
664 this.element.innerHTML = this.oldInnerHTML;
665 this.oldInnerHTML = null;
609 handleAJAXFailure: function(transport) {
610 this.triggerCallback('onFailure', transport);
611 if (this._oldInnerHTML) {
612 this.element.innerHTML = this._oldInnerHTML;
613 this._oldInnerHTML = null;
666 614 }
667 return false;
668 615 },
669 onSubmit: function() {
670 // onLoading resets these so we need to save them away for the Ajax call
671 var form = this.form;
672 var value = this.editField.value;
673
674 // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
675 // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
676 // to be displayed indefinitely
677 this.onLoading();
678
679 if (this.options.evalScripts) {
680 new Ajax.Request(
681 this.url, Object.extend({
682 parameters: this.options.callback(form, value),
683 onComplete: this.onComplete.bind(this),
684 onFailure: this.onFailure.bind(this),
685 asynchronous:true,
686 evalScripts:true
687 }, this.options.ajaxOptions));
688 } else {
689 new Ajax.Updater(
690 { success: this.element,
691 // don't update on failure (this could be an option)
692 failure: null },
693 this.url, Object.extend({
694 parameters: this.options.callback(form, value),
695 onComplete: this.onComplete.bind(this),
696 onFailure: this.onFailure.bind(this)
697 }, this.options.ajaxOptions));
698 }
699 // stop the event to avoid a page refresh in Safari
700 if (arguments.length > 1) {
701 Event.stop(arguments[0]);
616 handleFormCancellation: function(e) {
617 this.wrapUp();
618 if (e) Event.stop(e);
619 },
620 handleFormSubmission: function(e) {
621 var form = this._form;
622 var value = $F(this._controls.editor);
623 this.prepareSubmission();
624 var params = this.options.callback(form, value) || '';
625 if (Object.isString(params))
626 params = params.toQueryParams();
627 params.editorId = this.element.id;
628 if (this.options.htmlResponse) {
629 var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
630 Object.extend(options, {
631 parameters: params,
632 onComplete: this._boundWrapperHandler,
633 onFailure: this._boundFailureHandler
634 });
635 new Ajax.Updater({ success: this.element }, this.url, options);
636 } else {
637 var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
638 Object.extend(options, {
639 parameters: params,
640 onComplete: this._boundWrapperHandler,
641 onFailure: this._boundFailureHandler
642 });
643 new Ajax.Request(this.url, options);
702 644 }
703 return false;
645 if (e) Event.stop(e);
646 },
647 leaveEditMode: function() {
648 this.element.removeClassName(this.options.savingClassName);
649 this.removeForm();
650 this.leaveHover();
651 this.element.style.backgroundColor = this._originalBackground;
652 this.element.show();
653 if (this.options.externalControl)
654 this.options.externalControl.show();
655 this._saving = false;
656 this._editing = false;
657 this._oldInnerHTML = null;
658 this.triggerCallback('onLeaveEditMode');
659 },
660 leaveHover: function(e) {
661 if (this.options.hoverClassName)
662 this.element.removeClassName(this.options.hoverClassName);
663 if (this._saving) return;
664 this.triggerCallback('onLeaveHover');
704 665 },
705 onLoading: function() {
706 this.saving = true;
666 loadExternalText: function() {
667 this._form.addClassName(this.options.loadingClassName);
668 this._controls.editor.disabled = true;
669 var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
670 Object.extend(options, {
671 parameters: 'editorId=' + encodeURIComponent(this.element.id),
672 onComplete: Prototype.emptyFunction,
673 onSuccess: function(transport) {
674 this._form.removeClassName(this.options.loadingClassName);
675 var text = transport.responseText;
676 if (this.options.stripLoadedTextTags)
677 text = text.stripTags();
678 this._controls.editor.value = text;
679 this._controls.editor.disabled = false;
680 this.postProcessEditField();
681 }.bind(this),
682 onFailure: this._boundFailureHandler
683 });
684 new Ajax.Request(this.options.loadTextURL, options);
685 },
686 postProcessEditField: function() {
687 var fpc = this.options.fieldPostCreation;
688 if (fpc)
689 $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
690 },
691 prepareOptions: function() {
692 this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
693 Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
694 [this._extraDefaultOptions].flatten().compact().each(function(defs) {
695 Object.extend(this.options, defs);
696 }.bind(this));
697 },
698 prepareSubmission: function() {
699 this._saving = true;
707 700 this.removeForm();
708 701 this.leaveHover();
709 702 this.showSaving();
710 703 },
704 registerListeners: function() {
705 this._listeners = { };
706 var listener;
707 $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
708 listener = this[pair.value].bind(this);
709 this._listeners[pair.key] = listener;
710 if (!this.options.externalControlOnly)
711 this.element.observe(pair.key, listener);
712 if (this.options.externalControl)
713 this.options.externalControl.observe(pair.key, listener);
714 }.bind(this));
715 },
716 removeForm: function() {
717 if (!this._form) return;
718 this._form.remove();
719 this._form = null;
720 this._controls = { };
721 },
711 722 showSaving: function() {
712 this.oldInnerHTML = this.element.innerHTML;
723 this._oldInnerHTML = this.element.innerHTML;
713 724 this.element.innerHTML = this.options.savingText;
714 Element.addClassName(this.element, this.options.savingClassName);
715 this.element.style.backgroundColor = this.originalBackground;
716 Element.show(this.element);
725 this.element.addClassName(this.options.savingClassName);
726 this.element.style.backgroundColor = this._originalBackground;
727 this.element.show();
717 728 },
718 removeForm: function() {
719 if(this.form) {
720 if (this.form.parentNode) Element.remove(this.form);
721 this.form = null;
729 triggerCallback: function(cbName, arg) {
730 if ('function' == typeof this.options[cbName]) {
731 this.options[cbName](this, arg);
722 732 }
723 733 },
724 enterHover: function() {
725 if (this.saving) return;
726 this.element.style.backgroundColor = this.options.highlightcolor;
727 if (this.effect) {
728 this.effect.cancel();
729 }
730 Element.addClassName(this.element, this.options.hoverClassName)
734 unregisterListeners: function() {
735 $H(this._listeners).each(function(pair) {
736 if (!this.options.externalControlOnly)
737 this.element.stopObserving(pair.key, pair.value);
738 if (this.options.externalControl)
739 this.options.externalControl.stopObserving(pair.key, pair.value);
740 }.bind(this));
731 741 },
732 leaveHover: function() {
733 if (this.options.backgroundColor) {
734 this.element.style.backgroundColor = this.oldBackground;
735 }
736 Element.removeClassName(this.element, this.options.hoverClassName)
737 if (this.saving) return;
738 this.effect = new Effect.Highlight(this.element, {
739 startcolor: this.options.highlightcolor,
740 endcolor: this.options.highlightendcolor,
741 restorecolor: this.originalBackground
742 wrapUp: function(transport) {
743 this.leaveEditMode();
744 // Can't use triggerCallback due to backward compatibility: requires
745 // binding + direct element
746 this._boundComplete(transport, this.element);
747 }
748 });
749
750 Object.extend(Ajax.InPlaceEditor.prototype, {
751 dispose: Ajax.InPlaceEditor.prototype.destroy
752 });
753
754 Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
755 initialize: function($super, element, url, options) {
756 this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
757 $super(element, url, options);
758 },
759
760 createEditField: function() {
761 var list = document.createElement('select');
762 list.name = this.options.paramName;
763 list.size = 1;
764 this._controls.editor = list;
765 this._collection = this.options.collection || [];
766 if (this.options.loadCollectionURL)
767 this.loadCollection();
768 else
769 this.checkForExternalText();
770 this._form.appendChild(this._controls.editor);
771 },
772
773 loadCollection: function() {
774 this._form.addClassName(this.options.loadingClassName);
775 this.showLoadingText(this.options.loadingCollectionText);
776 var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
777 Object.extend(options, {
778 parameters: 'editorId=' + encodeURIComponent(this.element.id),
779 onComplete: Prototype.emptyFunction,
780 onSuccess: function(transport) {
781 var js = transport.responseText.strip();
782 if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
783 throw 'Server returned an invalid collection representation.';
784 this._collection = eval(js);
785 this.checkForExternalText();
786 }.bind(this),
787 onFailure: this.onFailure
742 788 });
789 new Ajax.Request(this.options.loadCollectionURL, options);
743 790 },
744 leaveEditMode: function() {
745 Element.removeClassName(this.element, this.options.savingClassName);
746 this.removeForm();
747 this.leaveHover();
748 this.element.style.backgroundColor = this.originalBackground;
749 Element.show(this.element);
750 if (this.options.externalControl) {
751 Element.show(this.options.externalControl);
791
792 showLoadingText: function(text) {
793 this._controls.editor.disabled = true;
794 var tempOption = this._controls.editor.firstChild;
795 if (!tempOption) {
796 tempOption = document.createElement('option');
797 tempOption.value = '';
798 this._controls.editor.appendChild(tempOption);
799 tempOption.selected = true;
752 800 }
753 this.editing = false;
754 this.saving = false;
755 this.oldInnerHTML = null;
756 this.onLeaveEditMode();
801 tempOption.update((text || '').stripScripts().stripTags());
757 802 },
758 onComplete: function(transport) {
759 this.leaveEditMode();
760 this.options.onComplete.bind(this)(transport, this.element);
803
804 checkForExternalText: function() {
805 this._text = this.getText();
806 if (this.options.loadTextURL)
807 this.loadExternalText();
808 else
809 this.buildOptionList();
761 810 },
762 onEnterEditMode: function() {},
763 onLeaveEditMode: function() {},
764 dispose: function() {
765 if (this.oldInnerHTML) {
766 this.element.innerHTML = this.oldInnerHTML;
767 }
768 this.leaveEditMode();
769 Event.stopObserving(this.element, 'click', this.onclickListener);
770 Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
771 Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
772 if (this.options.externalControl) {
773 Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
774 Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
775 Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
776 }
811
812 loadExternalText: function() {
813 this.showLoadingText(this.options.loadingText);
814 var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
815 Object.extend(options, {
816 parameters: 'editorId=' + encodeURIComponent(this.element.id),
817 onComplete: Prototype.emptyFunction,
818 onSuccess: function(transport) {
819 this._text = transport.responseText.strip();
820 this.buildOptionList();
821 }.bind(this),
822 onFailure: this.onFailure
823 });
824 new Ajax.Request(this.options.loadTextURL, options);
825 },
826
827 buildOptionList: function() {
828 this._form.removeClassName(this.options.loadingClassName);
829 this._collection = this._collection.map(function(entry) {
830 return 2 === entry.length ? entry : [entry, entry].flatten();
831 });
832 var marker = ('value' in this.options) ? this.options.value : this._text;
833 var textFound = this._collection.any(function(entry) {
834 return entry[0] == marker;
835 }.bind(this));
836 this._controls.editor.update('');
837 var option;
838 this._collection.each(function(entry, index) {
839 option = document.createElement('option');
840 option.value = entry[0];
841 option.selected = textFound ? entry[0] == marker : 0 == index;
842 option.appendChild(document.createTextNode(entry[1]));
843 this._controls.editor.appendChild(option);
844 }.bind(this));
845 this._controls.editor.disabled = false;
846 Field.scrollFreeActivate(this._controls.editor);
777 847 }
778 };
848 });
779 849
780 Ajax.InPlaceCollectionEditor = Class.create();
781 Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
782 Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
783 createEditField: function() {
784 if (!this.cached_selectTag) {
785 var selectTag = document.createElement("select");
786 var collection = this.options.collection || [];
787 var optionTag;
788 collection.each(function(e,i) {
789 optionTag = document.createElement("option");
790 optionTag.value = (e instanceof Array) ? e[0] : e;
791 if((typeof this.options.value == 'undefined') &&
792 ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
793 if(this.options.value==optionTag.value) optionTag.selected = true;
794 optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
795 selectTag.appendChild(optionTag);
796 }.bind(this));
797 this.cached_selectTag = selectTag;
798 }
850 //**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
851 //**** This only exists for a while, in order to let ****
852 //**** users adapt to the new API. Read up on the new ****
853 //**** API and convert your code to it ASAP! ****
854
855 Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
856 if (!options) return;
857 function fallback(name, expr) {
858 if (name in options || expr === undefined) return;
859 options[name] = expr;
860 };
861 fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
862 options.cancelLink == options.cancelButton == false ? false : undefined)));
863 fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
864 options.okLink == options.okButton == false ? false : undefined)));
865 fallback('highlightColor', options.highlightcolor);
866 fallback('highlightEndColor', options.highlightendcolor);
867 };
799 868
800 this.editField = this.cached_selectTag;
801 if(this.options.loadTextURL) this.loadExternalText();
802 this.form.appendChild(this.editField);
803 this.options.callback = function(form, value) {
804 return "value=" + encodeURIComponent(value);
869 Object.extend(Ajax.InPlaceEditor, {
870 DefaultOptions: {
871 ajaxOptions: { },
872 autoRows: 3, // Use when multi-line w/ rows == 1
873 cancelControl: 'link', // 'link'|'button'|false
874 cancelText: 'cancel',
875 clickToEditText: 'Click to edit',
876 externalControl: null, // id|elt
877 externalControlOnly: false,
878 fieldPostCreation: 'activate', // 'activate'|'focus'|false
879 formClassName: 'inplaceeditor-form',
880 formId: null, // id|elt
881 highlightColor: '#ffff99',
882 highlightEndColor: '#ffffff',
883 hoverClassName: '',
884 htmlResponse: true,
885 loadingClassName: 'inplaceeditor-loading',
886 loadingText: 'Loading...',
887 okControl: 'button', // 'link'|'button'|false
888 okText: 'ok',
889 paramName: 'value',
890 rows: 1, // If 1 and multi-line, uses autoRows
891 savingClassName: 'inplaceeditor-saving',
892 savingText: 'Saving...',
893 size: 0,
894 stripLoadedTextTags: false,
895 submitOnBlur: false,
896 textAfterControls: '',
897 textBeforeControls: '',
898 textBetweenControls: ''
899 },
900 DefaultCallbacks: {
901 callback: function(form) {
902 return Form.serialize(form);
903 },
904 onComplete: function(transport, element) {
905 // For backward compatibility, this one is bound to the IPE, and passes
906 // the element directly. It was too often customized, so we don't break it.
907 new Effect.Highlight(element, {
908 startcolor: this.options.highlightColor, keepBackgroundImage: true });
909 },
910 onEnterEditMode: null,
911 onEnterHover: function(ipe) {
912 ipe.element.style.backgroundColor = ipe.options.highlightColor;
913 if (ipe._effect)
914 ipe._effect.cancel();
915 },
916 onFailure: function(transport, ipe) {
917 alert('Error communication with the server: ' + transport.responseText.stripTags());
918 },
919 onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
920 onLeaveEditMode: null,
921 onLeaveHover: function(ipe) {
922 ipe._effect = new Effect.Highlight(ipe.element, {
923 startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
924 restorecolor: ipe._originalBackground, keepBackgroundImage: true
925 });
805 926 }
927 },
928 Listeners: {
929 click: 'enterEditMode',
930 keydown: 'checkForEscapeOrReturn',
931 mouseover: 'enterHover',
932 mouseout: 'leaveHover'
806 933 }
807 934 });
808 935
936 Ajax.InPlaceCollectionEditor.DefaultOptions = {
937 loadingCollectionText: 'Loading options...'
938 };
939
809 940 // Delayed observer, like Form.Element.Observer,
810 941 // but waits for delay after last key input
811 942 // Ideal for live-search fields
812 943
813 Form.Element.DelayedObserver = Class.create();
814 Form.Element.DelayedObserver.prototype = {
944 Form.Element.DelayedObserver = Class.create({
815 945 initialize: function(element, delay, callback) {
816 946 this.delay = delay || 0.5;
817 947 this.element = $(element);
@@ -830,4 +960,4 Form.Element.DelayedObserver.prototype = {
830 960 this.timer = null;
831 961 this.callback(this.element, $F(this.element));
832 962 }
833 };
963 });
@@ -1,10 +1,10
1 // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 // (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
1 // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 // (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
3 3 //
4 4 // script.aculo.us is freely distributable under the terms of an MIT-style license.
5 5 // For details, see the script.aculo.us web site: http://script.aculo.us/
6 6
7 if(typeof Effect == 'undefined')
7 if(Object.isUndefined(Effect))
8 8 throw("dragdrop.js requires including script.aculo.us' effects.js library");
9 9
10 10 var Droppables = {
@@ -20,14 +20,13 var Droppables = {
20 20 greedy: true,
21 21 hoverclass: null,
22 22 tree: false
23 }, arguments[1] || {});
23 }, arguments[1] || { });
24 24
25 25 // cache containers
26 26 if(options.containment) {
27 27 options._containers = [];
28 28 var containment = options.containment;
29 if((typeof containment == 'object') &&
30 (containment.constructor == Array)) {
29 if(Object.isArray(containment)) {
31 30 containment.each( function(c) { options._containers.push($(c)) });
32 31 } else {
33 32 options._containers.push($(containment));
@@ -87,21 +86,23 var Droppables = {
87 86
88 87 show: function(point, element) {
89 88 if(!this.drops.length) return;
90 var affected = [];
89 var drop, affected = [];
91 90
92 if(this.last_active) this.deactivate(this.last_active);
93 91 this.drops.each( function(drop) {
94 92 if(Droppables.isAffected(point, element, drop))
95 93 affected.push(drop);
96 94 });
97 95
98 if(affected.length>0) {
96 if(affected.length>0)
99 97 drop = Droppables.findDeepestChild(affected);
98
99 if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
100 if (drop) {
100 101 Position.within(drop.element, point[0], point[1]);
101 102 if(drop.onHover)
102 103 drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
103 104
104 Droppables.activate(drop);
105 if (drop != this.last_active) Droppables.activate(drop);
105 106 }
106 107 },
107 108
@@ -110,8 +111,10 var Droppables = {
110 111 Position.prepare();
111 112
112 113 if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
113 if (this.last_active.onDrop)
114 this.last_active.onDrop(element, this.last_active.element, event);
114 if (this.last_active.onDrop) {
115 this.last_active.onDrop(element, this.last_active.element, event);
116 return true;
117 }
115 118 },
116 119
117 120 reset: function() {
@@ -219,10 +222,7 var Draggables = {
219 222
220 223 /*--------------------------------------------------------------------------*/
221 224
222 var Draggable = Class.create();
223 Draggable._dragging = {};
224
225 Draggable.prototype = {
225 var Draggable = Class.create({
226 226 initialize: function(element) {
227 227 var defaults = {
228 228 handle: false,
@@ -233,7 +233,7 Draggable.prototype = {
233 233 });
234 234 },
235 235 endeffect: function(element) {
236 var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0;
236 var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
237 237 new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
238 238 queue: {scope:'_draggable', position:'end'},
239 239 afterFinish: function(){
@@ -243,6 +243,7 Draggable.prototype = {
243 243 },
244 244 zindex: 1000,
245 245 revert: false,
246 quiet: false,
246 247 scroll: false,
247 248 scrollSensitivity: 20,
248 249 scrollSpeed: 15,
@@ -250,7 +251,7 Draggable.prototype = {
250 251 delay: 0
251 252 };
252 253
253 if(!arguments[1] || typeof arguments[1].endeffect == 'undefined')
254 if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
254 255 Object.extend(defaults, {
255 256 starteffect: function(element) {
256 257 element._opacity = Element.getOpacity(element);
@@ -259,11 +260,11 Draggable.prototype = {
259 260 }
260 261 });
261 262
262 var options = Object.extend(defaults, arguments[1] || {});
263 var options = Object.extend(defaults, arguments[1] || { });
263 264
264 265 this.element = $(element);
265 266
266 if(options.handle && (typeof options.handle == 'string'))
267 if(options.handle && Object.isString(options.handle))
267 268 this.handle = this.element.down('.'+options.handle, 0);
268 269
269 270 if(!this.handle) this.handle = $(options.handle);
@@ -276,7 +277,6 Draggable.prototype = {
276 277
277 278 Element.makePositioned(this.element); // fix IE
278 279
279 this.delta = this.currentDelta();
280 280 this.options = options;
281 281 this.dragging = false;
282 282
@@ -298,17 +298,17 Draggable.prototype = {
298 298 },
299 299
300 300 initDrag: function(event) {
301 if(typeof Draggable._dragging[this.element] != 'undefined' &&
301 if(!Object.isUndefined(Draggable._dragging[this.element]) &&
302 302 Draggable._dragging[this.element]) return;
303 303 if(Event.isLeftClick(event)) {
304 304 // abort on form elements, fixes a Firefox issue
305 305 var src = Event.element(event);
306 if(src.tagName && (
307 src.tagName=='INPUT' ||
308 src.tagName=='SELECT' ||
309 src.tagName=='OPTION' ||
310 src.tagName=='BUTTON' ||
311 src.tagName=='TEXTAREA')) return;
306 if((tag_name = src.tagName.toUpperCase()) && (
307 tag_name=='INPUT' ||
308 tag_name=='SELECT' ||
309 tag_name=='OPTION' ||
310 tag_name=='BUTTON' ||
311 tag_name=='TEXTAREA')) return;
312 312
313 313 var pointer = [Event.pointerX(event), Event.pointerY(event)];
314 314 var pos = Position.cumulativeOffset(this.element);
@@ -321,6 +321,8 Draggable.prototype = {
321 321
322 322 startDrag: function(event) {
323 323 this.dragging = true;
324 if(!this.delta)
325 this.delta = this.currentDelta();
324 326
325 327 if(this.options.zindex) {
326 328 this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
@@ -329,7 +331,9 Draggable.prototype = {
329 331
330 332 if(this.options.ghosting) {
331 333 this._clone = this.element.cloneNode(true);
332 Position.absolutize(this.element);
334 this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
335 if (!this.element._originallyAbsolute)
336 Position.absolutize(this.element);
333 337 this.element.parentNode.insertBefore(this._clone, this.element);
334 338 }
335 339
@@ -351,8 +355,12 Draggable.prototype = {
351 355
352 356 updateDrag: function(event, pointer) {
353 357 if(!this.dragging) this.startDrag(event);
354 Position.prepare();
355 Droppables.show(pointer, this.element);
358
359 if(!this.options.quiet){
360 Position.prepare();
361 Droppables.show(pointer, this.element);
362 }
363
356 364 Draggables.notify('onDrag', this, event);
357 365
358 366 this.draw(pointer);
@@ -380,30 +388,44 Draggable.prototype = {
380 388 }
381 389
382 390 // fix AppleWebKit rendering
383 if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
391 if(Prototype.Browser.WebKit) window.scrollBy(0,0);
384 392
385 393 Event.stop(event);
386 394 },
387 395
388 396 finishDrag: function(event, success) {
389 397 this.dragging = false;
398
399 if(this.options.quiet){
400 Position.prepare();
401 var pointer = [Event.pointerX(event), Event.pointerY(event)];
402 Droppables.show(pointer, this.element);
403 }
390 404
391 405 if(this.options.ghosting) {
392 Position.relativize(this.element);
406 if (!this.element._originallyAbsolute)
407 Position.relativize(this.element);
408 delete this.element._originallyAbsolute;
393 409 Element.remove(this._clone);
394 410 this._clone = null;
395 411 }
396 412
397 if(success) Droppables.fire(event, this.element);
413 var dropped = false;
414 if(success) {
415 dropped = Droppables.fire(event, this.element);
416 if (!dropped) dropped = false;
417 }
418 if(dropped && this.options.onDropped) this.options.onDropped(this.element);
398 419 Draggables.notify('onEnd', this, event);
399 420
400 421 var revert = this.options.revert;
401 if(revert && typeof revert == 'function') revert = revert(this.element);
422 if(revert && Object.isFunction(revert)) revert = revert(this.element);
402 423
403 424 var d = this.currentDelta();
404 425 if(revert && this.options.reverteffect) {
405 this.options.reverteffect(this.element,
406 d[1]-this.delta[1], d[0]-this.delta[0]);
426 if (dropped == 0 || revert != 'failure')
427 this.options.reverteffect(this.element,
428 d[1]-this.delta[1], d[0]-this.delta[0]);
407 429 } else {
408 430 this.delta = d;
409 431 }
@@ -451,15 +473,15 Draggable.prototype = {
451 473 }.bind(this));
452 474
453 475 if(this.options.snap) {
454 if(typeof this.options.snap == 'function') {
476 if(Object.isFunction(this.options.snap)) {
455 477 p = this.options.snap(p[0],p[1],this);
456 478 } else {
457 if(this.options.snap instanceof Array) {
479 if(Object.isArray(this.options.snap)) {
458 480 p = p.map( function(v, i) {
459 return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
481 return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
460 482 } else {
461 483 p = p.map( function(v) {
462 return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
484 return (v/this.options.snap).round()*this.options.snap }.bind(this))
463 485 }
464 486 }}
465 487
@@ -543,12 +565,13 Draggable.prototype = {
543 565 }
544 566 return { top: T, left: L, width: W, height: H };
545 567 }
546 }
568 });
569
570 Draggable._dragging = { };
547 571
548 572 /*--------------------------------------------------------------------------*/
549 573
550 var SortableObserver = Class.create();
551 SortableObserver.prototype = {
574 var SortableObserver = Class.create({
552 575 initialize: function(element, observer) {
553 576 this.element = $(element);
554 577 this.observer = observer;
@@ -564,15 +587,15 SortableObserver.prototype = {
564 587 if(this.lastValue != Sortable.serialize(this.element))
565 588 this.observer(this.element)
566 589 }
567 }
590 });
568 591
569 592 var Sortable = {
570 593 SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
571 594
572 sortables: {},
595 sortables: { },
573 596
574 597 _findRootElement: function(element) {
575 while (element.tagName != "BODY") {
598 while (element.tagName.toUpperCase() != "BODY") {
576 599 if(element.id && Sortable.sortables[element.id]) return element;
577 600 element = element.parentNode;
578 601 }
@@ -612,13 +635,20 var Sortable = {
612 635 delay: 0,
613 636 hoverclass: null,
614 637 ghosting: false,
638 quiet: false,
615 639 scroll: false,
616 640 scrollSensitivity: 20,
617 641 scrollSpeed: 15,
618 642 format: this.SERIALIZE_RULE,
643
644 // these take arrays of elements or ids and can be
645 // used for better initialization performance
646 elements: false,
647 handles: false,
648
619 649 onChange: Prototype.emptyFunction,
620 650 onUpdate: Prototype.emptyFunction
621 }, arguments[1] || {});
651 }, arguments[1] || { });
622 652
623 653 // clear any old sortable with same element
624 654 this.destroy(element);
@@ -626,6 +656,7 var Sortable = {
626 656 // build options for the draggables
627 657 var options_for_draggable = {
628 658 revert: true,
659 quiet: options.quiet,
629 660 scroll: options.scroll,
630 661 scrollSpeed: options.scrollSpeed,
631 662 scrollSensitivity: options.scrollSensitivity,
@@ -679,10 +710,9 var Sortable = {
679 710 options.droppables.push(element);
680 711 }
681 712
682 (this.findElements(element, options) || []).each( function(e) {
683 // handles are per-draggable
684 var handle = options.handle ?
685 $(e).down('.'+options.handle,0) : e;
713 (options.elements || this.findElements(element, options) || []).each( function(e,i) {
714 var handle = options.handles ? $(options.handles[i]) :
715 (options.handle ? $(e).select('.' + options.handle)[0] : e);
686 716 options.draggables.push(
687 717 new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
688 718 Droppables.add(e, options_for_droppable);
@@ -842,7 +872,7 var Sortable = {
842 872 only: sortableOptions.only,
843 873 name: element.id,
844 874 format: sortableOptions.format
845 }, arguments[1] || {});
875 }, arguments[1] || { });
846 876
847 877 var root = {
848 878 id: null,
@@ -866,7 +896,7 var Sortable = {
866 896
867 897 sequence: function(element) {
868 898 element = $(element);
869 var options = Object.extend(this.options(element), arguments[1] || {});
899 var options = Object.extend(this.options(element), arguments[1] || { });
870 900
871 901 return $(this.findElements(element, options) || []).map( function(item) {
872 902 return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
@@ -875,9 +905,9 var Sortable = {
875 905
876 906 setSequence: function(element, new_sequence) {
877 907 element = $(element);
878 var options = Object.extend(this.options(element), arguments[2] || {});
908 var options = Object.extend(this.options(element), arguments[2] || { });
879 909
880 var nodeMap = {};
910 var nodeMap = { };
881 911 this.findElements(element, options).each( function(n) {
882 912 if (n.id.match(options.format))
883 913 nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
@@ -895,7 +925,7 var Sortable = {
895 925
896 926 serialize: function(element) {
897 927 element = $(element);
898 var options = Object.extend(Sortable.options(element), arguments[1] || {});
928 var options = Object.extend(Sortable.options(element), arguments[1] || { });
899 929 var name = encodeURIComponent(
900 930 (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
901 931
@@ -919,7 +949,7 Element.isParent = function(child, element) {
919 949 return Element.isParent(child.parentNode, element);
920 950 }
921 951
922 Element.findChildren = function(element, only, recursive, tagName) {
952 Element.findChildren = function(element, only, recursive, tagName) {
923 953 if(!element.hasChildNodes()) return null;
924 954 tagName = tagName.toUpperCase();
925 955 if(only) only = [only].flatten();
This diff has been collapsed as it changes many lines, (760 lines changed) Show them Hide them
@@ -1,4 +1,4
1 // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
1 // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 2 // Contributors:
3 3 // Justin Palmer (http://encytemedia.com/)
4 4 // Mark Pilgrim (http://diveintomark.org/)
@@ -11,17 +11,17
11 11 // returns self (or first argument) if not convertable
12 12 String.prototype.parseColor = function() {
13 13 var color = '#';
14 if(this.slice(0,4) == 'rgb(') {
14 if (this.slice(0,4) == 'rgb(') {
15 15 var cols = this.slice(4,this.length-1).split(',');
16 16 var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
17 17 } else {
18 if(this.slice(0,1) == '#') {
19 if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
20 if(this.length==7) color = this.toLowerCase();
18 if (this.slice(0,1) == '#') {
19 if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
20 if (this.length==7) color = this.toLowerCase();
21 21 }
22 22 }
23 return(color.length==7 ? color : (arguments[0] || this));
24 }
23 return (color.length==7 ? color : (arguments[0] || this));
24 };
25 25
26 26 /*--------------------------------------------------------------------------*/
27 27
@@ -30,7 +30,7 Element.collectTextNodes = function(element) {
30 30 return (node.nodeType==3 ? node.nodeValue :
31 31 (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
32 32 }).flatten().join('');
33 }
33 };
34 34
35 35 Element.collectTextNodesIgnoreClass = function(element, className) {
36 36 return $A($(element).childNodes).collect( function(node) {
@@ -38,47 +38,18 Element.collectTextNodesIgnoreClass = function(element, className) {
38 38 ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
39 39 Element.collectTextNodesIgnoreClass(node, className) : ''));
40 40 }).flatten().join('');
41 }
41 };
42 42
43 43 Element.setContentZoom = function(element, percent) {
44 44 element = $(element);
45 45 element.setStyle({fontSize: (percent/100) + 'em'});
46 if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
46 if (Prototype.Browser.WebKit) window.scrollBy(0,0);
47 47 return element;
48 }
49
50 Element.getOpacity = function(element){
51 element = $(element);
52 var opacity;
53 if (opacity = element.getStyle('opacity'))
54 return parseFloat(opacity);
55 if (opacity = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
56 if(opacity[1]) return parseFloat(opacity[1]) / 100;
57 return 1.0;
58 }
48 };
59 49
60 Element.setOpacity = function(element, value){
61 element= $(element);
62 if (value == 1){
63 element.setStyle({ opacity:
64 (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ?
65 0.999999 : 1.0 });
66 if(/MSIE/.test(navigator.userAgent) && !window.opera)
67 element.setStyle({filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});
68 } else {
69 if(value < 0.00001) value = 0;
70 element.setStyle({opacity: value});
71 if(/MSIE/.test(navigator.userAgent) && !window.opera)
72 element.setStyle(
73 { filter: element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') +
74 'alpha(opacity='+value*100+')' });
75 }
76 return element;
77 }
78
79 Element.getInlineOpacity = function(element){
50 Element.getInlineOpacity = function(element){
80 51 return $(element).style.opacity || '';
81 }
52 };
82 53
83 54 Element.forceRerendering = function(element) {
84 55 try {
@@ -91,31 +62,63 Element.forceRerendering = function(element) {
91 62
92 63 /*--------------------------------------------------------------------------*/
93 64
94 Array.prototype.call = function() {
95 var args = arguments;
96 this.each(function(f){ f.apply(this, args) });
97 }
98
99 /*--------------------------------------------------------------------------*/
100
101 65 var Effect = {
102 66 _elementDoesNotExistError: {
103 67 name: 'ElementDoesNotExistError',
104 68 message: 'The specified DOM element does not exist, but is required for this effect to operate'
105 69 },
70 Transitions: {
71 linear: Prototype.K,
72 sinoidal: function(pos) {
73 return (-Math.cos(pos*Math.PI)/2) + 0.5;
74 },
75 reverse: function(pos) {
76 return 1-pos;
77 },
78 flicker: function(pos) {
79 var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
80 return pos > 1 ? 1 : pos;
81 },
82 wobble: function(pos) {
83 return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
84 },
85 pulse: function(pos, pulses) {
86 pulses = pulses || 5;
87 return (
88 ((pos % (1/pulses)) * pulses).round() == 0 ?
89 ((pos * pulses * 2) - (pos * pulses * 2).floor()) :
90 1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
91 );
92 },
93 spring: function(pos) {
94 return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
95 },
96 none: function(pos) {
97 return 0;
98 },
99 full: function(pos) {
100 return 1;
101 }
102 },
103 DefaultOptions: {
104 duration: 1.0, // seconds
105 fps: 100, // 100= assume 66fps max.
106 sync: false, // true for combining
107 from: 0.0,
108 to: 1.0,
109 delay: 0.0,
110 queue: 'parallel'
111 },
106 112 tagifyText: function(element) {
107 if(typeof Builder == 'undefined')
108 throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
109
110 113 var tagifyStyle = 'position:relative';
111 if(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1';
114 if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
112 115
113 116 element = $(element);
114 117 $A(element.childNodes).each( function(child) {
115 if(child.nodeType==3) {
118 if (child.nodeType==3) {
116 119 child.nodeValue.toArray().each( function(character) {
117 120 element.insertBefore(
118 Builder.node('span',{style: tagifyStyle},
121 new Element('span', {style: tagifyStyle}).update(
119 122 character == ' ' ? String.fromCharCode(160) : character),
120 123 child);
121 124 });
@@ -125,8 +128,8 var Effect = {
125 128 },
126 129 multiple: function(element, effect) {
127 130 var elements;
128 if(((typeof element == 'object') ||
129 (typeof element == 'function')) &&
131 if (((typeof element == 'object') ||
132 Object.isFunction(element)) &&
130 133 (element.length))
131 134 elements = element;
132 135 else
@@ -135,7 +138,7 var Effect = {
135 138 var options = Object.extend({
136 139 speed: 0.1,
137 140 delay: 0.0
138 }, arguments[2] || {});
141 }, arguments[2] || { });
139 142 var masterDelay = options.delay;
140 143
141 144 $A(elements).each( function(element, index) {
@@ -152,53 +155,20 var Effect = {
152 155 effect = (effect || 'appear').toLowerCase();
153 156 var options = Object.extend({
154 157 queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
155 }, arguments[2] || {});
158 }, arguments[2] || { });
156 159 Effect[element.visible() ?
157 160 Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
158 161 }
159 162 };
160 163
161 var Effect2 = Effect; // deprecated
162
163 /* ------------- transitions ------------- */
164
165 Effect.Transitions = {
166 linear: Prototype.K,
167 sinoidal: function(pos) {
168 return (-Math.cos(pos*Math.PI)/2) + 0.5;
169 },
170 reverse: function(pos) {
171 return 1-pos;
172 },
173 flicker: function(pos) {
174 return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
175 },
176 wobble: function(pos) {
177 return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
178 },
179 pulse: function(pos, pulses) {
180 pulses = pulses || 5;
181 return (
182 Math.round((pos % (1/pulses)) * pulses) == 0 ?
183 ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) :
184 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2))
185 );
186 },
187 none: function(pos) {
188 return 0;
189 },
190 full: function(pos) {
191 return 1;
192 }
193 };
164 Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
194 165
195 166 /* ------------- core effects ------------- */
196 167
197 Effect.ScopedQueue = Class.create();
198 Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
168 Effect.ScopedQueue = Class.create(Enumerable, {
199 169 initialize: function() {
200 170 this.effects = [];
201 this.interval = null;
171 this.interval = null;
202 172 },
203 173 _each: function(iterator) {
204 174 this.effects._each(iterator);
@@ -206,7 +176,7 Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
206 176 add: function(effect) {
207 177 var timestamp = new Date().getTime();
208 178
209 var position = (typeof effect.options.queue == 'string') ?
179 var position = Object.isString(effect.options.queue) ?
210 180 effect.options.queue : effect.options.queue.position;
211 181
212 182 switch(position) {
@@ -229,115 +199,111 Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
229 199 effect.startOn += timestamp;
230 200 effect.finishOn += timestamp;
231 201
232 if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
202 if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
233 203 this.effects.push(effect);
234 204
235 if(!this.interval)
236 this.interval = setInterval(this.loop.bind(this), 40);
205 if (!this.interval)
206 this.interval = setInterval(this.loop.bind(this), 15);
237 207 },
238 208 remove: function(effect) {
239 209 this.effects = this.effects.reject(function(e) { return e==effect });
240 if(this.effects.length == 0) {
210 if (this.effects.length == 0) {
241 211 clearInterval(this.interval);
242 212 this.interval = null;
243 213 }
244 214 },
245 215 loop: function() {
246 216 var timePos = new Date().getTime();
247 this.effects.invoke('loop', timePos);
217 for(var i=0, len=this.effects.length;i<len;i++)
218 this.effects[i] && this.effects[i].loop(timePos);
248 219 }
249 220 });
250 221
251 222 Effect.Queues = {
252 223 instances: $H(),
253 224 get: function(queueName) {
254 if(typeof queueName != 'string') return queueName;
225 if (!Object.isString(queueName)) return queueName;
255 226
256 if(!this.instances[queueName])
257 this.instances[queueName] = new Effect.ScopedQueue();
258
259 return this.instances[queueName];
227 return this.instances.get(queueName) ||
228 this.instances.set(queueName, new Effect.ScopedQueue());
260 229 }
261 }
230 };
262 231 Effect.Queue = Effect.Queues.get('global');
263 232
264 Effect.DefaultOptions = {
265 transition: Effect.Transitions.sinoidal,
266 duration: 1.0, // seconds
267 fps: 25.0, // max. 25fps due to Effect.Queue implementation
268 sync: false, // true for combining
269 from: 0.0,
270 to: 1.0,
271 delay: 0.0,
272 queue: 'parallel'
273 }
274
275 Effect.Base = function() {};
276 Effect.Base.prototype = {
233 Effect.Base = Class.create({
277 234 position: null,
278 235 start: function(options) {
279 this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
236 function codeForEvent(options,eventName){
237 return (
238 (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
239 (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
240 );
241 }
242 if (options && options.transition === false) options.transition = Effect.Transitions.linear;
243 this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
280 244 this.currentFrame = 0;
281 245 this.state = 'idle';
282 246 this.startOn = this.options.delay*1000;
283 this.finishOn = this.startOn + (this.options.duration*1000);
247 this.finishOn = this.startOn+(this.options.duration*1000);
248 this.fromToDelta = this.options.to-this.options.from;
249 this.totalTime = this.finishOn-this.startOn;
250 this.totalFrames = this.options.fps*this.options.duration;
251
252 eval('this.render = function(pos){ '+
253 'if (this.state=="idle"){this.state="running";'+
254 codeForEvent(this.options,'beforeSetup')+
255 (this.setup ? 'this.setup();':'')+
256 codeForEvent(this.options,'afterSetup')+
257 '};if (this.state=="running"){'+
258 'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
259 'this.position=pos;'+
260 codeForEvent(this.options,'beforeUpdate')+
261 (this.update ? 'this.update(pos);':'')+
262 codeForEvent(this.options,'afterUpdate')+
263 '}}');
264
284 265 this.event('beforeStart');
285 if(!this.options.sync)
286 Effect.Queues.get(typeof this.options.queue == 'string' ?
266 if (!this.options.sync)
267 Effect.Queues.get(Object.isString(this.options.queue) ?
287 268 'global' : this.options.queue.scope).add(this);
288 269 },
289 270 loop: function(timePos) {
290 if(timePos >= this.startOn) {
291 if(timePos >= this.finishOn) {
271 if (timePos >= this.startOn) {
272 if (timePos >= this.finishOn) {
292 273 this.render(1.0);
293 274 this.cancel();
294 275 this.event('beforeFinish');
295 if(this.finish) this.finish();
276 if (this.finish) this.finish();
296 277 this.event('afterFinish');
297 278 return;
298 279 }
299 var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
300 var frame = Math.round(pos * this.options.fps * this.options.duration);
301 if(frame > this.currentFrame) {
280 var pos = (timePos - this.startOn) / this.totalTime,
281 frame = (pos * this.totalFrames).round();
282 if (frame > this.currentFrame) {
302 283 this.render(pos);
303 284 this.currentFrame = frame;
304 285 }
305 286 }
306 287 },
307 render: function(pos) {
308 if(this.state == 'idle') {
309 this.state = 'running';
310 this.event('beforeSetup');
311 if(this.setup) this.setup();
312 this.event('afterSetup');
313 }
314 if(this.state == 'running') {
315 if(this.options.transition) pos = this.options.transition(pos);
316 pos *= (this.options.to-this.options.from);
317 pos += this.options.from;
318 this.position = pos;
319 this.event('beforeUpdate');
320 if(this.update) this.update(pos);
321 this.event('afterUpdate');
322 }
323 },
324 288 cancel: function() {
325 if(!this.options.sync)
326 Effect.Queues.get(typeof this.options.queue == 'string' ?
289 if (!this.options.sync)
290 Effect.Queues.get(Object.isString(this.options.queue) ?
327 291 'global' : this.options.queue.scope).remove(this);
328 292 this.state = 'finished';
329 293 },
330 294 event: function(eventName) {
331 if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
332 if(this.options[eventName]) this.options[eventName](this);
295 if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
296 if (this.options[eventName]) this.options[eventName](this);
333 297 },
334 298 inspect: function() {
335 return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
299 var data = $H();
300 for(property in this)
301 if (!Object.isFunction(this[property])) data.set(property, this[property]);
302 return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
336 303 }
337 }
304 });
338 305
339 Effect.Parallel = Class.create();
340 Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
306 Effect.Parallel = Class.create(Effect.Base, {
341 307 initialize: function(effects) {
342 308 this.effects = effects || [];
343 309 this.start(arguments[1]);
@@ -350,35 +316,45 Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
350 316 effect.render(1.0);
351 317 effect.cancel();
352 318 effect.event('beforeFinish');
353 if(effect.finish) effect.finish(position);
319 if (effect.finish) effect.finish(position);
354 320 effect.event('afterFinish');
355 321 });
356 322 }
357 323 });
358 324
359 Effect.Event = Class.create();
360 Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), {
325 Effect.Tween = Class.create(Effect.Base, {
326 initialize: function(object, from, to) {
327 object = Object.isString(object) ? $(object) : object;
328 var args = $A(arguments), method = args.last(),
329 options = args.length == 5 ? args[3] : null;
330 this.method = Object.isFunction(method) ? method.bind(object) :
331 Object.isFunction(object[method]) ? object[method].bind(object) :
332 function(value) { object[method] = value };
333 this.start(Object.extend({ from: from, to: to }, options || { }));
334 },
335 update: function(position) {
336 this.method(position);
337 }
338 });
339
340 Effect.Event = Class.create(Effect.Base, {
361 341 initialize: function() {
362 var options = Object.extend({
363 duration: 0
364 }, arguments[0] || {});
365 this.start(options);
342 this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
366 343 },
367 344 update: Prototype.emptyFunction
368 345 });
369 346
370 Effect.Opacity = Class.create();
371 Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
347 Effect.Opacity = Class.create(Effect.Base, {
372 348 initialize: function(element) {
373 349 this.element = $(element);
374 if(!this.element) throw(Effect._elementDoesNotExistError);
350 if (!this.element) throw(Effect._elementDoesNotExistError);
375 351 // make this work on IE on elements without 'layout'
376 if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout))
352 if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
377 353 this.element.setStyle({zoom: 1});
378 354 var options = Object.extend({
379 355 from: this.element.getOpacity() || 0.0,
380 356 to: 1.0
381 }, arguments[1] || {});
357 }, arguments[1] || { });
382 358 this.start(options);
383 359 },
384 360 update: function(position) {
@@ -386,36 +362,30 Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
386 362 }
387 363 });
388 364
389 Effect.Move = Class.create();
390 Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
365 Effect.Move = Class.create(Effect.Base, {
391 366 initialize: function(element) {
392 367 this.element = $(element);
393 if(!this.element) throw(Effect._elementDoesNotExistError);
368 if (!this.element) throw(Effect._elementDoesNotExistError);
394 369 var options = Object.extend({
395 370 x: 0,
396 371 y: 0,
397 372 mode: 'relative'
398 }, arguments[1] || {});
373 }, arguments[1] || { });
399 374 this.start(options);
400 375 },
401 376 setup: function() {
402 // Bug in Opera: Opera returns the "real" position of a static element or
403 // relative element that does not have top/left explicitly set.
404 // ==> Always set top and left for position relative elements in your stylesheets
405 // (to 0 if you do not need them)
406 377 this.element.makePositioned();
407 378 this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
408 379 this.originalTop = parseFloat(this.element.getStyle('top') || '0');
409 if(this.options.mode == 'absolute') {
410 // absolute movement, so we need to calc deltaX and deltaY
380 if (this.options.mode == 'absolute') {
411 381 this.options.x = this.options.x - this.originalLeft;
412 382 this.options.y = this.options.y - this.originalTop;
413 383 }
414 384 },
415 385 update: function(position) {
416 386 this.element.setStyle({
417 left: Math.round(this.options.x * position + this.originalLeft) + 'px',
418 top: Math.round(this.options.y * position + this.originalTop) + 'px'
387 left: (this.options.x * position + this.originalLeft).round() + 'px',
388 top: (this.options.y * position + this.originalTop).round() + 'px'
419 389 });
420 390 }
421 391 });
@@ -423,30 +393,29 Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
423 393 // for backwards compatibility
424 394 Effect.MoveBy = function(element, toTop, toLeft) {
425 395 return new Effect.Move(element,
426 Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
396 Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
427 397 };
428 398
429 Effect.Scale = Class.create();
430 Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
399 Effect.Scale = Class.create(Effect.Base, {
431 400 initialize: function(element, percent) {
432 401 this.element = $(element);
433 if(!this.element) throw(Effect._elementDoesNotExistError);
402 if (!this.element) throw(Effect._elementDoesNotExistError);
434 403 var options = Object.extend({
435 404 scaleX: true,
436 405 scaleY: true,
437 406 scaleContent: true,
438 407 scaleFromCenter: false,
439 scaleMode: 'box', // 'box' or 'contents' or {} with provided values
408 scaleMode: 'box', // 'box' or 'contents' or { } with provided values
440 409 scaleFrom: 100.0,
441 410 scaleTo: percent
442 }, arguments[2] || {});
411 }, arguments[2] || { });
443 412 this.start(options);
444 413 },
445 414 setup: function() {
446 415 this.restoreAfterFinish = this.options.restoreAfterFinish || false;
447 416 this.elementPositioning = this.element.getStyle('position');
448 417
449 this.originalStyle = {};
418 this.originalStyle = { };
450 419 ['top','left','width','height','fontSize'].each( function(k) {
451 420 this.originalStyle[k] = this.element.style[k];
452 421 }.bind(this));
@@ -456,7 +425,7 Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
456 425
457 426 var fontSize = this.element.getStyle('font-size') || '100%';
458 427 ['em','px','%','pt'].each( function(fontSizeType) {
459 if(fontSize.indexOf(fontSizeType)>0) {
428 if (fontSize.indexOf(fontSizeType)>0) {
460 429 this.fontSize = parseFloat(fontSize);
461 430 this.fontSizeType = fontSizeType;
462 431 }
@@ -465,60 +434,61 Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
465 434 this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
466 435
467 436 this.dims = null;
468 if(this.options.scaleMode=='box')
437 if (this.options.scaleMode=='box')
469 438 this.dims = [this.element.offsetHeight, this.element.offsetWidth];
470 if(/^content/.test(this.options.scaleMode))
439 if (/^content/.test(this.options.scaleMode))
471 440 this.dims = [this.element.scrollHeight, this.element.scrollWidth];
472 if(!this.dims)
441 if (!this.dims)
473 442 this.dims = [this.options.scaleMode.originalHeight,
474 443 this.options.scaleMode.originalWidth];
475 444 },
476 445 update: function(position) {
477 446 var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
478 if(this.options.scaleContent && this.fontSize)
447 if (this.options.scaleContent && this.fontSize)
479 448 this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
480 449 this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
481 450 },
482 451 finish: function(position) {
483 if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
452 if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
484 453 },
485 454 setDimensions: function(height, width) {
486 var d = {};
487 if(this.options.scaleX) d.width = Math.round(width) + 'px';
488 if(this.options.scaleY) d.height = Math.round(height) + 'px';
489 if(this.options.scaleFromCenter) {
455 var d = { };
456 if (this.options.scaleX) d.width = width.round() + 'px';
457 if (this.options.scaleY) d.height = height.round() + 'px';
458 if (this.options.scaleFromCenter) {
490 459 var topd = (height - this.dims[0])/2;
491 460 var leftd = (width - this.dims[1])/2;
492 if(this.elementPositioning == 'absolute') {
493 if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
494 if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
461 if (this.elementPositioning == 'absolute') {
462 if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
463 if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
495 464 } else {
496 if(this.options.scaleY) d.top = -topd + 'px';
497 if(this.options.scaleX) d.left = -leftd + 'px';
465 if (this.options.scaleY) d.top = -topd + 'px';
466 if (this.options.scaleX) d.left = -leftd + 'px';
498 467 }
499 468 }
500 469 this.element.setStyle(d);
501 470 }
502 471 });
503 472
504 Effect.Highlight = Class.create();
505 Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
473 Effect.Highlight = Class.create(Effect.Base, {
506 474 initialize: function(element) {
507 475 this.element = $(element);
508 if(!this.element) throw(Effect._elementDoesNotExistError);
509 var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
476 if (!this.element) throw(Effect._elementDoesNotExistError);
477 var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
510 478 this.start(options);
511 479 },
512 480 setup: function() {
513 481 // Prevent executing on elements not in the layout flow
514 if(this.element.getStyle('display')=='none') { this.cancel(); return; }
482 if (this.element.getStyle('display')=='none') { this.cancel(); return; }
515 483 // Disable background image during the effect
516 this.oldStyle = {
517 backgroundImage: this.element.getStyle('background-image') };
518 this.element.setStyle({backgroundImage: 'none'});
519 if(!this.options.endcolor)
484 this.oldStyle = { };
485 if (!this.options.keepBackgroundImage) {
486 this.oldStyle.backgroundImage = this.element.getStyle('background-image');
487 this.element.setStyle({backgroundImage: 'none'});
488 }
489 if (!this.options.endcolor)
520 490 this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
521 if(!this.options.restorecolor)
491 if (!this.options.restorecolor)
522 492 this.options.restorecolor = this.element.getStyle('background-color');
523 493 // init color calculations
524 494 this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
@@ -526,7 +496,7 Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype),
526 496 },
527 497 update: function(position) {
528 498 this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
529 return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
499 return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
530 500 },
531 501 finish: function() {
532 502 this.element.setStyle(Object.extend(this.oldStyle, {
@@ -535,30 +505,21 Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype),
535 505 }
536 506 });
537 507
538 Effect.ScrollTo = Class.create();
539 Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
540 initialize: function(element) {
541 this.element = $(element);
542 this.start(arguments[1] || {});
543 },
544 setup: function() {
545 Position.prepare();
546 var offsets = Position.cumulativeOffset(this.element);
547 if(this.options.offset) offsets[1] += this.options.offset;
548 var max = window.innerHeight ?
549 window.height - window.innerHeight :
550 document.body.scrollHeight -
551 (document.documentElement.clientHeight ?
552 document.documentElement.clientHeight : document.body.clientHeight);
553 this.scrollStart = Position.deltaY;
554 this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
555 },
556 update: function(position) {
557 Position.prepare();
558 window.scrollTo(Position.deltaX,
559 this.scrollStart + (position*this.delta));
560 }
561 });
508 Effect.ScrollTo = function(element) {
509 var options = arguments[1] || { },
510 scrollOffsets = document.viewport.getScrollOffsets(),
511 elementOffsets = $(element).cumulativeOffset(),
512 max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();
513
514 if (options.offset) elementOffsets[1] += options.offset;
515
516 return new Effect.Tween(null,
517 scrollOffsets.top,
518 elementOffsets[1] > max ? max : elementOffsets[1],
519 options,
520 function(p){ scrollTo(scrollOffsets.left, p.round()) }
521 );
522 };
562 523
563 524 /* ------------- combination effects ------------- */
564 525
@@ -566,14 +527,15 Effect.Fade = function(element) {
566 527 element = $(element);
567 528 var oldOpacity = element.getInlineOpacity();
568 529 var options = Object.extend({
569 from: element.getOpacity() || 1.0,
570 to: 0.0,
571 afterFinishInternal: function(effect) {
572 if(effect.options.to!=0) return;
573 effect.element.hide().setStyle({opacity: oldOpacity});
574 }}, arguments[1] || {});
530 from: element.getOpacity() || 1.0,
531 to: 0.0,
532 afterFinishInternal: function(effect) {
533 if (effect.options.to!=0) return;
534 effect.element.hide().setStyle({opacity: oldOpacity});
535 }
536 }, arguments[1] || { });
575 537 return new Effect.Opacity(element,options);
576 }
538 };
577 539
578 540 Effect.Appear = function(element) {
579 541 element = $(element);
@@ -586,9 +548,9 Effect.Appear = function(element) {
586 548 },
587 549 beforeSetup: function(effect) {
588 550 effect.element.setOpacity(effect.options.from).show();
589 }}, arguments[1] || {});
551 }}, arguments[1] || { });
590 552 return new Effect.Opacity(element,options);
591 }
553 };
592 554
593 555 Effect.Puff = function(element) {
594 556 element = $(element);
@@ -610,9 +572,9 Effect.Puff = function(element) {
610 572 },
611 573 afterFinishInternal: function(effect) {
612 574 effect.effects[0].element.hide().setStyle(oldStyle); }
613 }, arguments[1] || {})
575 }, arguments[1] || { })
614 576 );
615 }
577 };
616 578
617 579 Effect.BlindUp = function(element) {
618 580 element = $(element);
@@ -624,9 +586,9 Effect.BlindUp = function(element) {
624 586 afterFinishInternal: function(effect) {
625 587 effect.element.hide().undoClipping();
626 588 }
627 }, arguments[1] || {})
589 }, arguments[1] || { })
628 590 );
629 }
591 };
630 592
631 593 Effect.BlindDown = function(element) {
632 594 element = $(element);
@@ -643,8 +605,8 Effect.BlindDown = function(element) {
643 605 afterFinishInternal: function(effect) {
644 606 effect.element.undoClipping();
645 607 }
646 }, arguments[1] || {}));
647 }
608 }, arguments[1] || { }));
609 };
648 610
649 611 Effect.SwitchOff = function(element) {
650 612 element = $(element);
@@ -665,8 +627,8 Effect.SwitchOff = function(element) {
665 627 }
666 628 })
667 629 }
668 }, arguments[1] || {}));
669 }
630 }, arguments[1] || { }));
631 };
670 632
671 633 Effect.DropOut = function(element) {
672 634 element = $(element);
@@ -685,29 +647,35 Effect.DropOut = function(element) {
685 647 afterFinishInternal: function(effect) {
686 648 effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
687 649 }
688 }, arguments[1] || {}));
689 }
650 }, arguments[1] || { }));
651 };
690 652
691 653 Effect.Shake = function(element) {
692 654 element = $(element);
655 var options = Object.extend({
656 distance: 20,
657 duration: 0.5
658 }, arguments[1] || {});
659 var distance = parseFloat(options.distance);
660 var split = parseFloat(options.duration) / 10.0;
693 661 var oldStyle = {
694 662 top: element.getStyle('top'),
695 663 left: element.getStyle('left') };
696 return new Effect.Move(element,
697 { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
664 return new Effect.Move(element,
665 { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) {
698 666 new Effect.Move(effect.element,
699 { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
667 { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
700 668 new Effect.Move(effect.element,
701 { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
669 { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
702 670 new Effect.Move(effect.element,
703 { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
671 { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
704 672 new Effect.Move(effect.element,
705 { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
673 { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
706 674 new Effect.Move(effect.element,
707 { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
675 { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
708 676 effect.element.undoPositioned().setStyle(oldStyle);
709 677 }}) }}) }}) }}) }}) }});
710 }
678 };
711 679
712 680 Effect.SlideDown = function(element) {
713 681 element = $(element).cleanWhitespace();
@@ -723,7 +691,7 Effect.SlideDown = function(element) {
723 691 afterSetup: function(effect) {
724 692 effect.element.makePositioned();
725 693 effect.element.down().makePositioned();
726 if(window.opera) effect.element.setStyle({top: ''});
694 if (window.opera) effect.element.setStyle({top: ''});
727 695 effect.element.makeClipping().setStyle({height: '0px'}).show();
728 696 },
729 697 afterUpdateInternal: function(effect) {
@@ -733,23 +701,25 Effect.SlideDown = function(element) {
733 701 afterFinishInternal: function(effect) {
734 702 effect.element.undoClipping().undoPositioned();
735 703 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
736 }, arguments[1] || {})
704 }, arguments[1] || { })
737 705 );
738 }
706 };
739 707
740 708 Effect.SlideUp = function(element) {
741 709 element = $(element).cleanWhitespace();
742 710 var oldInnerBottom = element.down().getStyle('bottom');
711 var elementDimensions = element.getDimensions();
743 712 return new Effect.Scale(element, window.opera ? 0 : 1,
744 713 Object.extend({ scaleContent: false,
745 714 scaleX: false,
746 715 scaleMode: 'box',
747 716 scaleFrom: 100,
717 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
748 718 restoreAfterFinish: true,
749 beforeStartInternal: function(effect) {
719 afterSetup: function(effect) {
750 720 effect.element.makePositioned();
751 721 effect.element.down().makePositioned();
752 if(window.opera) effect.element.setStyle({top: ''});
722 if (window.opera) effect.element.setStyle({top: ''});
753 723 effect.element.makeClipping().show();
754 724 },
755 725 afterUpdateInternal: function(effect) {
@@ -757,12 +727,12 Effect.SlideUp = function(element) {
757 727 (effect.dims[0] - effect.element.clientHeight) + 'px' });
758 728 },
759 729 afterFinishInternal: function(effect) {
760 effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom});
761 effect.element.down().undoPositioned();
730 effect.element.hide().undoClipping().undoPositioned();
731 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
762 732 }
763 }, arguments[1] || {})
733 }, arguments[1] || { })
764 734 );
765 }
735 };
766 736
767 737 // Bug in opera makes the TD containing this element expand for a instance after finish
768 738 Effect.Squish = function(element) {
@@ -775,7 +745,7 Effect.Squish = function(element) {
775 745 effect.element.hide().undoClipping();
776 746 }
777 747 });
778 }
748 };
779 749
780 750 Effect.Grow = function(element) {
781 751 element = $(element);
@@ -784,7 +754,7 Effect.Grow = function(element) {
784 754 moveTransition: Effect.Transitions.sinoidal,
785 755 scaleTransition: Effect.Transitions.sinoidal,
786 756 opacityTransition: Effect.Transitions.full
787 }, arguments[1] || {});
757 }, arguments[1] || { });
788 758 var oldStyle = {
789 759 top: element.style.top,
790 760 left: element.style.left,
@@ -849,7 +819,7 Effect.Grow = function(element) {
849 819 )
850 820 }
851 821 });
852 }
822 };
853 823
854 824 Effect.Shrink = function(element) {
855 825 element = $(element);
@@ -858,7 +828,7 Effect.Shrink = function(element) {
858 828 moveTransition: Effect.Transitions.sinoidal,
859 829 scaleTransition: Effect.Transitions.sinoidal,
860 830 opacityTransition: Effect.Transitions.none
861 }, arguments[1] || {});
831 }, arguments[1] || { });
862 832 var oldStyle = {
863 833 top: element.style.top,
864 834 left: element.style.left,
@@ -903,11 +873,11 Effect.Shrink = function(element) {
903 873 effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
904 874 }, options)
905 875 );
906 }
876 };
907 877
908 878 Effect.Pulsate = function(element) {
909 879 element = $(element);
910 var options = arguments[1] || {};
880 var options = arguments[1] || { };
911 881 var oldOpacity = element.getInlineOpacity();
912 882 var transition = options.transition || Effect.Transitions.sinoidal;
913 883 var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
@@ -916,7 +886,7 Effect.Pulsate = function(element) {
916 886 Object.extend(Object.extend({ duration: 2.0, from: 0,
917 887 afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
918 888 }, options), {transition: reverser}));
919 }
889 };
920 890
921 891 Effect.Fold = function(element) {
922 892 element = $(element);
@@ -936,37 +906,71 Effect.Fold = function(element) {
936 906 afterFinishInternal: function(effect) {
937 907 effect.element.hide().undoClipping().setStyle(oldStyle);
938 908 } });
939 }}, arguments[1] || {}));
909 }}, arguments[1] || { }));
940 910 };
941 911
942 Effect.Morph = Class.create();
943 Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
912 Effect.Morph = Class.create(Effect.Base, {
944 913 initialize: function(element) {
945 914 this.element = $(element);
946 if(!this.element) throw(Effect._elementDoesNotExistError);
915 if (!this.element) throw(Effect._elementDoesNotExistError);
947 916 var options = Object.extend({
948 style: ''
949 }, arguments[1] || {});
917 style: { }
918 }, arguments[1] || { });
919
920 if (!Object.isString(options.style)) this.style = $H(options.style);
921 else {
922 if (options.style.include(':'))
923 this.style = options.style.parseStyle();
924 else {
925 this.element.addClassName(options.style);
926 this.style = $H(this.element.getStyles());
927 this.element.removeClassName(options.style);
928 var css = this.element.getStyles();
929 this.style = this.style.reject(function(style) {
930 return style.value == css[style.key];
931 });
932 options.afterFinishInternal = function(effect) {
933 effect.element.addClassName(effect.options.style);
934 effect.transforms.each(function(transform) {
935 effect.element.style[transform.style] = '';
936 });
937 }
938 }
939 }
950 940 this.start(options);
951 941 },
942
952 943 setup: function(){
953 944 function parseColor(color){
954 if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
945 if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
955 946 color = color.parseColor();
956 947 return $R(0,2).map(function(i){
957 948 return parseInt( color.slice(i*2+1,i*2+3), 16 )
958 949 });
959 950 }
960 this.transforms = this.options.style.parseStyle().map(function(property){
961 var originalValue = this.element.getStyle(property[0]);
962 return $H({
963 style: property[0],
964 originalValue: property[1].unit=='color' ?
965 parseColor(originalValue) : parseFloat(originalValue || 0),
966 targetValue: property[1].unit=='color' ?
967 parseColor(property[1].value) : property[1].value,
968 unit: property[1].unit
969 });
951 this.transforms = this.style.map(function(pair){
952 var property = pair[0], value = pair[1], unit = null;
953
954 if (value.parseColor('#zzzzzz') != '#zzzzzz') {
955 value = value.parseColor();
956 unit = 'color';
957 } else if (property == 'opacity') {
958 value = parseFloat(value);
959 if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
960 this.element.setStyle({zoom: 1});
961 } else if (Element.CSS_LENGTH.test(value)) {
962 var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
963 value = parseFloat(components[1]);
964 unit = (components.length == 3) ? components[2] : null;
965 }
966
967 var originalValue = this.element.getStyle(property);
968 return {
969 style: property.camelize(),
970 originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
971 targetValue: unit=='color' ? parseColor(value) : value,
972 unit: unit
973 };
970 974 }.bind(this)).reject(function(transform){
971 975 return (
972 976 (transform.originalValue == transform.targetValue) ||
@@ -978,32 +982,35 Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
978 982 });
979 983 },
980 984 update: function(position) {
981 var style = $H(), value = null;
982 this.transforms.each(function(transform){
983 value = transform.unit=='color' ?
984 $R(0,2).inject('#',function(m,v,i){
985 return m+(Math.round(transform.originalValue[i]+
986 (transform.targetValue[i] - transform.originalValue[i])*position)).toColorPart() }) :
987 transform.originalValue + Math.round(
988 ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
989 style[transform.style] = value;
990 });
991 this.element.setStyle(style);
985 var style = { }, transform, i = this.transforms.length;
986 while(i--)
987 style[(transform = this.transforms[i]).style] =
988 transform.unit=='color' ? '#'+
989 (Math.round(transform.originalValue[0]+
990 (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
991 (Math.round(transform.originalValue[1]+
992 (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
993 (Math.round(transform.originalValue[2]+
994 (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
995 (transform.originalValue +
996 (transform.targetValue - transform.originalValue) * position).toFixed(3) +
997 (transform.unit === null ? '' : transform.unit);
998 this.element.setStyle(style, true);
992 999 }
993 1000 });
994 1001
995 Effect.Transform = Class.create();
996 Object.extend(Effect.Transform.prototype, {
1002 Effect.Transform = Class.create({
997 1003 initialize: function(tracks){
998 1004 this.tracks = [];
999 this.options = arguments[1] || {};
1005 this.options = arguments[1] || { };
1000 1006 this.addTracks(tracks);
1001 1007 },
1002 1008 addTracks: function(tracks){
1003 1009 tracks.each(function(track){
1004 var data = $H(track).values().first();
1010 track = $H(track);
1011 var data = track.values().first();
1005 1012 this.tracks.push($H({
1006 ids: $H(track).keys().first(),
1013 ids: track.keys().first(),
1007 1014 effect: Effect.Morph,
1008 1015 options: { style: data }
1009 1016 }));
@@ -1013,76 +1020,101 Object.extend(Effect.Transform.prototype, {
1013 1020 play: function(){
1014 1021 return new Effect.Parallel(
1015 1022 this.tracks.map(function(track){
1016 var elements = [$(track.ids) || $$(track.ids)].flatten();
1017 return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
1023 var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
1024 var elements = [$(ids) || $$(ids)].flatten();
1025 return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
1018 1026 }).flatten(),
1019 1027 this.options
1020 1028 );
1021 1029 }
1022 1030 });
1023 1031
1024 Element.CSS_PROPERTIES = ['azimuth', 'backgroundAttachment', 'backgroundColor', 'backgroundImage',
1025 'backgroundPosition', 'backgroundRepeat', 'borderBottomColor', 'borderBottomStyle',
1026 'borderBottomWidth', 'borderCollapse', 'borderLeftColor', 'borderLeftStyle', 'borderLeftWidth',
1027 'borderRightColor', 'borderRightStyle', 'borderRightWidth', 'borderSpacing', 'borderTopColor',
1028 'borderTopStyle', 'borderTopWidth', 'bottom', 'captionSide', 'clear', 'clip', 'color', 'content',
1029 'counterIncrement', 'counterReset', 'cssFloat', 'cueAfter', 'cueBefore', 'cursor', 'direction',
1030 'display', 'elevation', 'emptyCells', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch',
1031 'fontStyle', 'fontVariant', 'fontWeight', 'height', 'left', 'letterSpacing', 'lineHeight',
1032 'listStyleImage', 'listStylePosition', 'listStyleType', 'marginBottom', 'marginLeft', 'marginRight',
1033 'marginTop', 'markerOffset', 'marks', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'opacity',
1034 'orphans', 'outlineColor', 'outlineOffset', 'outlineStyle', 'outlineWidth', 'overflowX', 'overflowY',
1035 'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop', 'page', 'pageBreakAfter', 'pageBreakBefore',
1036 'pageBreakInside', 'pauseAfter', 'pauseBefore', 'pitch', 'pitchRange', 'position', 'quotes',
1037 'richness', 'right', 'size', 'speakHeader', 'speakNumeral', 'speakPunctuation', 'speechRate', 'stress',
1038 'tableLayout', 'textAlign', 'textDecoration', 'textIndent', 'textShadow', 'textTransform', 'top',
1039 'unicodeBidi', 'verticalAlign', 'visibility', 'voiceFamily', 'volume', 'whiteSpace', 'widows',
1040 'width', 'wordSpacing', 'zIndex'];
1032 Element.CSS_PROPERTIES = $w(
1033 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
1034 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
1035 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
1036 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
1037 'fontSize fontWeight height left letterSpacing lineHeight ' +
1038 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
1039 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
1040 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
1041 'right textIndent top width wordSpacing zIndex');
1041 1042
1042 1043 Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1043 1044
1045 String.__parseStyleElement = document.createElement('div');
1044 1046 String.prototype.parseStyle = function(){
1045 var element = Element.extend(document.createElement('div'));
1046 element.innerHTML = '<div style="' + this + '"></div>';
1047 var style = element.down().style, styleRules = $H();
1047 var style, styleRules = $H();
1048 if (Prototype.Browser.WebKit)
1049 style = new Element('div',{style:this}).style;
1050 else {
1051 String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
1052 style = String.__parseStyleElement.childNodes[0].style;
1053 }
1048 1054
1049 1055 Element.CSS_PROPERTIES.each(function(property){
1050 if(style[property]) styleRules[property] = style[property];
1056 if (style[property]) styleRules.set(property, style[property]);
1051 1057 });
1052 1058
1053 var result = $H();
1054
1055 styleRules.each(function(pair){
1056 var property = pair[0], value = pair[1], unit = null;
1057
1058 if(value.parseColor('#zzzzzz') != '#zzzzzz') {
1059 value = value.parseColor();
1060 unit = 'color';
1061 } else if(Element.CSS_LENGTH.test(value))
1062 var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/),
1063 value = parseFloat(components[1]), unit = (components.length == 3) ? components[2] : null;
1064
1065 result[property.underscore().dasherize()] = $H({ value:value, unit:unit });
1066 }.bind(this));
1067
1068 return result;
1059 if (Prototype.Browser.IE && this.include('opacity'))
1060 styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
1061
1062 return styleRules;
1069 1063 };
1070 1064
1071 Element.morph = function(element, style) {
1072 new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
1073 return element;
1065 if (document.defaultView && document.defaultView.getComputedStyle) {
1066 Element.getStyles = function(element) {
1067 var css = document.defaultView.getComputedStyle($(element), null);
1068 return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
1069 styles[property] = css[property];
1070 return styles;
1071 });
1072 };
1073 } else {
1074 Element.getStyles = function(element) {
1075 element = $(element);
1076 var css = element.currentStyle, styles;
1077 styles = Element.CSS_PROPERTIES.inject({ }, function(hash, property) {
1078 hash.set(property, css[property]);
1079 return hash;
1080 });
1081 if (!styles.opacity) styles.set('opacity', element.getOpacity());
1082 return styles;
1083 };
1074 1084 };
1075 1085
1076 ['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
1077 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each(
1078 function(f) { Element.Methods[f] = Element[f]; }
1086 Effect.Methods = {
1087 morph: function(element, style) {
1088 element = $(element);
1089 new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
1090 return element;
1091 },
1092 visualEffect: function(element, effect, options) {
1093 element = $(element)
1094 var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
1095 new Effect[klass](element, options);
1096 return element;
1097 },
1098 highlight: function(element, options) {
1099 element = $(element);
1100 new Effect.Highlight(element, options);
1101 return element;
1102 }
1103 };
1104
1105 $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
1106 'pulsate shake puff squish switchOff dropOut').each(
1107 function(effect) {
1108 Effect.Methods[effect] = function(element, options){
1109 element = $(element);
1110 Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
1111 return element;
1112 }
1113 }
1079 1114 );
1080 1115
1081 Element.Methods.visualEffect = function(element, effect, options) {
1082 s = effect.gsub(/_/, '-').camelize();
1083 effect_class = s.charAt(0).toUpperCase() + s.substring(1);
1084 new Effect[effect_class](element, options);
1085 return $(element);
1086 };
1116 $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
1117 function(f) { Effect.Methods[f] = Element[f]; }
1118 );
1087 1119
1088 Element.addMethods(); No newline at end of file
1120 Element.addMethods(Effect.Methods);
This diff has been collapsed as it changes many lines, (3924 lines changed) Show them Hide them
@@ -1,43 +1,114
1 /* Prototype JavaScript framework, version 1.5.0
1 /* Prototype JavaScript framework, version 1.6.0.1
2 2 * (c) 2005-2007 Sam Stephenson
3 3 *
4 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/
5 * For details, see the Prototype web site: http://www.prototypejs.org/
6 6 *
7 /*--------------------------------------------------------------------------*/
7 *--------------------------------------------------------------------------*/
8 8
9 9 var Prototype = {
10 Version: '1.5.0',
10 Version: '1.6.0.1',
11
12 Browser: {
13 IE: !!(window.attachEvent && !window.opera),
14 Opera: !!window.opera,
15 WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
16 Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
17 MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
18 },
19
11 20 BrowserFeatures: {
12 XPath: !!document.evaluate
21 XPath: !!document.evaluate,
22 ElementExtensions: !!window.HTMLElement,
23 SpecificElementExtensions:
24 document.createElement('div').__proto__ &&
25 document.createElement('div').__proto__ !==
26 document.createElement('form').__proto__
13 27 },
14 28
15 ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
16 emptyFunction: function() {},
29 ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
30 JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
31
32 emptyFunction: function() { },
17 33 K: function(x) { return x }
18 }
34 };
35
36 if (Prototype.Browser.MobileSafari)
37 Prototype.BrowserFeatures.SpecificElementExtensions = false;
19 38
39
40 /* Based on Alex Arnell's inheritance implementation. */
20 41 var Class = {
21 42 create: function() {
22 return function() {
43 var parent = null, properties = $A(arguments);
44 if (Object.isFunction(properties[0]))
45 parent = properties.shift();
46
47 function klass() {
23 48 this.initialize.apply(this, arguments);
24 49 }
50
51 Object.extend(klass, Class.Methods);
52 klass.superclass = parent;
53 klass.subclasses = [];
54
55 if (parent) {
56 var subclass = function() { };
57 subclass.prototype = parent.prototype;
58 klass.prototype = new subclass;
59 parent.subclasses.push(klass);
60 }
61
62 for (var i = 0; i < properties.length; i++)
63 klass.addMethods(properties[i]);
64
65 if (!klass.prototype.initialize)
66 klass.prototype.initialize = Prototype.emptyFunction;
67
68 klass.prototype.constructor = klass;
69
70 return klass;
71 }
72 };
73
74 Class.Methods = {
75 addMethods: function(source) {
76 var ancestor = this.superclass && this.superclass.prototype;
77 var properties = Object.keys(source);
78
79 if (!Object.keys({ toString: true }).length)
80 properties.push("toString", "valueOf");
81
82 for (var i = 0, length = properties.length; i < length; i++) {
83 var property = properties[i], value = source[property];
84 if (ancestor && Object.isFunction(value) &&
85 value.argumentNames().first() == "$super") {
86 var method = value, value = Object.extend((function(m) {
87 return function() { return ancestor[m].apply(this, arguments) };
88 })(property).wrap(method), {
89 valueOf: function() { return method },
90 toString: function() { return method.toString() }
91 });
92 }
93 this.prototype[property] = value;
94 }
95
96 return this;
25 97 }
26 }
98 };
27 99
28 var Abstract = new Object();
100 var Abstract = { };
29 101
30 102 Object.extend = function(destination, source) {
31 for (var property in source) {
103 for (var property in source)
32 104 destination[property] = source[property];
33 }
34 105 return destination;
35 }
106 };
36 107
37 108 Object.extend(Object, {
38 109 inspect: function(object) {
39 110 try {
40 if (object === undefined) return 'undefined';
111 if (Object.isUndefined(object)) return 'undefined';
41 112 if (object === null) return 'null';
42 113 return object.inspect ? object.inspect() : object.toString();
43 114 } catch (e) {
@@ -46,6 +117,37 Object.extend(Object, {
46 117 }
47 118 },
48 119
120 toJSON: function(object) {
121 var type = typeof object;
122 switch (type) {
123 case 'undefined':
124 case 'function':
125 case 'unknown': return;
126 case 'boolean': return object.toString();
127 }
128
129 if (object === null) return 'null';
130 if (object.toJSON) return object.toJSON();
131 if (Object.isElement(object)) return;
132
133 var results = [];
134 for (var property in object) {
135 var value = Object.toJSON(object[property]);
136 if (!Object.isUndefined(value))
137 results.push(property.toJSON() + ': ' + value);
138 }
139
140 return '{' + results.join(', ') + '}';
141 },
142
143 toQueryString: function(object) {
144 return $H(object).toQueryString();
145 },
146
147 toHTML: function(object) {
148 return object && object.toHTML ? object.toHTML() : String.interpret(object);
149 },
150
49 151 keys: function(object) {
50 152 var keys = [];
51 153 for (var property in object)
@@ -61,41 +163,101 Object.extend(Object, {
61 163 },
62 164
63 165 clone: function(object) {
64 return Object.extend({}, object);
166 return Object.extend({ }, object);
167 },
168
169 isElement: function(object) {
170 return object && object.nodeType == 1;
171 },
172
173 isArray: function(object) {
174 return object && object.constructor === Array;
175 },
176
177 isHash: function(object) {
178 return object instanceof Hash;
179 },
180
181 isFunction: function(object) {
182 return typeof object == "function";
183 },
184
185 isString: function(object) {
186 return typeof object == "string";
187 },
188
189 isNumber: function(object) {
190 return typeof object == "number";
191 },
192
193 isUndefined: function(object) {
194 return typeof object == "undefined";
65 195 }
66 196 });
67 197
68 Function.prototype.bind = function() {
69 var __method = this, args = $A(arguments), object = args.shift();
70 return function() {
71 return __method.apply(object, args.concat($A(arguments)));
72 }
73 }
198 Object.extend(Function.prototype, {
199 argumentNames: function() {
200 var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
201 return names.length == 1 && !names[0] ? [] : names;
202 },
74 203
75 Function.prototype.bindAsEventListener = function(object) {
76 var __method = this, args = $A(arguments), object = args.shift();
77 return function(event) {
78 return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments)));
79 }
80 }
204 bind: function() {
205 if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
206 var __method = this, args = $A(arguments), object = args.shift();
207 return function() {
208 return __method.apply(object, args.concat($A(arguments)));
209 }
210 },
81 211
82 Object.extend(Number.prototype, {
83 toColorPart: function() {
84 var digits = this.toString(16);
85 if (this < 16) return '0' + digits;
86 return digits;
212 bindAsEventListener: function() {
213 var __method = this, args = $A(arguments), object = args.shift();
214 return function(event) {
215 return __method.apply(object, [event || window.event].concat(args));
216 }
87 217 },
88 218
89 succ: function() {
90 return this + 1;
219 curry: function() {
220 if (!arguments.length) return this;
221 var __method = this, args = $A(arguments);
222 return function() {
223 return __method.apply(this, args.concat($A(arguments)));
224 }
91 225 },
92 226
93 times: function(iterator) {
94 $R(0, this, true).each(iterator);
95 return this;
227 delay: function() {
228 var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
229 return window.setTimeout(function() {
230 return __method.apply(__method, args);
231 }, timeout);
232 },
233
234 wrap: function(wrapper) {
235 var __method = this;
236 return function() {
237 return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
238 }
239 },
240
241 methodize: function() {
242 if (this._methodized) return this._methodized;
243 var __method = this;
244 return this._methodized = function() {
245 return __method.apply(null, [this].concat($A(arguments)));
246 };
96 247 }
97 248 });
98 249
250 Function.prototype.defer = Function.prototype.delay.curry(0.01);
251
252 Date.prototype.toJSON = function() {
253 return '"' + this.getUTCFullYear() + '-' +
254 (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
255 this.getUTCDate().toPaddedString(2) + 'T' +
256 this.getUTCHours().toPaddedString(2) + ':' +
257 this.getUTCMinutes().toPaddedString(2) + ':' +
258 this.getUTCSeconds().toPaddedString(2) + 'Z"';
259 };
260
99 261 var Try = {
100 262 these: function() {
101 263 var returnValue;
@@ -105,17 +267,22 var Try = {
105 267 try {
106 268 returnValue = lambda();
107 269 break;
108 } catch (e) {}
270 } catch (e) { }
109 271 }
110 272
111 273 return returnValue;
112 274 }
113 }
275 };
276
277 RegExp.prototype.match = RegExp.prototype.test;
278
279 RegExp.escape = function(str) {
280 return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
281 };
114 282
115 283 /*--------------------------------------------------------------------------*/
116 284
117 var PeriodicalExecuter = Class.create();
118 PeriodicalExecuter.prototype = {
285 var PeriodicalExecuter = Class.create({
119 286 initialize: function(callback, frequency) {
120 287 this.callback = callback;
121 288 this.frequency = frequency;
@@ -128,6 +295,10 PeriodicalExecuter.prototype = {
128 295 this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
129 296 },
130 297
298 execute: function() {
299 this.callback(this);
300 },
301
131 302 stop: function() {
132 303 if (!this.timer) return;
133 304 clearInterval(this.timer);
@@ -138,16 +309,26 PeriodicalExecuter.prototype = {
138 309 if (!this.currentlyExecuting) {
139 310 try {
140 311 this.currentlyExecuting = true;
141 this.callback(this);
312 this.execute();
142 313 } finally {
143 314 this.currentlyExecuting = false;
144 315 }
145 316 }
146 317 }
147 }
148 String.interpret = function(value){
149 return value == null ? '' : String(value);
150 }
318 });
319 Object.extend(String, {
320 interpret: function(value) {
321 return value == null ? '' : String(value);
322 },
323 specialChar: {
324 '\b': '\\b',
325 '\t': '\\t',
326 '\n': '\\n',
327 '\f': '\\f',
328 '\r': '\\r',
329 '\\': '\\\\'
330 }
331 });
151 332
152 333 Object.extend(String.prototype, {
153 334 gsub: function(pattern, replacement) {
@@ -168,7 +349,7 Object.extend(String.prototype, {
168 349
169 350 sub: function(pattern, replacement, count) {
170 351 replacement = this.gsub.prepareReplacement(replacement);
171 count = count === undefined ? 1 : count;
352 count = Object.isUndefined(count) ? 1 : count;
172 353
173 354 return this.gsub(pattern, function(match) {
174 355 if (--count < 0) return match[0];
@@ -178,14 +359,14 Object.extend(String.prototype, {
178 359
179 360 scan: function(pattern, iterator) {
180 361 this.gsub(pattern, iterator);
181 return this;
362 return String(this);
182 363 },
183 364
184 365 truncate: function(length, truncation) {
185 366 length = length || 30;
186 truncation = truncation === undefined ? '...' : truncation;
367 truncation = Object.isUndefined(truncation) ? '...' : truncation;
187 368 return this.length > length ?
188 this.slice(0, length - truncation.length) + truncation : this;
369 this.slice(0, length - truncation.length) + truncation : String(this);
189 370 },
190 371
191 372 strip: function() {
@@ -213,35 +394,34 Object.extend(String.prototype, {
213 394 },
214 395
215 396 escapeHTML: function() {
216 var div = document.createElement('div');
217 var text = document.createTextNode(this);
218 div.appendChild(text);
219 return div.innerHTML;
397 var self = arguments.callee;
398 self.text.data = this;
399 return self.div.innerHTML;
220 400 },
221 401
222 402 unescapeHTML: function() {
223 var div = document.createElement('div');
403 var div = new Element('div');
224 404 div.innerHTML = this.stripTags();
225 405 return div.childNodes[0] ? (div.childNodes.length > 1 ?
226 $A(div.childNodes).inject('',function(memo,node){ return memo+node.nodeValue }) :
406 $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
227 407 div.childNodes[0].nodeValue) : '';
228 408 },
229 409
230 410 toQueryParams: function(separator) {
231 411 var match = this.strip().match(/([^?#]*)(#.*)?$/);
232 if (!match) return {};
412 if (!match) return { };
233 413
234 return match[1].split(separator || '&').inject({}, function(hash, pair) {
414 return match[1].split(separator || '&').inject({ }, function(hash, pair) {
235 415 if ((pair = pair.split('='))[0]) {
236 var name = decodeURIComponent(pair[0]);
237 var value = pair[1] ? decodeURIComponent(pair[1]) : undefined;
416 var key = decodeURIComponent(pair.shift());
417 var value = pair.length > 1 ? pair.join('=') : pair[0];
418 if (value != undefined) value = decodeURIComponent(value);
238 419
239 if (hash[name] !== undefined) {
240 if (hash[name].constructor != Array)
241 hash[name] = [hash[name]];
242 if (value) hash[name].push(value);
420 if (key in hash) {
421 if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
422 hash[key].push(value);
243 423 }
244 else hash[name] = value;
424 else hash[key] = value;
245 425 }
246 426 return hash;
247 427 });
@@ -256,6 +436,10 Object.extend(String.prototype, {
256 436 String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
257 437 },
258 438
439 times: function(count) {
440 return count < 1 ? '' : new Array(count + 1).join(this);
441 },
442
259 443 camelize: function() {
260 444 var parts = this.split('-'), len = parts.length;
261 445 if (len == 1) return parts[0];
@@ -270,7 +454,7 Object.extend(String.prototype, {
270 454 return camelized;
271 455 },
272 456
273 capitalize: function(){
457 capitalize: function() {
274 458 return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
275 459 },
276 460
@@ -283,52 +467,131 Object.extend(String.prototype, {
283 467 },
284 468
285 469 inspect: function(useDoubleQuotes) {
286 var escapedString = this.replace(/\\/g, '\\\\');
287 if (useDoubleQuotes)
288 return '"' + escapedString.replace(/"/g, '\\"') + '"';
289 else
290 return "'" + escapedString.replace(/'/g, '\\\'') + "'";
470 var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
471 var character = String.specialChar[match[0]];
472 return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
473 });
474 if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
475 return "'" + escapedString.replace(/'/g, '\\\'') + "'";
476 },
477
478 toJSON: function() {
479 return this.inspect(true);
480 },
481
482 unfilterJSON: function(filter) {
483 return this.sub(filter || Prototype.JSONFilter, '#{1}');
484 },
485
486 isJSON: function() {
487 var str = this;
488 if (str.blank()) return false;
489 str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
490 return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
491 },
492
493 evalJSON: function(sanitize) {
494 var json = this.unfilterJSON();
495 try {
496 if (!sanitize || json.isJSON()) return eval('(' + json + ')');
497 } catch (e) { }
498 throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
499 },
500
501 include: function(pattern) {
502 return this.indexOf(pattern) > -1;
503 },
504
505 startsWith: function(pattern) {
506 return this.indexOf(pattern) === 0;
507 },
508
509 endsWith: function(pattern) {
510 var d = this.length - pattern.length;
511 return d >= 0 && this.lastIndexOf(pattern) === d;
512 },
513
514 empty: function() {
515 return this == '';
516 },
517
518 blank: function() {
519 return /^\s*$/.test(this);
520 },
521
522 interpolate: function(object, pattern) {
523 return new Template(this, pattern).evaluate(object);
524 }
525 });
526
527 if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
528 escapeHTML: function() {
529 return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
530 },
531 unescapeHTML: function() {
532 return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
291 533 }
292 534 });
293 535
294 536 String.prototype.gsub.prepareReplacement = function(replacement) {
295 if (typeof replacement == 'function') return replacement;
537 if (Object.isFunction(replacement)) return replacement;
296 538 var template = new Template(replacement);
297 539 return function(match) { return template.evaluate(match) };
298 }
540 };
299 541
300 542 String.prototype.parseQuery = String.prototype.toQueryParams;
301 543
302 var Template = Class.create();
303 Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
304 Template.prototype = {
544 Object.extend(String.prototype.escapeHTML, {
545 div: document.createElement('div'),
546 text: document.createTextNode('')
547 });
548
549 with (String.prototype.escapeHTML) div.appendChild(text);
550
551 var Template = Class.create({
305 552 initialize: function(template, pattern) {
306 553 this.template = template.toString();
307 this.pattern = pattern || Template.Pattern;
554 this.pattern = pattern || Template.Pattern;
308 555 },
309 556
310 557 evaluate: function(object) {
558 if (Object.isFunction(object.toTemplateReplacements))
559 object = object.toTemplateReplacements();
560
311 561 return this.template.gsub(this.pattern, function(match) {
312 var before = match[1];
562 if (object == null) return '';
563
564 var before = match[1] || '';
313 565 if (before == '\\') return match[2];
314 return before + String.interpret(object[match[3]]);
315 });
566
567 var ctx = object, expr = match[3];
568 var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
569 match = pattern.exec(expr);
570 if (match == null) return before;
571
572 while (match != null) {
573 var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
574 ctx = ctx[comp];
575 if (null == ctx || '' == match[3]) break;
576 expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
577 match = pattern.exec(expr);
578 }
579
580 return before + String.interpret(ctx);
581 }.bind(this));
316 582 }
317 }
583 });
584 Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
318 585
319 var $break = new Object();
320 var $continue = new Object();
586 var $break = { };
321 587
322 588 var Enumerable = {
323 each: function(iterator) {
589 each: function(iterator, context) {
324 590 var index = 0;
591 iterator = iterator.bind(context);
325 592 try {
326 593 this._each(function(value) {
327 try {
328 iterator(value, index++);
329 } catch (e) {
330 if (e != $continue) throw e;
331 }
594 iterator(value, index++);
332 595 });
333 596 } catch (e) {
334 597 if (e != $break) throw e;
@@ -336,40 +599,45 var Enumerable = {
336 599 return this;
337 600 },
338 601
339 eachSlice: function(number, iterator) {
602 eachSlice: function(number, iterator, context) {
603 iterator = iterator ? iterator.bind(context) : Prototype.K;
340 604 var index = -number, slices = [], array = this.toArray();
341 605 while ((index += number) < array.length)
342 606 slices.push(array.slice(index, index+number));
343 return slices.map(iterator);
607 return slices.collect(iterator, context);
344 608 },
345 609
346 all: function(iterator) {
610 all: function(iterator, context) {
611 iterator = iterator ? iterator.bind(context) : Prototype.K;
347 612 var result = true;
348 613 this.each(function(value, index) {
349 result = result && !!(iterator || Prototype.K)(value, index);
614 result = result && !!iterator(value, index);
350 615 if (!result) throw $break;
351 616 });
352 617 return result;
353 618 },
354 619
355 any: function(iterator) {
620 any: function(iterator, context) {
621 iterator = iterator ? iterator.bind(context) : Prototype.K;
356 622 var result = false;
357 623 this.each(function(value, index) {
358 if (result = !!(iterator || Prototype.K)(value, index))
624 if (result = !!iterator(value, index))
359 625 throw $break;
360 626 });
361 627 return result;
362 628 },
363 629
364 collect: function(iterator) {
630 collect: function(iterator, context) {
631 iterator = iterator ? iterator.bind(context) : Prototype.K;
365 632 var results = [];
366 633 this.each(function(value, index) {
367 results.push((iterator || Prototype.K)(value, index));
634 results.push(iterator(value, index));
368 635 });
369 636 return results;
370 637 },
371 638
372 detect: function(iterator) {
639 detect: function(iterator, context) {
640 iterator = iterator.bind(context);
373 641 var result;
374 642 this.each(function(value, index) {
375 643 if (iterator(value, index)) {
@@ -380,7 +648,8 var Enumerable = {
380 648 return result;
381 649 },
382 650
383 findAll: function(iterator) {
651 findAll: function(iterator, context) {
652 iterator = iterator.bind(context);
384 653 var results = [];
385 654 this.each(function(value, index) {
386 655 if (iterator(value, index))
@@ -389,17 +658,24 var Enumerable = {
389 658 return results;
390 659 },
391 660
392 grep: function(pattern, iterator) {
661 grep: function(filter, iterator, context) {
662 iterator = iterator ? iterator.bind(context) : Prototype.K;
393 663 var results = [];
664
665 if (Object.isString(filter))
666 filter = new RegExp(filter);
667
394 668 this.each(function(value, index) {
395 var stringValue = value.toString();
396 if (stringValue.match(pattern))
397 results.push((iterator || Prototype.K)(value, index));
398 })
669 if (filter.match(value))
670 results.push(iterator(value, index));
671 });
399 672 return results;
400 673 },
401 674
402 675 include: function(object) {
676 if (Object.isFunction(this.indexOf))
677 if (this.indexOf(object) != -1) return true;
678
403 679 var found = false;
404 680 this.each(function(value) {
405 681 if (value == object) {
@@ -411,14 +687,15 var Enumerable = {
411 687 },
412 688
413 689 inGroupsOf: function(number, fillWith) {
414 fillWith = fillWith === undefined ? null : fillWith;
690 fillWith = Object.isUndefined(fillWith) ? null : fillWith;
415 691 return this.eachSlice(number, function(slice) {
416 692 while(slice.length < number) slice.push(fillWith);
417 693 return slice;
418 694 });
419 695 },
420 696
421 inject: function(memo, iterator) {
697 inject: function(memo, iterator, context) {
698 iterator = iterator.bind(context);
422 699 this.each(function(value, index) {
423 700 memo = iterator(memo, value, index);
424 701 });
@@ -432,30 +709,33 var Enumerable = {
432 709 });
433 710 },
434 711
435 max: function(iterator) {
712 max: function(iterator, context) {
713 iterator = iterator ? iterator.bind(context) : Prototype.K;
436 714 var result;
437 715 this.each(function(value, index) {
438 value = (iterator || Prototype.K)(value, index);
439 if (result == undefined || value >= result)
716 value = iterator(value, index);
717 if (result == null || value >= result)
440 718 result = value;
441 719 });
442 720 return result;
443 721 },
444 722
445 min: function(iterator) {
723 min: function(iterator, context) {
724 iterator = iterator ? iterator.bind(context) : Prototype.K;
446 725 var result;
447 726 this.each(function(value, index) {
448 value = (iterator || Prototype.K)(value, index);
449 if (result == undefined || value < result)
727 value = iterator(value, index);
728 if (result == null || value < result)
450 729 result = value;
451 730 });
452 731 return result;
453 732 },
454 733
455 partition: function(iterator) {
734 partition: function(iterator, context) {
735 iterator = iterator ? iterator.bind(context) : Prototype.K;
456 736 var trues = [], falses = [];
457 737 this.each(function(value, index) {
458 ((iterator || Prototype.K)(value, index) ?
738 (iterator(value, index) ?
459 739 trues : falses).push(value);
460 740 });
461 741 return [trues, falses];
@@ -463,13 +743,14 var Enumerable = {
463 743
464 744 pluck: function(property) {
465 745 var results = [];
466 this.each(function(value, index) {
746 this.each(function(value) {
467 747 results.push(value[property]);
468 748 });
469 749 return results;
470 750 },
471 751
472 reject: function(iterator) {
752 reject: function(iterator, context) {
753 iterator = iterator.bind(context);
473 754 var results = [];
474 755 this.each(function(value, index) {
475 756 if (!iterator(value, index))
@@ -478,7 +759,8 var Enumerable = {
478 759 return results;
479 760 },
480 761
481 sortBy: function(iterator) {
762 sortBy: function(iterator, context) {
763 iterator = iterator.bind(context);
482 764 return this.map(function(value, index) {
483 765 return {value: value, criteria: iterator(value, index)};
484 766 }).sort(function(left, right) {
@@ -493,7 +775,7 var Enumerable = {
493 775
494 776 zip: function() {
495 777 var iterator = Prototype.K, args = $A(arguments);
496 if (typeof args.last() == 'function')
778 if (Object.isFunction(args.last()))
497 779 iterator = args.pop();
498 780
499 781 var collections = [this].concat(args).map($A);
@@ -509,31 +791,42 var Enumerable = {
509 791 inspect: function() {
510 792 return '#<Enumerable:' + this.toArray().inspect() + '>';
511 793 }
512 }
794 };
513 795
514 796 Object.extend(Enumerable, {
515 797 map: Enumerable.collect,
516 798 find: Enumerable.detect,
517 799 select: Enumerable.findAll,
800 filter: Enumerable.findAll,
518 801 member: Enumerable.include,
519 entries: Enumerable.toArray
802 entries: Enumerable.toArray,
803 every: Enumerable.all,
804 some: Enumerable.any
520 805 });
521 var $A = Array.from = function(iterable) {
806 function $A(iterable) {
522 807 if (!iterable) return [];
523 if (iterable.toArray) {
524 return iterable.toArray();
525 } else {
526 var results = [];
527 for (var i = 0, length = iterable.length; i < length; i++)
528 results.push(iterable[i]);
808 if (iterable.toArray) return iterable.toArray();
809 var length = iterable.length, results = new Array(length);
810 while (length--) results[length] = iterable[length];
811 return results;
812 }
813
814 if (Prototype.Browser.WebKit) {
815 function $A(iterable) {
816 if (!iterable) return [];
817 if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
818 iterable.toArray) return iterable.toArray();
819 var length = iterable.length, results = new Array(length);
820 while (length--) results[length] = iterable[length];
529 821 return results;
530 822 }
531 823 }
532 824
825 Array.from = $A;
826
533 827 Object.extend(Array.prototype, Enumerable);
534 828
535 if (!Array.prototype._reverse)
536 Array.prototype._reverse = Array.prototype.reverse;
829 if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
537 830
538 831 Object.extend(Array.prototype, {
539 832 _each: function(iterator) {
@@ -562,7 +855,7 Object.extend(Array.prototype, {
562 855
563 856 flatten: function() {
564 857 return this.inject([], function(array, value) {
565 return array.concat(value && value.constructor == Array ?
858 return array.concat(Object.isArray(value) ?
566 859 value.flatten() : [value]);
567 860 });
568 861 },
@@ -574,12 +867,6 Object.extend(Array.prototype, {
574 867 });
575 868 },
576 869
577 indexOf: function(object) {
578 for (var i = 0, length = this.length; i < length; i++)
579 if (this[i] == object) return i;
580 return -1;
581 },
582
583 870 reverse: function(inline) {
584 871 return (inline !== false ? this : this.toArray())._reverse();
585 872 },
@@ -588,9 +875,17 Object.extend(Array.prototype, {
588 875 return this.length > 1 ? this : this[0];
589 876 },
590 877
591 uniq: function() {
592 return this.inject([], function(array, value) {
593 return array.include(value) ? array : array.concat([value]);
878 uniq: function(sorted) {
879 return this.inject([], function(array, value, index) {
880 if (0 == index || (sorted ? array.last() != value : !array.include(value)))
881 array.push(value);
882 return array;
883 });
884 },
885
886 intersect: function(array) {
887 return this.uniq().findAll(function(item) {
888 return array.detect(function(value) { return item === value });
594 889 });
595 890 },
596 891
@@ -604,125 +899,187 Object.extend(Array.prototype, {
604 899
605 900 inspect: function() {
606 901 return '[' + this.map(Object.inspect).join(', ') + ']';
902 },
903
904 toJSON: function() {
905 var results = [];
906 this.each(function(object) {
907 var value = Object.toJSON(object);
908 if (!Object.isUndefined(value)) results.push(value);
909 });
910 return '[' + results.join(', ') + ']';
607 911 }
608 912 });
609 913
914 // use native browser JS 1.6 implementation if available
915 if (Object.isFunction(Array.prototype.forEach))
916 Array.prototype._each = Array.prototype.forEach;
917
918 if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
919 i || (i = 0);
920 var length = this.length;
921 if (i < 0) i = length + i;
922 for (; i < length; i++)
923 if (this[i] === item) return i;
924 return -1;
925 };
926
927 if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
928 i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
929 var n = this.slice(0, i).reverse().indexOf(item);
930 return (n < 0) ? n : i - n - 1;
931 };
932
610 933 Array.prototype.toArray = Array.prototype.clone;
611 934
612 function $w(string){
935 function $w(string) {
936 if (!Object.isString(string)) return [];
613 937 string = string.strip();
614 938 return string ? string.split(/\s+/) : [];
615 939 }
616 940
617 if(window.opera){
618 Array.prototype.concat = function(){
941 if (Prototype.Browser.Opera){
942 Array.prototype.concat = function() {
619 943 var array = [];
620 for(var i = 0, length = this.length; i < length; i++) array.push(this[i]);
621 for(var i = 0, length = arguments.length; i < length; i++) {
622 if(arguments[i].constructor == Array) {
623 for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
944 for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
945 for (var i = 0, length = arguments.length; i < length; i++) {
946 if (Object.isArray(arguments[i])) {
947 for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
624 948 array.push(arguments[i][j]);
625 949 } else {
626 950 array.push(arguments[i]);
627 951 }
628 952 }
629 953 return array;
630 }
954 };
631 955 }
632 var Hash = function(obj) {
633 Object.extend(this, obj || {});
634 };
635
636 Object.extend(Hash, {
637 toQueryString: function(obj) {
638 var parts = [];
639
640 this.prototype._each.call(obj, function(pair) {
641 if (!pair.key) return;
642
643 if (pair.value && pair.value.constructor == Array) {
644 var values = pair.value.compact();
645 if (values.length < 2) pair.value = values.reduce();
646 else {
647 key = encodeURIComponent(pair.key);
648 values.each(function(value) {
649 value = value != undefined ? encodeURIComponent(value) : '';
650 parts.push(key + '=' + encodeURIComponent(value));
651 });
652 return;
653 }
654 }
655 if (pair.value == undefined) pair[1] = '';
656 parts.push(pair.map(encodeURIComponent).join('='));
657 });
658
659 return parts.join('&');
660 }
661 });
662
663 Object.extend(Hash.prototype, Enumerable);
664 Object.extend(Hash.prototype, {
665 _each: function(iterator) {
666 for (var key in this) {
667 var value = this[key];
668 if (value && value == Hash.prototype[key]) continue;
669
670 var pair = [key, value];
671 pair.key = key;
672 pair.value = value;
673 iterator(pair);
674 }
675 },
676
677 keys: function() {
678 return this.pluck('key');
679 },
680
681 values: function() {
682 return this.pluck('value');
956 Object.extend(Number.prototype, {
957 toColorPart: function() {
958 return this.toPaddedString(2, 16);
683 959 },
684 960
685 merge: function(hash) {
686 return $H(hash).inject(this, function(mergedHash, pair) {
687 mergedHash[pair.key] = pair.value;
688 return mergedHash;
689 });
961 succ: function() {
962 return this + 1;
690 963 },
691 964
692 remove: function() {
693 var result;
694 for(var i = 0, length = arguments.length; i < length; i++) {
695 var value = this[arguments[i]];
696 if (value !== undefined){
697 if (result === undefined) result = value;
698 else {
699 if (result.constructor != Array) result = [result];
700 result.push(value)
701 }
702 }
703 delete this[arguments[i]];
704 }
705 return result;
965 times: function(iterator) {
966 $R(0, this, true).each(iterator);
967 return this;
706 968 },
707 969
708 toQueryString: function() {
709 return Hash.toQueryString(this);
970 toPaddedString: function(length, radix) {
971 var string = this.toString(radix || 10);
972 return '0'.times(length - string.length) + string;
710 973 },
711 974
712 inspect: function() {
713 return '#<Hash:{' + this.map(function(pair) {
714 return pair.map(Object.inspect).join(': ');
715 }).join(', ') + '}>';
975 toJSON: function() {
976 return isFinite(this) ? this.toString() : 'null';
716 977 }
717 978 });
718 979
980 $w('abs round ceil floor').each(function(method){
981 Number.prototype[method] = Math[method].methodize();
982 });
719 983 function $H(object) {
720 if (object && object.constructor == Hash) return object;
721 984 return new Hash(object);
722 985 };
723 ObjectRange = Class.create();
724 Object.extend(ObjectRange.prototype, Enumerable);
725 Object.extend(ObjectRange.prototype, {
986
987 var Hash = Class.create(Enumerable, (function() {
988
989 function toQueryPair(key, value) {
990 if (Object.isUndefined(value)) return key;
991 return key + '=' + encodeURIComponent(String.interpret(value));
992 }
993
994 return {
995 initialize: function(object) {
996 this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
997 },
998
999 _each: function(iterator) {
1000 for (var key in this._object) {
1001 var value = this._object[key], pair = [key, value];
1002 pair.key = key;
1003 pair.value = value;
1004 iterator(pair);
1005 }
1006 },
1007
1008 set: function(key, value) {
1009 return this._object[key] = value;
1010 },
1011
1012 get: function(key) {
1013 return this._object[key];
1014 },
1015
1016 unset: function(key) {
1017 var value = this._object[key];
1018 delete this._object[key];
1019 return value;
1020 },
1021
1022 toObject: function() {
1023 return Object.clone(this._object);
1024 },
1025
1026 keys: function() {
1027 return this.pluck('key');
1028 },
1029
1030 values: function() {
1031 return this.pluck('value');
1032 },
1033
1034 index: function(value) {
1035 var match = this.detect(function(pair) {
1036 return pair.value === value;
1037 });
1038 return match && match.key;
1039 },
1040
1041 merge: function(object) {
1042 return this.clone().update(object);
1043 },
1044
1045 update: function(object) {
1046 return new Hash(object).inject(this, function(result, pair) {
1047 result.set(pair.key, pair.value);
1048 return result;
1049 });
1050 },
1051
1052 toQueryString: function() {
1053 return this.map(function(pair) {
1054 var key = encodeURIComponent(pair.key), values = pair.value;
1055
1056 if (values && typeof values == 'object') {
1057 if (Object.isArray(values))
1058 return values.map(toQueryPair.curry(key)).join('&');
1059 }
1060 return toQueryPair(key, values);
1061 }).join('&');
1062 },
1063
1064 inspect: function() {
1065 return '#<Hash:{' + this.map(function(pair) {
1066 return pair.map(Object.inspect).join(': ');
1067 }).join(', ') + '}>';
1068 },
1069
1070 toJSON: function() {
1071 return Object.toJSON(this.toObject());
1072 },
1073
1074 clone: function() {
1075 return new Hash(this);
1076 }
1077 }
1078 })());
1079
1080 Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
1081 Hash.from = $H;
1082 var ObjectRange = Class.create(Enumerable, {
726 1083 initialize: function(start, end, exclusive) {
727 1084 this.start = start;
728 1085 this.end = end;
@@ -748,7 +1105,7 Object.extend(ObjectRange.prototype, {
748 1105
749 1106 var $R = function(start, end, exclusive) {
750 1107 return new ObjectRange(start, end, exclusive);
751 }
1108 };
752 1109
753 1110 var Ajax = {
754 1111 getTransport: function() {
@@ -760,7 +1117,7 var Ajax = {
760 1117 },
761 1118
762 1119 activeRequestCount: 0
763 }
1120 };
764 1121
765 1122 Ajax.Responders = {
766 1123 responders: [],
@@ -780,10 +1137,10 Ajax.Responders = {
780 1137
781 1138 dispatch: function(callback, request, transport, json) {
782 1139 this.each(function(responder) {
783 if (typeof responder[callback] == 'function') {
1140 if (Object.isFunction(responder[callback])) {
784 1141 try {
785 1142 responder[callback].apply(responder, [request, transport, json]);
786 } catch (e) {}
1143 } catch (e) { }
787 1144 }
788 1145 });
789 1146 }
@@ -792,49 +1149,45 Ajax.Responders = {
792 1149 Object.extend(Ajax.Responders, Enumerable);
793 1150
794 1151 Ajax.Responders.register({
795 onCreate: function() {
796 Ajax.activeRequestCount++;
797 },
798 onComplete: function() {
799 Ajax.activeRequestCount--;
800 }
1152 onCreate: function() { Ajax.activeRequestCount++ },
1153 onComplete: function() { Ajax.activeRequestCount-- }
801 1154 });
802 1155
803 Ajax.Base = function() {};
804 Ajax.Base.prototype = {
805 setOptions: function(options) {
1156 Ajax.Base = Class.create({
1157 initialize: function(options) {
806 1158 this.options = {
807 1159 method: 'post',
808 1160 asynchronous: true,
809 1161 contentType: 'application/x-www-form-urlencoded',
810 1162 encoding: 'UTF-8',
811 parameters: ''
812 }
813 Object.extend(this.options, options || {});
1163 parameters: '',
1164 evalJSON: true,
1165 evalJS: true
1166 };
1167 Object.extend(this.options, options || { });
814 1168
815 1169 this.options.method = this.options.method.toLowerCase();
816 if (typeof this.options.parameters == 'string')
1170
1171 if (Object.isString(this.options.parameters))
817 1172 this.options.parameters = this.options.parameters.toQueryParams();
1173 else if (Object.isHash(this.options.parameters))
1174 this.options.parameters = this.options.parameters.toObject();
818 1175 }
819 }
820
821 Ajax.Request = Class.create();
822 Ajax.Request.Events =
823 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
1176 });
824 1177
825 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
1178 Ajax.Request = Class.create(Ajax.Base, {
826 1179 _complete: false,
827 1180
828 initialize: function(url, options) {
1181 initialize: function($super, url, options) {
1182 $super(options);
829 1183 this.transport = Ajax.getTransport();
830 this.setOptions(options);
831 1184 this.request(url);
832 1185 },
833 1186
834 1187 request: function(url) {
835 1188 this.url = url;
836 1189 this.method = this.options.method;
837 var params = this.options.parameters;
1190 var params = Object.clone(this.options.parameters);
838 1191
839 1192 if (!['get', 'post'].include(this.method)) {
840 1193 // simulate other verbs over post
@@ -842,28 +1195,31 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
842 1195 this.method = 'post';
843 1196 }
844 1197
845 params = Hash.toQueryString(params);
846 if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_='
1198 this.parameters = params;
847 1199
848 // when GET, append parameters to URL
849 if (this.method == 'get' && params)
850 this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params;
1200 if (params = Object.toQueryString(params)) {
1201 // when GET, append parameters to URL
1202 if (this.method == 'get')
1203 this.url += (this.url.include('?') ? '&' : '?') + params;
1204 else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
1205 params += '&_=';
1206 }
851 1207
852 1208 try {
853 Ajax.Responders.dispatch('onCreate', this, this.transport);
1209 var response = new Ajax.Response(this);
1210 if (this.options.onCreate) this.options.onCreate(response);
1211 Ajax.Responders.dispatch('onCreate', this, response);
854 1212
855 1213 this.transport.open(this.method.toUpperCase(), this.url,
856 1214 this.options.asynchronous);
857 1215
858 if (this.options.asynchronous)
859 setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);
1216 if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
860 1217
861 1218 this.transport.onreadystatechange = this.onStateChange.bind(this);
862 1219 this.setRequestHeaders();
863 1220
864 var body = this.method == 'post' ? (this.options.postBody || params) : null;
865
866 this.transport.send(body);
1221 this.body = this.method == 'post' ? (this.options.postBody || params) : null;
1222 this.transport.send(this.body);
867 1223
868 1224 /* Force Firefox to handle ready state 4 for synchronous requests */
869 1225 if (!this.options.asynchronous && this.transport.overrideMimeType)
@@ -905,7 +1261,7 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
905 1261 if (typeof this.options.requestHeaders == 'object') {
906 1262 var extras = this.options.requestHeaders;
907 1263
908 if (typeof extras.push == 'function')
1264 if (Object.isFunction(extras.push))
909 1265 for (var i = 0, length = extras.length; i < length; i += 2)
910 1266 headers[extras[i]] = extras[i+1];
911 1267 else
@@ -917,32 +1273,39 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
917 1273 },
918 1274
919 1275 success: function() {
920 return !this.transport.status
921 || (this.transport.status >= 200 && this.transport.status < 300);
1276 var status = this.getStatus();
1277 return !status || (status >= 200 && status < 300);
1278 },
1279
1280 getStatus: function() {
1281 try {
1282 return this.transport.status || 0;
1283 } catch (e) { return 0 }
922 1284 },
923 1285
924 1286 respondToReadyState: function(readyState) {
925 var state = Ajax.Request.Events[readyState];
926 var transport = this.transport, json = this.evalJSON();
1287 var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
927 1288
928 1289 if (state == 'Complete') {
929 1290 try {
930 1291 this._complete = true;
931 (this.options['on' + this.transport.status]
1292 (this.options['on' + response.status]
932 1293 || this.options['on' + (this.success() ? 'Success' : 'Failure')]
933 || Prototype.emptyFunction)(transport, json);
1294 || Prototype.emptyFunction)(response, response.headerJSON);
934 1295 } catch (e) {
935 1296 this.dispatchException(e);
936 1297 }
937 1298
938 if ((this.getHeader('Content-type') || 'text/javascript').strip().
939 match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
940 this.evalResponse();
1299 var contentType = response.getHeader('Content-type');
1300 if (this.options.evalJS == 'force'
1301 || (this.options.evalJS && contentType
1302 && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
1303 this.evalResponse();
941 1304 }
942 1305
943 1306 try {
944 (this.options['on' + state] || Prototype.emptyFunction)(transport, json);
945 Ajax.Responders.dispatch('on' + state, this, transport, json);
1307 (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
1308 Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
946 1309 } catch (e) {
947 1310 this.dispatchException(e);
948 1311 }
@@ -959,16 +1322,9 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
959 1322 } catch (e) { return null }
960 1323 },
961 1324
962 evalJSON: function() {
963 try {
964 var json = this.getHeader('X-JSON');
965 return json ? eval('(' + json + ')') : null;
966 } catch (e) { return null }
967 },
968
969 1325 evalResponse: function() {
970 1326 try {
971 return eval(this.transport.responseText);
1327 return eval((this.transport.responseText || '').unfilterJSON());
972 1328 } catch (e) {
973 1329 this.dispatchException(e);
974 1330 }
@@ -980,57 +1336,126 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
980 1336 }
981 1337 });
982 1338
983 Ajax.Updater = Class.create();
1339 Ajax.Request.Events =
1340 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
984 1341
985 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
986 initialize: function(container, url, options) {
987 this.container = {
988 success: (container.success || container),
989 failure: (container.failure || (container.success ? null : container))
1342 Ajax.Response = Class.create({
1343 initialize: function(request){
1344 this.request = request;
1345 var transport = this.transport = request.transport,
1346 readyState = this.readyState = transport.readyState;
1347
1348 if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
1349 this.status = this.getStatus();
1350 this.statusText = this.getStatusText();
1351 this.responseText = String.interpret(transport.responseText);
1352 this.headerJSON = this._getHeaderJSON();
990 1353 }
991 1354
992 this.transport = Ajax.getTransport();
993 this.setOptions(options);
1355 if(readyState == 4) {
1356 var xml = transport.responseXML;
1357 this.responseXML = Object.isUndefined(xml) ? null : xml;
1358 this.responseJSON = this._getResponseJSON();
1359 }
1360 },
994 1361
995 var onComplete = this.options.onComplete || Prototype.emptyFunction;
996 this.options.onComplete = (function(transport, param) {
997 this.updateContent();
998 onComplete(transport, param);
999 }).bind(this);
1362 status: 0,
1363 statusText: '',
1000 1364
1001 this.request(url);
1365 getStatus: Ajax.Request.prototype.getStatus,
1366
1367 getStatusText: function() {
1368 try {
1369 return this.transport.statusText || '';
1370 } catch (e) { return '' }
1002 1371 },
1003 1372
1004 updateContent: function() {
1005 var receiver = this.container[this.success() ? 'success' : 'failure'];
1006 var response = this.transport.responseText;
1373 getHeader: Ajax.Request.prototype.getHeader,
1007 1374
1008 if (!this.options.evalScripts) response = response.stripScripts();
1375 getAllHeaders: function() {
1376 try {
1377 return this.getAllResponseHeaders();
1378 } catch (e) { return null }
1379 },
1009 1380
1010 if (receiver = $(receiver)) {
1011 if (this.options.insertion)
1012 new this.options.insertion(receiver, response);
1013 else
1014 receiver.update(response);
1381 getResponseHeader: function(name) {
1382 return this.transport.getResponseHeader(name);
1383 },
1384
1385 getAllResponseHeaders: function() {
1386 return this.transport.getAllResponseHeaders();
1387 },
1388
1389 _getHeaderJSON: function() {
1390 var json = this.getHeader('X-JSON');
1391 if (!json) return null;
1392 json = decodeURIComponent(escape(json));
1393 try {
1394 return json.evalJSON(this.request.options.sanitizeJSON);
1395 } catch (e) {
1396 this.request.dispatchException(e);
1397 }
1398 },
1399
1400 _getResponseJSON: function() {
1401 var options = this.request.options;
1402 if (!options.evalJSON || (options.evalJSON != 'force' &&
1403 !(this.getHeader('Content-type') || '').include('application/json')) ||
1404 this.responseText.blank())
1405 return null;
1406 try {
1407 return this.responseText.evalJSON(options.sanitizeJSON);
1408 } catch (e) {
1409 this.request.dispatchException(e);
1015 1410 }
1411 }
1412 });
1413
1414 Ajax.Updater = Class.create(Ajax.Request, {
1415 initialize: function($super, container, url, options) {
1416 this.container = {
1417 success: (container.success || container),
1418 failure: (container.failure || (container.success ? null : container))
1419 };
1420
1421 options = Object.clone(options);
1422 var onComplete = options.onComplete;
1423 options.onComplete = (function(response, json) {
1424 this.updateContent(response.responseText);
1425 if (Object.isFunction(onComplete)) onComplete(response, json);
1426 }).bind(this);
1427
1428 $super(url, options);
1429 },
1430
1431 updateContent: function(responseText) {
1432 var receiver = this.container[this.success() ? 'success' : 'failure'],
1433 options = this.options;
1016 1434
1017 if (this.success()) {
1018 if (this.onComplete)
1019 setTimeout(this.onComplete.bind(this), 10);
1435 if (!options.evalScripts) responseText = responseText.stripScripts();
1436
1437 if (receiver = $(receiver)) {
1438 if (options.insertion) {
1439 if (Object.isString(options.insertion)) {
1440 var insertion = { }; insertion[options.insertion] = responseText;
1441 receiver.insert(insertion);
1442 }
1443 else options.insertion(receiver, responseText);
1444 }
1445 else receiver.update(responseText);
1020 1446 }
1021 1447 }
1022 1448 });
1023 1449
1024 Ajax.PeriodicalUpdater = Class.create();
1025 Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
1026 initialize: function(container, url, options) {
1027 this.setOptions(options);
1450 Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
1451 initialize: function($super, container, url, options) {
1452 $super(options);
1028 1453 this.onComplete = this.options.onComplete;
1029 1454
1030 1455 this.frequency = (this.options.frequency || 2);
1031 1456 this.decay = (this.options.decay || 1);
1032 1457
1033 this.updater = {};
1458 this.updater = { };
1034 1459 this.container = container;
1035 1460 this.url = url;
1036 1461
@@ -1048,15 +1473,14 Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
1048 1473 (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
1049 1474 },
1050 1475
1051 updateComplete: function(request) {
1476 updateComplete: function(response) {
1052 1477 if (this.options.decay) {
1053 this.decay = (request.responseText == this.lastText ?
1478 this.decay = (response.responseText == this.lastText ?
1054 1479 this.decay * this.options.decay : 1);
1055 1480
1056 this.lastText = request.responseText;
1481 this.lastText = response.responseText;
1057 1482 }
1058 this.timer = setTimeout(this.onTimerEvent.bind(this),
1059 this.decay * this.frequency * 1000);
1483 this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
1060 1484 },
1061 1485
1062 1486 onTimerEvent: function() {
@@ -1069,7 +1493,7 function $(element) {
1069 1493 elements.push($(arguments[i]));
1070 1494 return elements;
1071 1495 }
1072 if (typeof element == 'string')
1496 if (Object.isString(element))
1073 1497 element = document.getElementById(element);
1074 1498 return Element.extend(element);
1075 1499 }
@@ -1080,63 +1504,51 if (Prototype.BrowserFeatures.XPath) {
1080 1504 var query = document.evaluate(expression, $(parentElement) || document,
1081 1505 null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1082 1506 for (var i = 0, length = query.snapshotLength; i < length; i++)
1083 results.push(query.snapshotItem(i));
1507 results.push(Element.extend(query.snapshotItem(i)));
1084 1508 return results;
1085 1509 };
1086 1510 }
1087 1511
1088 document.getElementsByClassName = function(className, parentElement) {
1089 if (Prototype.BrowserFeatures.XPath) {
1090 var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
1091 return document._getElementsByXPath(q, parentElement);
1092 } else {
1093 var children = ($(parentElement) || document.body).getElementsByTagName('*');
1094 var elements = [], child;
1095 for (var i = 0, length = children.length; i < length; i++) {
1096 child = children[i];
1097 if (Element.hasClassName(child, className))
1098 elements.push(Element.extend(child));
1099 }
1100 return elements;
1101 }
1102 };
1103
1104 1512 /*--------------------------------------------------------------------------*/
1105 1513
1106 if (!window.Element)
1107 var Element = new Object();
1108
1109 Element.extend = function(element) {
1110 if (!element || _nativeExtensions || element.nodeType == 3) return element;
1111
1112 if (!element._extended && element.tagName && element != window) {
1113 var methods = Object.clone(Element.Methods), cache = Element.extend.cache;
1114
1115 if (element.tagName == 'FORM')
1116 Object.extend(methods, Form.Methods);
1117 if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName))
1118 Object.extend(methods, Form.Element.Methods);
1119
1120 Object.extend(methods, Element.Methods.Simulated);
1514 if (!window.Node) var Node = { };
1515
1516 if (!Node.ELEMENT_NODE) {
1517 // DOM level 2 ECMAScript Language Binding
1518 Object.extend(Node, {
1519 ELEMENT_NODE: 1,
1520 ATTRIBUTE_NODE: 2,
1521 TEXT_NODE: 3,
1522 CDATA_SECTION_NODE: 4,
1523 ENTITY_REFERENCE_NODE: 5,
1524 ENTITY_NODE: 6,
1525 PROCESSING_INSTRUCTION_NODE: 7,
1526 COMMENT_NODE: 8,
1527 DOCUMENT_NODE: 9,
1528 DOCUMENT_TYPE_NODE: 10,
1529 DOCUMENT_FRAGMENT_NODE: 11,
1530 NOTATION_NODE: 12
1531 });
1532 }
1121 1533
1122 for (var property in methods) {
1123 var value = methods[property];
1124 if (typeof value == 'function' && !(property in element))
1125 element[property] = cache.findOrStore(value);
1534 (function() {
1535 var element = this.Element;
1536 this.Element = function(tagName, attributes) {
1537 attributes = attributes || { };
1538 tagName = tagName.toLowerCase();
1539 var cache = Element.cache;
1540 if (Prototype.Browser.IE && attributes.name) {
1541 tagName = '<' + tagName + ' name="' + attributes.name + '">';
1542 delete attributes.name;
1543 return Element.writeAttribute(document.createElement(tagName), attributes);
1126 1544 }
1127 }
1128
1129 element._extended = true;
1130 return element;
1131 };
1545 if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
1546 return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
1547 };
1548 Object.extend(this.Element, element || { });
1549 }).call(window);
1132 1550
1133 Element.extend.cache = {
1134 findOrStore: function(value) {
1135 return this[value] = this[value] || function() {
1136 return value.apply(null, [this].concat($A(arguments)));
1137 }
1138 }
1139 };
1551 Element.cache = { };
1140 1552
1141 1553 Element.Methods = {
1142 1554 visible: function(element) {
@@ -1165,28 +1577,74 Element.Methods = {
1165 1577 return element;
1166 1578 },
1167 1579
1168 update: function(element, html) {
1169 html = typeof html == 'undefined' ? '' : html.toString();
1170 $(element).innerHTML = html.stripScripts();
1171 setTimeout(function() {html.evalScripts()}, 10);
1580 update: function(element, content) {
1581 element = $(element);
1582 if (content && content.toElement) content = content.toElement();
1583 if (Object.isElement(content)) return element.update().insert(content);
1584 content = Object.toHTML(content);
1585 element.innerHTML = content.stripScripts();
1586 content.evalScripts.bind(content).defer();
1172 1587 return element;
1173 1588 },
1174 1589
1175 replace: function(element, html) {
1590 replace: function(element, content) {
1176 1591 element = $(element);
1177 html = typeof html == 'undefined' ? '' : html.toString();
1178 if (element.outerHTML) {
1179 element.outerHTML = html.stripScripts();
1180 } else {
1592 if (content && content.toElement) content = content.toElement();
1593 else if (!Object.isElement(content)) {
1594 content = Object.toHTML(content);
1181 1595 var range = element.ownerDocument.createRange();
1182 range.selectNodeContents(element);
1183 element.parentNode.replaceChild(
1184 range.createContextualFragment(html.stripScripts()), element);
1596 range.selectNode(element);
1597 content.evalScripts.bind(content).defer();
1598 content = range.createContextualFragment(content.stripScripts());
1599 }
1600 element.parentNode.replaceChild(content, element);
1601 return element;
1602 },
1603
1604 insert: function(element, insertions) {
1605 element = $(element);
1606
1607 if (Object.isString(insertions) || Object.isNumber(insertions) ||
1608 Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
1609 insertions = {bottom:insertions};
1610
1611 var content, t, range;
1612
1613 for (position in insertions) {
1614 content = insertions[position];
1615 position = position.toLowerCase();
1616 t = Element._insertionTranslations[position];
1617
1618 if (content && content.toElement) content = content.toElement();
1619 if (Object.isElement(content)) {
1620 t.insert(element, content);
1621 continue;
1622 }
1623
1624 content = Object.toHTML(content);
1625
1626 range = element.ownerDocument.createRange();
1627 t.initializeRange(element, range);
1628 t.insert(element, range.createContextualFragment(content.stripScripts()));
1629
1630 content.evalScripts.bind(content).defer();
1185 1631 }
1186 setTimeout(function() {html.evalScripts()}, 10);
1632
1187 1633 return element;
1188 1634 },
1189 1635
1636 wrap: function(element, wrapper, attributes) {
1637 element = $(element);
1638 if (Object.isElement(wrapper))
1639 $(wrapper).writeAttribute(attributes || { });
1640 else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
1641 else wrapper = new Element('div', wrapper);
1642 if (element.parentNode)
1643 element.parentNode.replaceChild(wrapper, element);
1644 wrapper.appendChild(element);
1645 return wrapper;
1646 },
1647
1190 1648 inspect: function(element) {
1191 1649 element = $(element);
1192 1650 var result = '<' + element.tagName.toLowerCase();
@@ -1212,7 +1670,13 Element.Methods = {
1212 1670 },
1213 1671
1214 1672 descendants: function(element) {
1215 return $A($(element).getElementsByTagName('*'));
1673 return $(element).getElementsBySelector("*");
1674 },
1675
1676 firstDescendant: function(element) {
1677 element = $(element).firstChild;
1678 while (element && element.nodeType != 1) element = element.nextSibling;
1679 return $(element);
1216 1680 },
1217 1681
1218 1682 immediateDescendants: function(element) {
@@ -1236,48 +1700,96 Element.Methods = {
1236 1700 },
1237 1701
1238 1702 match: function(element, selector) {
1239 if (typeof selector == 'string')
1703 if (Object.isString(selector))
1240 1704 selector = new Selector(selector);
1241 1705 return selector.match($(element));
1242 1706 },
1243 1707
1244 1708 up: function(element, expression, index) {
1245 return Selector.findElement($(element).ancestors(), expression, index);
1709 element = $(element);
1710 if (arguments.length == 1) return $(element.parentNode);
1711 var ancestors = element.ancestors();
1712 return expression ? Selector.findElement(ancestors, expression, index) :
1713 ancestors[index || 0];
1246 1714 },
1247 1715
1248 1716 down: function(element, expression, index) {
1249 return Selector.findElement($(element).descendants(), expression, index);
1717 element = $(element);
1718 if (arguments.length == 1) return element.firstDescendant();
1719 var descendants = element.descendants();
1720 return expression ? Selector.findElement(descendants, expression, index) :
1721 descendants[index || 0];
1250 1722 },
1251 1723
1252 1724 previous: function(element, expression, index) {
1253 return Selector.findElement($(element).previousSiblings(), expression, index);
1725 element = $(element);
1726 if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
1727 var previousSiblings = element.previousSiblings();
1728 return expression ? Selector.findElement(previousSiblings, expression, index) :
1729 previousSiblings[index || 0];
1254 1730 },
1255 1731
1256 1732 next: function(element, expression, index) {
1257 return Selector.findElement($(element).nextSiblings(), expression, index);
1733 element = $(element);
1734 if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
1735 var nextSiblings = element.nextSiblings();
1736 return expression ? Selector.findElement(nextSiblings, expression, index) :
1737 nextSiblings[index || 0];
1258 1738 },
1259 1739
1260 getElementsBySelector: function() {
1740 select: function() {
1261 1741 var args = $A(arguments), element = $(args.shift());
1262 1742 return Selector.findChildElements(element, args);
1263 1743 },
1264 1744
1265 getElementsByClassName: function(element, className) {
1266 return document.getElementsByClassName(className, element);
1745 adjacent: function() {
1746 var args = $A(arguments), element = $(args.shift());
1747 return Selector.findChildElements(element.parentNode, args).without(element);
1748 },
1749
1750 identify: function(element) {
1751 element = $(element);
1752 var id = element.readAttribute('id'), self = arguments.callee;
1753 if (id) return id;
1754 do { id = 'anonymous_element_' + self.counter++ } while ($(id));
1755 element.writeAttribute('id', id);
1756 return id;
1267 1757 },
1268 1758
1269 1759 readAttribute: function(element, name) {
1270 1760 element = $(element);
1271 if (document.all && !window.opera) {
1272 var t = Element._attributeTranslations;
1761 if (Prototype.Browser.IE) {
1762 var t = Element._attributeTranslations.read;
1273 1763 if (t.values[name]) return t.values[name](element, name);
1274 if (t.names[name]) name = t.names[name];
1275 var attribute = element.attributes[name];
1276 if(attribute) return attribute.nodeValue;
1764 if (t.names[name]) name = t.names[name];
1765 if (name.include(':')) {
1766 return (!element.attributes || !element.attributes[name]) ? null :
1767 element.attributes[name].value;
1768 }
1277 1769 }
1278 1770 return element.getAttribute(name);
1279 1771 },
1280 1772
1773 writeAttribute: function(element, name, value) {
1774 element = $(element);
1775 var attributes = { }, t = Element._attributeTranslations.write;
1776
1777 if (typeof name == 'object') attributes = name;
1778 else attributes[name] = Object.isUndefined(value) ? true : value;
1779
1780 for (var attr in attributes) {
1781 name = t.names[attr] || attr;
1782 value = attributes[attr];
1783 if (t.values[attr]) name = t.values[attr](element, value);
1784 if (value === false || value === null)
1785 element.removeAttribute(name);
1786 else if (value === true)
1787 element.setAttribute(name, name);
1788 else element.setAttribute(name, value);
1789 }
1790 return element;
1791 },
1792
1281 1793 getHeight: function(element) {
1282 1794 return $(element).getDimensions().height;
1283 1795 },
@@ -1293,39 +1805,28 Element.Methods = {
1293 1805 hasClassName: function(element, className) {
1294 1806 if (!(element = $(element))) return;
1295 1807 var elementClassName = element.className;
1296 if (elementClassName.length == 0) return false;
1297 if (elementClassName == className ||
1298 elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
1299 return true;
1300 return false;
1808 return (elementClassName.length > 0 && (elementClassName == className ||
1809 new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
1301 1810 },
1302 1811
1303 1812 addClassName: function(element, className) {
1304 1813 if (!(element = $(element))) return;
1305 Element.classNames(element).add(className);
1814 if (!element.hasClassName(className))
1815 element.className += (element.className ? ' ' : '') + className;
1306 1816 return element;
1307 1817 },
1308 1818
1309 1819 removeClassName: function(element, className) {
1310 1820 if (!(element = $(element))) return;
1311 Element.classNames(element).remove(className);
1821 element.className = element.className.replace(
1822 new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
1312 1823 return element;
1313 1824 },
1314 1825
1315 1826 toggleClassName: function(element, className) {
1316 1827 if (!(element = $(element))) return;
1317 Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
1318 return element;
1319 },
1320
1321 observe: function() {
1322 Event.observe.apply(Event, arguments);
1323 return $A(arguments).first();
1324 },
1325
1326 stopObserving: function() {
1327 Event.stopObserving.apply(Event, arguments);
1328 return $A(arguments).first();
1828 return element[element.hasClassName(className) ?
1829 'removeClassName' : 'addClassName'](className);
1329 1830 },
1330 1831
1331 1832 // removes whitespace-only text node children
@@ -1342,74 +1843,76 Element.Methods = {
1342 1843 },
1343 1844
1344 1845 empty: function(element) {
1345 return $(element).innerHTML.match(/^\s*$/);
1846 return $(element).innerHTML.blank();
1346 1847 },
1347 1848
1348 1849 descendantOf: function(element, ancestor) {
1349 1850 element = $(element), ancestor = $(ancestor);
1851 var originalAncestor = ancestor;
1852
1853 if (element.compareDocumentPosition)
1854 return (element.compareDocumentPosition(ancestor) & 8) === 8;
1855
1856 if (element.sourceIndex && !Prototype.Browser.Opera) {
1857 var e = element.sourceIndex, a = ancestor.sourceIndex,
1858 nextAncestor = ancestor.nextSibling;
1859 if (!nextAncestor) {
1860 do { ancestor = ancestor.parentNode; }
1861 while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
1862 }
1863 if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex);
1864 }
1865
1350 1866 while (element = element.parentNode)
1351 if (element == ancestor) return true;
1867 if (element == originalAncestor) return true;
1352 1868 return false;
1353 1869 },
1354 1870
1355 1871 scrollTo: function(element) {
1356 1872 element = $(element);
1357 var pos = Position.cumulativeOffset(element);
1873 var pos = element.cumulativeOffset();
1358 1874 window.scrollTo(pos[0], pos[1]);
1359 1875 return element;
1360 1876 },
1361 1877
1362 1878 getStyle: function(element, style) {
1363 1879 element = $(element);
1364 if (['float','cssFloat'].include(style))
1365 style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat');
1366 style = style.camelize();
1880 style = style == 'float' ? 'cssFloat' : style.camelize();
1367 1881 var value = element.style[style];
1368 1882 if (!value) {
1369 if (document.defaultView && document.defaultView.getComputedStyle) {
1370 var css = document.defaultView.getComputedStyle(element, null);
1371 value = css ? css[style] : null;
1372 } else if (element.currentStyle) {
1373 value = element.currentStyle[style];
1374 }
1883 var css = document.defaultView.getComputedStyle(element, null);
1884 value = css ? css[style] : null;
1375 1885 }
1886 if (style == 'opacity') return value ? parseFloat(value) : 1.0;
1887 return value == 'auto' ? null : value;
1888 },
1376 1889
1377 if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none'))
1378 value = element['offset'+style.capitalize()] + 'px';
1890 getOpacity: function(element) {
1891 return $(element).getStyle('opacity');
1892 },
1379 1893
1380 if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
1381 if (Element.getStyle(element, 'position') == 'static') value = 'auto';
1382 if(style == 'opacity') {
1383 if(value) return parseFloat(value);
1384 if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
1385 if(value[1]) return parseFloat(value[1]) / 100;
1386 return 1.0;
1894 setStyle: function(element, styles) {
1895 element = $(element);
1896 var elementStyle = element.style, match;
1897 if (Object.isString(styles)) {
1898 element.style.cssText += ';' + styles;
1899 return styles.include('opacity') ?
1900 element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
1387 1901 }
1388 return value == 'auto' ? null : value;
1902 for (var property in styles)
1903 if (property == 'opacity') element.setOpacity(styles[property]);
1904 else
1905 elementStyle[(property == 'float' || property == 'cssFloat') ?
1906 (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
1907 property] = styles[property];
1908
1909 return element;
1389 1910 },
1390 1911
1391 setStyle: function(element, style) {
1912 setOpacity: function(element, value) {
1392 1913 element = $(element);
1393 for (var name in style) {
1394 var value = style[name];
1395 if(name == 'opacity') {
1396 if (value == 1) {
1397 value = (/Gecko/.test(navigator.userAgent) &&
1398 !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0;
1399 if(/MSIE/.test(navigator.userAgent) && !window.opera)
1400 element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
1401 } else if(value == '') {
1402 if(/MSIE/.test(navigator.userAgent) && !window.opera)
1403 element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
1404 } else {
1405 if(value < 0.00001) value = 0;
1406 if(/MSIE/.test(navigator.userAgent) && !window.opera)
1407 element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') +
1408 'alpha(opacity='+value*100+')';
1409 }
1410 } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat';
1411 element.style[name.camelize()] = value;
1412 }
1914 element.style.opacity = (value == 1 || value === '') ? '' :
1915 (value < 0.00001) ? 0 : value;
1413 1916 return element;
1414 1917 },
1415 1918
@@ -1468,8 +1971,8 Element.Methods = {
1468 1971 makeClipping: function(element) {
1469 1972 element = $(element);
1470 1973 if (element._overflow) return element;
1471 element._overflow = element.style.overflow || 'auto';
1472 if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
1974 element._overflow = Element.getStyle(element, 'overflow') || 'auto';
1975 if (element._overflow !== 'hidden')
1473 1976 element.style.overflow = 'hidden';
1474 1977 return element;
1475 1978 },
@@ -1480,393 +1983,1398 Element.Methods = {
1480 1983 element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
1481 1984 element._overflow = null;
1482 1985 return element;
1483 }
1484 };
1485
1486 Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf});
1487
1488 Element._attributeTranslations = {};
1489
1490 Element._attributeTranslations.names = {
1491 colspan: "colSpan",
1492 rowspan: "rowSpan",
1493 valign: "vAlign",
1494 datetime: "dateTime",
1495 accesskey: "accessKey",
1496 tabindex: "tabIndex",
1497 enctype: "encType",
1498 maxlength: "maxLength",
1499 readonly: "readOnly",
1500 longdesc: "longDesc"
1501 };
1502
1503 Element._attributeTranslations.values = {
1504 _getAttr: function(element, attribute) {
1505 return element.getAttribute(attribute, 2);
1506 1986 },
1507 1987
1508 _flag: function(element, attribute) {
1509 return $(element).hasAttribute(attribute) ? attribute : null;
1988 cumulativeOffset: function(element) {
1989 var valueT = 0, valueL = 0;
1990 do {
1991 valueT += element.offsetTop || 0;
1992 valueL += element.offsetLeft || 0;
1993 element = element.offsetParent;
1994 } while (element);
1995 return Element._returnOffset(valueL, valueT);
1510 1996 },
1511 1997
1512 style: function(element) {
1513 return element.style.cssText.toLowerCase();
1998 positionedOffset: function(element) {
1999 var valueT = 0, valueL = 0;
2000 do {
2001 valueT += element.offsetTop || 0;
2002 valueL += element.offsetLeft || 0;
2003 element = element.offsetParent;
2004 if (element) {
2005 if (element.tagName == 'BODY') break;
2006 var p = Element.getStyle(element, 'position');
2007 if (p == 'relative' || p == 'absolute') break;
2008 }
2009 } while (element);
2010 return Element._returnOffset(valueL, valueT);
1514 2011 },
1515 2012
1516 title: function(element) {
1517 var node = element.getAttributeNode('title');
1518 return node.specified ? node.nodeValue : null;
1519 }
1520 };
1521
1522 Object.extend(Element._attributeTranslations.values, {
1523 href: Element._attributeTranslations.values._getAttr,
1524 src: Element._attributeTranslations.values._getAttr,
1525 disabled: Element._attributeTranslations.values._flag,
1526 checked: Element._attributeTranslations.values._flag,
1527 readonly: Element._attributeTranslations.values._flag,
1528 multiple: Element._attributeTranslations.values._flag
1529 });
2013 absolutize: function(element) {
2014 element = $(element);
2015 if (element.getStyle('position') == 'absolute') return;
2016 // Position.prepare(); // To be done manually by Scripty when it needs it.
1530 2017
1531 Element.Methods.Simulated = {
1532 hasAttribute: function(element, attribute) {
1533 var t = Element._attributeTranslations;
1534 attribute = t.names[attribute] || attribute;
1535 return $(element).getAttributeNode(attribute).specified;
1536 }
1537 };
2018 var offsets = element.positionedOffset();
2019 var top = offsets[1];
2020 var left = offsets[0];
2021 var width = element.clientWidth;
2022 var height = element.clientHeight;
1538 2023
1539 // IE is missing .innerHTML support for TABLE-related elements
1540 if (document.all && !window.opera){
1541 Element.Methods.update = function(element, html) {
1542 element = $(element);
1543 html = typeof html == 'undefined' ? '' : html.toString();
1544 var tagName = element.tagName.toUpperCase();
1545 if (['THEAD','TBODY','TR','TD'].include(tagName)) {
1546 var div = document.createElement('div');
1547 switch (tagName) {
1548 case 'THEAD':
1549 case 'TBODY':
1550 div.innerHTML = '<table><tbody>' + html.stripScripts() + '</tbody></table>';
1551 depth = 2;
1552 break;
1553 case 'TR':
1554 div.innerHTML = '<table><tbody><tr>' + html.stripScripts() + '</tr></tbody></table>';
1555 depth = 3;
1556 break;
1557 case 'TD':
1558 div.innerHTML = '<table><tbody><tr><td>' + html.stripScripts() + '</td></tr></tbody></table>';
1559 depth = 4;
1560 }
1561 $A(element.childNodes).each(function(node){
1562 element.removeChild(node)
1563 });
1564 depth.times(function(){ div = div.firstChild });
2024 element._originalLeft = left - parseFloat(element.style.left || 0);
2025 element._originalTop = top - parseFloat(element.style.top || 0);
2026 element._originalWidth = element.style.width;
2027 element._originalHeight = element.style.height;
1565 2028
1566 $A(div.childNodes).each(
1567 function(node){ element.appendChild(node) });
1568 } else {
1569 element.innerHTML = html.stripScripts();
1570 }
1571 setTimeout(function() {html.evalScripts()}, 10);
2029 element.style.position = 'absolute';
2030 element.style.top = top + 'px';
2031 element.style.left = left + 'px';
2032 element.style.width = width + 'px';
2033 element.style.height = height + 'px';
1572 2034 return element;
1573 }
1574 };
2035 },
1575 2036
1576 Object.extend(Element, Element.Methods);
2037 relativize: function(element) {
2038 element = $(element);
2039 if (element.getStyle('position') == 'relative') return;
2040 // Position.prepare(); // To be done manually by Scripty when it needs it.
1577 2041
1578 var _nativeExtensions = false;
2042 element.style.position = 'relative';
2043 var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
2044 var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
1579 2045
1580 if(/Konqueror|Safari|KHTML/.test(navigator.userAgent))
1581 ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) {
1582 var className = 'HTML' + tag + 'Element';
1583 if(window[className]) return;
1584 var klass = window[className] = {};
1585 klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__;
1586 });
2046 element.style.top = top + 'px';
2047 element.style.left = left + 'px';
2048 element.style.height = element._originalHeight;
2049 element.style.width = element._originalWidth;
2050 return element;
2051 },
1587 2052
1588 Element.addMethods = function(methods) {
1589 Object.extend(Element.Methods, methods || {});
2053 cumulativeScrollOffset: function(element) {
2054 var valueT = 0, valueL = 0;
2055 do {
2056 valueT += element.scrollTop || 0;
2057 valueL += element.scrollLeft || 0;
2058 element = element.parentNode;
2059 } while (element);
2060 return Element._returnOffset(valueL, valueT);
2061 },
1590 2062
1591 function copy(methods, destination, onlyIfAbsent) {
1592 onlyIfAbsent = onlyIfAbsent || false;
1593 var cache = Element.extend.cache;
1594 for (var property in methods) {
1595 var value = methods[property];
1596 if (!onlyIfAbsent || !(property in destination))
1597 destination[property] = cache.findOrStore(value);
1598 }
1599 }
2063 getOffsetParent: function(element) {
2064 if (element.offsetParent) return $(element.offsetParent);
2065 if (element == document.body) return $(element);
1600 2066
1601 if (typeof HTMLElement != 'undefined') {
1602 copy(Element.Methods, HTMLElement.prototype);
1603 copy(Element.Methods.Simulated, HTMLElement.prototype, true);
1604 copy(Form.Methods, HTMLFormElement.prototype);
1605 [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) {
1606 copy(Form.Element.Methods, klass.prototype);
1607 });
1608 _nativeExtensions = true;
1609 }
1610 }
2067 while ((element = element.parentNode) && element != document.body)
2068 if (Element.getStyle(element, 'position') != 'static')
2069 return $(element);
1611 2070
1612 var Toggle = new Object();
1613 Toggle.display = Element.toggle;
2071 return $(document.body);
2072 },
1614 2073
1615 /*--------------------------------------------------------------------------*/
2074 viewportOffset: function(forElement) {
2075 var valueT = 0, valueL = 0;
1616 2076
1617 Abstract.Insertion = function(adjacency) {
1618 this.adjacency = adjacency;
1619 }
2077 var element = forElement;
2078 do {
2079 valueT += element.offsetTop || 0;
2080 valueL += element.offsetLeft || 0;
1620 2081
1621 Abstract.Insertion.prototype = {
1622 initialize: function(element, content) {
1623 this.element = $(element);
1624 this.content = content.stripScripts();
2082 // Safari fix
2083 if (element.offsetParent == document.body &&
2084 Element.getStyle(element, 'position') == 'absolute') break;
1625 2085
1626 if (this.adjacency && this.element.insertAdjacentHTML) {
1627 try {
1628 this.element.insertAdjacentHTML(this.adjacency, this.content);
1629 } catch (e) {
1630 var tagName = this.element.tagName.toUpperCase();
1631 if (['TBODY', 'TR'].include(tagName)) {
1632 this.insertContent(this.contentFromAnonymousTable()._reverse());
1633 } else {
1634 throw e;
1635 }
2086 } while (element = element.offsetParent);
2087
2088 element = forElement;
2089 do {
2090 if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
2091 valueT -= element.scrollTop || 0;
2092 valueL -= element.scrollLeft || 0;
1636 2093 }
1637 } else {
1638 this.range = this.element.ownerDocument.createRange();
1639 if (this.initializeRange) this.initializeRange();
1640 this.insertContent([this.range.createContextualFragment(this.content)]);
1641 }
2094 } while (element = element.parentNode);
1642 2095
1643 setTimeout(function() {content.evalScripts()}, 10);
2096 return Element._returnOffset(valueL, valueT);
1644 2097 },
1645 2098
1646 contentFromAnonymousTable: function() {
1647 var div = document.createElement('div');
1648 div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
1649 return $A(div.childNodes[0].childNodes[0].childNodes);
1650 }
1651 }
2099 clonePosition: function(element, source) {
2100 var options = Object.extend({
2101 setLeft: true,
2102 setTop: true,
2103 setWidth: true,
2104 setHeight: true,
2105 offsetTop: 0,
2106 offsetLeft: 0
2107 }, arguments[2] || { });
1652 2108
1653 var Insertion = new Object();
2109 // find page position of source
2110 source = $(source);
2111 var p = source.viewportOffset();
1654 2112
1655 Insertion.Before = Class.create();
1656 Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
1657 initializeRange: function() {
1658 this.range.setStartBefore(this.element);
1659 },
2113 // find coordinate system to use
2114 element = $(element);
2115 var delta = [0, 0];
2116 var parent = null;
2117 // delta [0,0] will do fine with position: fixed elements,
2118 // position:absolute needs offsetParent deltas
2119 if (Element.getStyle(element, 'position') == 'absolute') {
2120 parent = element.getOffsetParent();
2121 delta = parent.viewportOffset();
2122 }
1660 2123
1661 insertContent: function(fragments) {
1662 fragments.each((function(fragment) {
1663 this.element.parentNode.insertBefore(fragment, this.element);
1664 }).bind(this));
2124 // correct by body offsets (fixes Safari)
2125 if (parent == document.body) {
2126 delta[0] -= document.body.offsetLeft;
2127 delta[1] -= document.body.offsetTop;
2128 }
2129
2130 // set position
2131 if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
2132 if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
2133 if (options.setWidth) element.style.width = source.offsetWidth + 'px';
2134 if (options.setHeight) element.style.height = source.offsetHeight + 'px';
2135 return element;
1665 2136 }
2137 };
2138
2139 Element.Methods.identify.counter = 1;
2140
2141 Object.extend(Element.Methods, {
2142 getElementsBySelector: Element.Methods.select,
2143 childElements: Element.Methods.immediateDescendants
1666 2144 });
1667 2145
1668 Insertion.Top = Class.create();
1669 Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
1670 initializeRange: function() {
1671 this.range.selectNodeContents(this.element);
1672 this.range.collapse(true);
2146 Element._attributeTranslations = {
2147 write: {
2148 names: {
2149 className: 'class',
2150 htmlFor: 'for'
2151 },
2152 values: { }
2153 }
2154 };
2155
2156
2157 if (!document.createRange || Prototype.Browser.Opera) {
2158 Element.Methods.insert = function(element, insertions) {
2159 element = $(element);
2160
2161 if (Object.isString(insertions) || Object.isNumber(insertions) ||
2162 Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
2163 insertions = { bottom: insertions };
2164
2165 var t = Element._insertionTranslations, content, position, pos, tagName;
2166
2167 for (position in insertions) {
2168 content = insertions[position];
2169 position = position.toLowerCase();
2170 pos = t[position];
2171
2172 if (content && content.toElement) content = content.toElement();
2173 if (Object.isElement(content)) {
2174 pos.insert(element, content);
2175 continue;
2176 }
2177
2178 content = Object.toHTML(content);
2179 tagName = ((position == 'before' || position == 'after')
2180 ? element.parentNode : element).tagName.toUpperCase();
2181
2182 if (t.tags[tagName]) {
2183 var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
2184 if (position == 'top' || position == 'after') fragments.reverse();
2185 fragments.each(pos.insert.curry(element));
2186 }
2187 else element.insertAdjacentHTML(pos.adjacency, content.stripScripts());
2188
2189 content.evalScripts.bind(content).defer();
2190 }
2191
2192 return element;
2193 };
2194 }
2195
2196 if (Prototype.Browser.Opera) {
2197 Element.Methods.getStyle = Element.Methods.getStyle.wrap(
2198 function(proceed, element, style) {
2199 switch (style) {
2200 case 'left': case 'top': case 'right': case 'bottom':
2201 if (proceed(element, 'position') === 'static') return null;
2202 case 'height': case 'width':
2203 // returns '0px' for hidden elements; we want it to return null
2204 if (!Element.visible(element)) return null;
2205
2206 // returns the border-box dimensions rather than the content-box
2207 // dimensions, so we subtract padding and borders from the value
2208 var dim = parseInt(proceed(element, style), 10);
2209
2210 if (dim !== element['offset' + style.capitalize()])
2211 return dim + 'px';
2212
2213 var properties;
2214 if (style === 'height') {
2215 properties = ['border-top-width', 'padding-top',
2216 'padding-bottom', 'border-bottom-width'];
2217 }
2218 else {
2219 properties = ['border-left-width', 'padding-left',
2220 'padding-right', 'border-right-width'];
2221 }
2222 return properties.inject(dim, function(memo, property) {
2223 var val = proceed(element, property);
2224 return val === null ? memo : memo - parseInt(val, 10);
2225 }) + 'px';
2226 default: return proceed(element, style);
2227 }
2228 }
2229 );
2230
2231 Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
2232 function(proceed, element, attribute) {
2233 if (attribute === 'title') return element.title;
2234 return proceed(element, attribute);
2235 }
2236 );
2237 }
2238
2239 else if (Prototype.Browser.IE) {
2240 $w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
2241 Element.Methods[method] = Element.Methods[method].wrap(
2242 function(proceed, element) {
2243 element = $(element);
2244 var position = element.getStyle('position');
2245 if (position != 'static') return proceed(element);
2246 element.setStyle({ position: 'relative' });
2247 var value = proceed(element);
2248 element.setStyle({ position: position });
2249 return value;
2250 }
2251 );
2252 });
2253
2254 Element.Methods.getStyle = function(element, style) {
2255 element = $(element);
2256 style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
2257 var value = element.style[style];
2258 if (!value && element.currentStyle) value = element.currentStyle[style];
2259
2260 if (style == 'opacity') {
2261 if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
2262 if (value[1]) return parseFloat(value[1]) / 100;
2263 return 1.0;
2264 }
2265
2266 if (value == 'auto') {
2267 if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
2268 return element['offset' + style.capitalize()] + 'px';
2269 return null;
2270 }
2271 return value;
2272 };
2273
2274 Element.Methods.setOpacity = function(element, value) {
2275 function stripAlpha(filter){
2276 return filter.replace(/alpha\([^\)]*\)/gi,'');
2277 }
2278 element = $(element);
2279 var currentStyle = element.currentStyle;
2280 if ((currentStyle && !currentStyle.hasLayout) ||
2281 (!currentStyle && element.style.zoom == 'normal'))
2282 element.style.zoom = 1;
2283
2284 var filter = element.getStyle('filter'), style = element.style;
2285 if (value == 1 || value === '') {
2286 (filter = stripAlpha(filter)) ?
2287 style.filter = filter : style.removeAttribute('filter');
2288 return element;
2289 } else if (value < 0.00001) value = 0;
2290 style.filter = stripAlpha(filter) +
2291 'alpha(opacity=' + (value * 100) + ')';
2292 return element;
2293 };
2294
2295 Element._attributeTranslations = {
2296 read: {
2297 names: {
2298 'class': 'className',
2299 'for': 'htmlFor'
2300 },
2301 values: {
2302 _getAttr: function(element, attribute) {
2303 return element.getAttribute(attribute, 2);
2304 },
2305 _getAttrNode: function(element, attribute) {
2306 var node = element.getAttributeNode(attribute);
2307 return node ? node.value : "";
2308 },
2309 _getEv: function(element, attribute) {
2310 attribute = element.getAttribute(attribute);
2311 return attribute ? attribute.toString().slice(23, -2) : null;
2312 },
2313 _flag: function(element, attribute) {
2314 return $(element).hasAttribute(attribute) ? attribute : null;
2315 },
2316 style: function(element) {
2317 return element.style.cssText.toLowerCase();
2318 },
2319 title: function(element) {
2320 return element.title;
2321 }
2322 }
2323 }
2324 };
2325
2326 Element._attributeTranslations.write = {
2327 names: Object.clone(Element._attributeTranslations.read.names),
2328 values: {
2329 checked: function(element, value) {
2330 element.checked = !!value;
2331 },
2332
2333 style: function(element, value) {
2334 element.style.cssText = value ? value : '';
2335 }
2336 }
2337 };
2338
2339 Element._attributeTranslations.has = {};
2340
2341 $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
2342 'encType maxLength readOnly longDesc').each(function(attr) {
2343 Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
2344 Element._attributeTranslations.has[attr.toLowerCase()] = attr;
2345 });
2346
2347 (function(v) {
2348 Object.extend(v, {
2349 href: v._getAttr,
2350 src: v._getAttr,
2351 type: v._getAttr,
2352 action: v._getAttrNode,
2353 disabled: v._flag,
2354 checked: v._flag,
2355 readonly: v._flag,
2356 multiple: v._flag,
2357 onload: v._getEv,
2358 onunload: v._getEv,
2359 onclick: v._getEv,
2360 ondblclick: v._getEv,
2361 onmousedown: v._getEv,
2362 onmouseup: v._getEv,
2363 onmouseover: v._getEv,
2364 onmousemove: v._getEv,
2365 onmouseout: v._getEv,
2366 onfocus: v._getEv,
2367 onblur: v._getEv,
2368 onkeypress: v._getEv,
2369 onkeydown: v._getEv,
2370 onkeyup: v._getEv,
2371 onsubmit: v._getEv,
2372 onreset: v._getEv,
2373 onselect: v._getEv,
2374 onchange: v._getEv
2375 });
2376 })(Element._attributeTranslations.read.values);
2377 }
2378
2379 else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
2380 Element.Methods.setOpacity = function(element, value) {
2381 element = $(element);
2382 element.style.opacity = (value == 1) ? 0.999999 :
2383 (value === '') ? '' : (value < 0.00001) ? 0 : value;
2384 return element;
2385 };
2386 }
2387
2388 else if (Prototype.Browser.WebKit) {
2389 Element.Methods.setOpacity = function(element, value) {
2390 element = $(element);
2391 element.style.opacity = (value == 1 || value === '') ? '' :
2392 (value < 0.00001) ? 0 : value;
2393
2394 if (value == 1)
2395 if(element.tagName == 'IMG' && element.width) {
2396 element.width++; element.width--;
2397 } else try {
2398 var n = document.createTextNode(' ');
2399 element.appendChild(n);
2400 element.removeChild(n);
2401 } catch (e) { }
2402
2403 return element;
2404 };
2405
2406 // Safari returns margins on body which is incorrect if the child is absolutely
2407 // positioned. For performance reasons, redefine Element#cumulativeOffset for
2408 // KHTML/WebKit only.
2409 Element.Methods.cumulativeOffset = function(element) {
2410 var valueT = 0, valueL = 0;
2411 do {
2412 valueT += element.offsetTop || 0;
2413 valueL += element.offsetLeft || 0;
2414 if (element.offsetParent == document.body)
2415 if (Element.getStyle(element, 'position') == 'absolute') break;
2416
2417 element = element.offsetParent;
2418 } while (element);
2419
2420 return Element._returnOffset(valueL, valueT);
2421 };
2422 }
2423
2424 if (Prototype.Browser.IE || Prototype.Browser.Opera) {
2425 // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
2426 Element.Methods.update = function(element, content) {
2427 element = $(element);
2428
2429 if (content && content.toElement) content = content.toElement();
2430 if (Object.isElement(content)) return element.update().insert(content);
2431
2432 content = Object.toHTML(content);
2433 var tagName = element.tagName.toUpperCase();
2434
2435 if (tagName in Element._insertionTranslations.tags) {
2436 $A(element.childNodes).each(function(node) { element.removeChild(node) });
2437 Element._getContentFromAnonymousElement(tagName, content.stripScripts())
2438 .each(function(node) { element.appendChild(node) });
2439 }
2440 else element.innerHTML = content.stripScripts();
2441
2442 content.evalScripts.bind(content).defer();
2443 return element;
2444 };
2445 }
2446
2447 if (document.createElement('div').outerHTML) {
2448 Element.Methods.replace = function(element, content) {
2449 element = $(element);
2450
2451 if (content && content.toElement) content = content.toElement();
2452 if (Object.isElement(content)) {
2453 element.parentNode.replaceChild(content, element);
2454 return element;
2455 }
2456
2457 content = Object.toHTML(content);
2458 var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
2459
2460 if (Element._insertionTranslations.tags[tagName]) {
2461 var nextSibling = element.next();
2462 var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
2463 parent.removeChild(element);
2464 if (nextSibling)
2465 fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
2466 else
2467 fragments.each(function(node) { parent.appendChild(node) });
2468 }
2469 else element.outerHTML = content.stripScripts();
2470
2471 content.evalScripts.bind(content).defer();
2472 return element;
2473 };
2474 }
2475
2476 Element._returnOffset = function(l, t) {
2477 var result = [l, t];
2478 result.left = l;
2479 result.top = t;
2480 return result;
2481 };
2482
2483 Element._getContentFromAnonymousElement = function(tagName, html) {
2484 var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
2485 div.innerHTML = t[0] + html + t[1];
2486 t[2].times(function() { div = div.firstChild });
2487 return $A(div.childNodes);
2488 };
2489
2490 Element._insertionTranslations = {
2491 before: {
2492 adjacency: 'beforeBegin',
2493 insert: function(element, node) {
2494 element.parentNode.insertBefore(node, element);
2495 },
2496 initializeRange: function(element, range) {
2497 range.setStartBefore(element);
2498 }
2499 },
2500 top: {
2501 adjacency: 'afterBegin',
2502 insert: function(element, node) {
2503 element.insertBefore(node, element.firstChild);
2504 },
2505 initializeRange: function(element, range) {
2506 range.selectNodeContents(element);
2507 range.collapse(true);
2508 }
2509 },
2510 bottom: {
2511 adjacency: 'beforeEnd',
2512 insert: function(element, node) {
2513 element.appendChild(node);
2514 }
2515 },
2516 after: {
2517 adjacency: 'afterEnd',
2518 insert: function(element, node) {
2519 element.parentNode.insertBefore(node, element.nextSibling);
2520 },
2521 initializeRange: function(element, range) {
2522 range.setStartAfter(element);
2523 }
1673 2524 },
2525 tags: {
2526 TABLE: ['<table>', '</table>', 1],
2527 TBODY: ['<table><tbody>', '</tbody></table>', 2],
2528 TR: ['<table><tbody><tr>', '</tr></tbody></table>', 3],
2529 TD: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
2530 SELECT: ['<select>', '</select>', 1]
2531 }
2532 };
2533
2534 (function() {
2535 this.bottom.initializeRange = this.top.initializeRange;
2536 Object.extend(this.tags, {
2537 THEAD: this.tags.TBODY,
2538 TFOOT: this.tags.TBODY,
2539 TH: this.tags.TD
2540 });
2541 }).call(Element._insertionTranslations);
1674 2542
1675 insertContent: function(fragments) {
1676 fragments.reverse(false).each((function(fragment) {
1677 this.element.insertBefore(fragment, this.element.firstChild);
1678 }).bind(this));
2543 Element.Methods.Simulated = {
2544 hasAttribute: function(element, attribute) {
2545 attribute = Element._attributeTranslations.has[attribute] || attribute;
2546 var node = $(element).getAttributeNode(attribute);
2547 return node && node.specified;
1679 2548 }
1680 });
2549 };
1681 2550
1682 Insertion.Bottom = Class.create();
1683 Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
1684 initializeRange: function() {
1685 this.range.selectNodeContents(this.element);
1686 this.range.collapse(this.element);
1687 },
2551 Element.Methods.ByTag = { };
2552
2553 Object.extend(Element, Element.Methods);
2554
2555 if (!Prototype.BrowserFeatures.ElementExtensions &&
2556 document.createElement('div').__proto__) {
2557 window.HTMLElement = { };
2558 window.HTMLElement.prototype = document.createElement('div').__proto__;
2559 Prototype.BrowserFeatures.ElementExtensions = true;
2560 }
2561
2562 Element.extend = (function() {
2563 if (Prototype.BrowserFeatures.SpecificElementExtensions)
2564 return Prototype.K;
2565
2566 var Methods = { }, ByTag = Element.Methods.ByTag;
1688 2567
1689 insertContent: function(fragments) {
1690 fragments.each((function(fragment) {
1691 this.element.appendChild(fragment);
1692 }).bind(this));
2568 var extend = Object.extend(function(element) {
2569 if (!element || element._extendedByPrototype ||
2570 element.nodeType != 1 || element == window) return element;
2571
2572 var methods = Object.clone(Methods),
2573 tagName = element.tagName, property, value;
2574
2575 // extend methods for specific tags
2576 if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
2577
2578 for (property in methods) {
2579 value = methods[property];
2580 if (Object.isFunction(value) && !(property in element))
2581 element[property] = value.methodize();
2582 }
2583
2584 element._extendedByPrototype = Prototype.emptyFunction;
2585 return element;
2586
2587 }, {
2588 refresh: function() {
2589 // extend methods for all tags (Safari doesn't need this)
2590 if (!Prototype.BrowserFeatures.ElementExtensions) {
2591 Object.extend(Methods, Element.Methods);
2592 Object.extend(Methods, Element.Methods.Simulated);
2593 }
2594 }
2595 });
2596
2597 extend.refresh();
2598 return extend;
2599 })();
2600
2601 Element.hasAttribute = function(element, attribute) {
2602 if (element.hasAttribute) return element.hasAttribute(attribute);
2603 return Element.Methods.Simulated.hasAttribute(element, attribute);
2604 };
2605
2606 Element.addMethods = function(methods) {
2607 var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
2608
2609 if (!methods) {
2610 Object.extend(Form, Form.Methods);
2611 Object.extend(Form.Element, Form.Element.Methods);
2612 Object.extend(Element.Methods.ByTag, {
2613 "FORM": Object.clone(Form.Methods),
2614 "INPUT": Object.clone(Form.Element.Methods),
2615 "SELECT": Object.clone(Form.Element.Methods),
2616 "TEXTAREA": Object.clone(Form.Element.Methods)
2617 });
1693 2618 }
1694 });
1695 2619
1696 Insertion.After = Class.create();
1697 Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
1698 initializeRange: function() {
1699 this.range.setStartAfter(this.element);
1700 },
2620 if (arguments.length == 2) {
2621 var tagName = methods;
2622 methods = arguments[1];
2623 }
1701 2624
1702 insertContent: function(fragments) {
1703 fragments.each((function(fragment) {
1704 this.element.parentNode.insertBefore(fragment,
1705 this.element.nextSibling);
1706 }).bind(this));
2625 if (!tagName) Object.extend(Element.Methods, methods || { });
2626 else {
2627 if (Object.isArray(tagName)) tagName.each(extend);
2628 else extend(tagName);
1707 2629 }
1708 });
1709 2630
1710 /*--------------------------------------------------------------------------*/
2631 function extend(tagName) {
2632 tagName = tagName.toUpperCase();
2633 if (!Element.Methods.ByTag[tagName])
2634 Element.Methods.ByTag[tagName] = { };
2635 Object.extend(Element.Methods.ByTag[tagName], methods);
2636 }
1711 2637
1712 Element.ClassNames = Class.create();
1713 Element.ClassNames.prototype = {
1714 initialize: function(element) {
1715 this.element = $(element);
1716 },
2638 function copy(methods, destination, onlyIfAbsent) {
2639 onlyIfAbsent = onlyIfAbsent || false;
2640 for (var property in methods) {
2641 var value = methods[property];
2642 if (!Object.isFunction(value)) continue;
2643 if (!onlyIfAbsent || !(property in destination))
2644 destination[property] = value.methodize();
2645 }
2646 }
1717 2647
1718 _each: function(iterator) {
1719 this.element.className.split(/\s+/).select(function(name) {
1720 return name.length > 0;
1721 })._each(iterator);
1722 },
2648 function findDOMClass(tagName) {
2649 var klass;
2650 var trans = {
2651 "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
2652 "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
2653 "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
2654 "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
2655 "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
2656 "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
2657 "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
2658 "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
2659 "FrameSet", "IFRAME": "IFrame"
2660 };
2661 if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
2662 if (window[klass]) return window[klass];
2663 klass = 'HTML' + tagName + 'Element';
2664 if (window[klass]) return window[klass];
2665 klass = 'HTML' + tagName.capitalize() + 'Element';
2666 if (window[klass]) return window[klass];
2667
2668 window[klass] = { };
2669 window[klass].prototype = document.createElement(tagName).__proto__;
2670 return window[klass];
2671 }
1723 2672
1724 set: function(className) {
1725 this.element.className = className;
2673 if (F.ElementExtensions) {
2674 copy(Element.Methods, HTMLElement.prototype);
2675 copy(Element.Methods.Simulated, HTMLElement.prototype, true);
2676 }
2677
2678 if (F.SpecificElementExtensions) {
2679 for (var tag in Element.Methods.ByTag) {
2680 var klass = findDOMClass(tag);
2681 if (Object.isUndefined(klass)) continue;
2682 copy(T[tag], klass.prototype);
2683 }
2684 }
2685
2686 Object.extend(Element, Element.Methods);
2687 delete Element.ByTag;
2688
2689 if (Element.extend.refresh) Element.extend.refresh();
2690 Element.cache = { };
2691 };
2692
2693 document.viewport = {
2694 getDimensions: function() {
2695 var dimensions = { };
2696 var B = Prototype.Browser;
2697 $w('width height').each(function(d) {
2698 var D = d.capitalize();
2699 dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :
2700 (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];
2701 });
2702 return dimensions;
1726 2703 },
1727 2704
1728 add: function(classNameToAdd) {
1729 if (this.include(classNameToAdd)) return;
1730 this.set($A(this).concat(classNameToAdd).join(' '));
2705 getWidth: function() {
2706 return this.getDimensions().width;
1731 2707 },
1732 2708
1733 remove: function(classNameToRemove) {
1734 if (!this.include(classNameToRemove)) return;
1735 this.set($A(this).without(classNameToRemove).join(' '));
2709 getHeight: function() {
2710 return this.getDimensions().height;
1736 2711 },
1737 2712
1738 toString: function() {
1739 return $A(this).join(' ');
2713 getScrollOffsets: function() {
2714 return Element._returnOffset(
2715 window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
2716 window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
1740 2717 }
1741 2718 };
2719 /* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
2720 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
2721 * license. Please see http://www.yui-ext.com/ for more information. */
1742 2722
1743 Object.extend(Element.ClassNames.prototype, Enumerable);
1744 var Selector = Class.create();
1745 Selector.prototype = {
2723 var Selector = Class.create({
1746 2724 initialize: function(expression) {
1747 this.params = {classNames: []};
1748 this.expression = expression.toString().strip();
1749 this.parseExpression();
2725 this.expression = expression.strip();
1750 2726 this.compileMatcher();
1751 2727 },
1752 2728
1753 parseExpression: function() {
1754 function abort(message) { throw 'Parse error in selector: ' + message; }
2729 shouldUseXPath: function() {
2730 if (!Prototype.BrowserFeatures.XPath) return false;
1755 2731
1756 if (this.expression == '') abort('empty expression');
2732 var e = this.expression;
1757 2733
1758 var params = this.params, expr = this.expression, match, modifier, clause, rest;
1759 while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
1760 params.attributes = params.attributes || [];
1761 params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
1762 expr = match[1];
1763 }
2734 // Safari 3 chokes on :*-of-type and :empty
2735 if (Prototype.Browser.WebKit &&
2736 (e.include("-of-type") || e.include(":empty")))
2737 return false;
1764 2738
1765 if (expr == '*') return this.params.wildcard = true;
2739 // XPath can't do namespaced attributes, nor can it read
2740 // the "checked" property from DOM nodes
2741 if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
2742 return false;
2743
2744 return true;
2745 },
2746
2747 compileMatcher: function() {
2748 if (this.shouldUseXPath())
2749 return this.compileXPathMatcher();
2750
2751 var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
2752 c = Selector.criteria, le, p, m;
2753
2754 if (Selector._cache[e]) {
2755 this.matcher = Selector._cache[e];
2756 return;
2757 }
1766 2758
1767 while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
1768 modifier = match[1], clause = match[2], rest = match[3];
1769 switch (modifier) {
1770 case '#': params.id = clause; break;
1771 case '.': params.classNames.push(clause); break;
1772 case '':
1773 case undefined: params.tagName = clause.toUpperCase(); break;
1774 default: abort(expr.inspect());
2759 this.matcher = ["this.matcher = function(root) {",
2760 "var r = root, h = Selector.handlers, c = false, n;"];
2761
2762 while (e && le != e && (/\S/).test(e)) {
2763 le = e;
2764 for (var i in ps) {
2765 p = ps[i];
2766 if (m = e.match(p)) {
2767 this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
2768 new Template(c[i]).evaluate(m));
2769 e = e.replace(m[0], '');
2770 break;
2771 }
1775 2772 }
1776 expr = rest;
1777 2773 }
1778 2774
1779 if (expr.length > 0) abort(expr.inspect());
2775 this.matcher.push("return h.unique(n);\n}");
2776 eval(this.matcher.join('\n'));
2777 Selector._cache[this.expression] = this.matcher;
1780 2778 },
1781 2779
1782 buildMatchExpression: function() {
1783 var params = this.params, conditions = [], clause;
2780 compileXPathMatcher: function() {
2781 var e = this.expression, ps = Selector.patterns,
2782 x = Selector.xpath, le, m;
1784 2783
1785 if (params.wildcard)
1786 conditions.push('true');
1787 if (clause = params.id)
1788 conditions.push('element.readAttribute("id") == ' + clause.inspect());
1789 if (clause = params.tagName)
1790 conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
1791 if ((clause = params.classNames).length > 0)
1792 for (var i = 0, length = clause.length; i < length; i++)
1793 conditions.push('element.hasClassName(' + clause[i].inspect() + ')');
1794 if (clause = params.attributes) {
1795 clause.each(function(attribute) {
1796 var value = 'element.readAttribute(' + attribute.name.inspect() + ')';
1797 var splitValueBy = function(delimiter) {
1798 return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
1799 }
2784 if (Selector._cache[e]) {
2785 this.xpath = Selector._cache[e]; return;
2786 }
1800 2787
1801 switch (attribute.operator) {
1802 case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break;
1803 case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
1804 case '|=': conditions.push(
1805 splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
1806 ); break;
1807 case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break;
1808 case '':
1809 case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break;
1810 default: throw 'Unknown operator ' + attribute.operator + ' in selector';
2788 this.matcher = ['.//*'];
2789 while (e && le != e && (/\S/).test(e)) {
2790 le = e;
2791 for (var i in ps) {
2792 if (m = e.match(ps[i])) {
2793 this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
2794 new Template(x[i]).evaluate(m));
2795 e = e.replace(m[0], '');
2796 break;
1811 2797 }
1812 });
2798 }
1813 2799 }
1814 2800
1815 return conditions.join(' && ');
2801 this.xpath = this.matcher.join('');
2802 Selector._cache[this.expression] = this.xpath;
1816 2803 },
1817 2804
1818 compileMatcher: function() {
1819 this.match = new Function('element', 'if (!element.tagName) return false; \
1820 element = $(element); \
1821 return ' + this.buildMatchExpression());
2805 findElements: function(root) {
2806 root = root || document;
2807 if (this.xpath) return document._getElementsByXPath(this.xpath, root);
2808 return this.matcher(root);
1822 2809 },
1823 2810
1824 findElements: function(scope) {
1825 var element;
2811 match: function(element) {
2812 this.tokens = [];
1826 2813
1827 if (element = $(this.params.id))
1828 if (this.match(element))
1829 if (!scope || Element.childOf(element, scope))
1830 return [element];
2814 var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
2815 var le, p, m;
1831 2816
1832 scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
2817 while (e && le !== e && (/\S/).test(e)) {
2818 le = e;
2819 for (var i in ps) {
2820 p = ps[i];
2821 if (m = e.match(p)) {
2822 // use the Selector.assertions methods unless the selector
2823 // is too complex.
2824 if (as[i]) {
2825 this.tokens.push([i, Object.clone(m)]);
2826 e = e.replace(m[0], '');
2827 } else {
2828 // reluctantly do a document-wide search
2829 // and look for a match in the array
2830 return this.findElements(document).include(element);
2831 }
2832 }
2833 }
2834 }
1833 2835
1834 var results = [];
1835 for (var i = 0, length = scope.length; i < length; i++)
1836 if (this.match(element = scope[i]))
1837 results.push(Element.extend(element));
2836 var match = true, name, matches;
2837 for (var i = 0, token; token = this.tokens[i]; i++) {
2838 name = token[0], matches = token[1];
2839 if (!Selector.assertions[name](element, matches)) {
2840 match = false; break;
2841 }
2842 }
1838 2843
1839 return results;
2844 return match;
1840 2845 },
1841 2846
1842 2847 toString: function() {
1843 2848 return this.expression;
2849 },
2850
2851 inspect: function() {
2852 return "#<Selector:" + this.expression.inspect() + ">";
1844 2853 }
1845 }
2854 });
1846 2855
1847 2856 Object.extend(Selector, {
2857 _cache: { },
2858
2859 xpath: {
2860 descendant: "//*",
2861 child: "/*",
2862 adjacent: "/following-sibling::*[1]",
2863 laterSibling: '/following-sibling::*',
2864 tagName: function(m) {
2865 if (m[1] == '*') return '';
2866 return "[local-name()='" + m[1].toLowerCase() +
2867 "' or local-name()='" + m[1].toUpperCase() + "']";
2868 },
2869 className: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
2870 id: "[@id='#{1}']",
2871 attrPresence: function(m) {
2872 m[1] = m[1].toLowerCase();
2873 return new Template("[@#{1}]").evaluate(m);
2874 },
2875 attr: function(m) {
2876 m[1] = m[1].toLowerCase();
2877 m[3] = m[5] || m[6];
2878 return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
2879 },
2880 pseudo: function(m) {
2881 var h = Selector.xpath.pseudos[m[1]];
2882 if (!h) return '';
2883 if (Object.isFunction(h)) return h(m);
2884 return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
2885 },
2886 operators: {
2887 '=': "[@#{1}='#{3}']",
2888 '!=': "[@#{1}!='#{3}']",
2889 '^=': "[starts-with(@#{1}, '#{3}')]",
2890 '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
2891 '*=': "[contains(@#{1}, '#{3}')]",
2892 '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
2893 '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
2894 },
2895 pseudos: {
2896 'first-child': '[not(preceding-sibling::*)]',
2897 'last-child': '[not(following-sibling::*)]',
2898 'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
2899 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
2900 'checked': "[@checked]",
2901 'disabled': "[@disabled]",
2902 'enabled': "[not(@disabled)]",
2903 'not': function(m) {
2904 var e = m[6], p = Selector.patterns,
2905 x = Selector.xpath, le, v;
2906
2907 var exclusion = [];
2908 while (e && le != e && (/\S/).test(e)) {
2909 le = e;
2910 for (var i in p) {
2911 if (m = e.match(p[i])) {
2912 v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
2913 exclusion.push("(" + v.substring(1, v.length - 1) + ")");
2914 e = e.replace(m[0], '');
2915 break;
2916 }
2917 }
2918 }
2919 return "[not(" + exclusion.join(" and ") + ")]";
2920 },
2921 'nth-child': function(m) {
2922 return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
2923 },
2924 'nth-last-child': function(m) {
2925 return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
2926 },
2927 'nth-of-type': function(m) {
2928 return Selector.xpath.pseudos.nth("position() ", m);
2929 },
2930 'nth-last-of-type': function(m) {
2931 return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
2932 },
2933 'first-of-type': function(m) {
2934 m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
2935 },
2936 'last-of-type': function(m) {
2937 m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
2938 },
2939 'only-of-type': function(m) {
2940 var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
2941 },
2942 nth: function(fragment, m) {
2943 var mm, formula = m[6], predicate;
2944 if (formula == 'even') formula = '2n+0';
2945 if (formula == 'odd') formula = '2n+1';
2946 if (mm = formula.match(/^(\d+)$/)) // digit only
2947 return '[' + fragment + "= " + mm[1] + ']';
2948 if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
2949 if (mm[1] == "-") mm[1] = -1;
2950 var a = mm[1] ? Number(mm[1]) : 1;
2951 var b = mm[2] ? Number(mm[2]) : 0;
2952 predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
2953 "((#{fragment} - #{b}) div #{a} >= 0)]";
2954 return new Template(predicate).evaluate({
2955 fragment: fragment, a: a, b: b });
2956 }
2957 }
2958 }
2959 },
2960
2961 criteria: {
2962 tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
2963 className: 'n = h.className(n, r, "#{1}", c); c = false;',
2964 id: 'n = h.id(n, r, "#{1}", c); c = false;',
2965 attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
2966 attr: function(m) {
2967 m[3] = (m[5] || m[6]);
2968 return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
2969 },
2970 pseudo: function(m) {
2971 if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
2972 return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
2973 },
2974 descendant: 'c = "descendant";',
2975 child: 'c = "child";',
2976 adjacent: 'c = "adjacent";',
2977 laterSibling: 'c = "laterSibling";'
2978 },
2979
2980 patterns: {
2981 // combinators must be listed first
2982 // (and descendant needs to be last combinator)
2983 laterSibling: /^\s*~\s*/,
2984 child: /^\s*>\s*/,
2985 adjacent: /^\s*\+\s*/,
2986 descendant: /^\s/,
2987
2988 // selectors follow
2989 tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
2990 id: /^#([\w\-\*]+)(\b|$)/,
2991 className: /^\.([\w\-\*]+)(\b|$)/,
2992 pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/,
2993 attrPresence: /^\[([\w]+)\]/,
2994 attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
2995 },
2996
2997 // for Selector.match and Element#match
2998 assertions: {
2999 tagName: function(element, matches) {
3000 return matches[1].toUpperCase() == element.tagName.toUpperCase();
3001 },
3002
3003 className: function(element, matches) {
3004 return Element.hasClassName(element, matches[1]);
3005 },
3006
3007 id: function(element, matches) {
3008 return element.id === matches[1];
3009 },
3010
3011 attrPresence: function(element, matches) {
3012 return Element.hasAttribute(element, matches[1]);
3013 },
3014
3015 attr: function(element, matches) {
3016 var nodeValue = Element.readAttribute(element, matches[1]);
3017 return Selector.operators[matches[2]](nodeValue, matches[3]);
3018 }
3019 },
3020
3021 handlers: {
3022 // UTILITY FUNCTIONS
3023 // joins two collections
3024 concat: function(a, b) {
3025 for (var i = 0, node; node = b[i]; i++)
3026 a.push(node);
3027 return a;
3028 },
3029
3030 // marks an array of nodes for counting
3031 mark: function(nodes) {
3032 for (var i = 0, node; node = nodes[i]; i++)
3033 node._counted = true;
3034 return nodes;
3035 },
3036
3037 unmark: function(nodes) {
3038 for (var i = 0, node; node = nodes[i]; i++)
3039 node._counted = undefined;
3040 return nodes;
3041 },
3042
3043 // mark each child node with its position (for nth calls)
3044 // "ofType" flag indicates whether we're indexing for nth-of-type
3045 // rather than nth-child
3046 index: function(parentNode, reverse, ofType) {
3047 parentNode._counted = true;
3048 if (reverse) {
3049 for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
3050 var node = nodes[i];
3051 if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
3052 }
3053 } else {
3054 for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
3055 if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
3056 }
3057 },
3058
3059 // filters out duplicates and extends all nodes
3060 unique: function(nodes) {
3061 if (nodes.length == 0) return nodes;
3062 var results = [], n;
3063 for (var i = 0, l = nodes.length; i < l; i++)
3064 if (!(n = nodes[i])._counted) {
3065 n._counted = true;
3066 results.push(Element.extend(n));
3067 }
3068 return Selector.handlers.unmark(results);
3069 },
3070
3071 // COMBINATOR FUNCTIONS
3072 descendant: function(nodes) {
3073 var h = Selector.handlers;
3074 for (var i = 0, results = [], node; node = nodes[i]; i++)
3075 h.concat(results, node.getElementsByTagName('*'));
3076 return results;
3077 },
3078
3079 child: function(nodes) {
3080 var h = Selector.handlers;
3081 for (var i = 0, results = [], node; node = nodes[i]; i++) {
3082 for (var j = 0, child; child = node.childNodes[j]; j++)
3083 if (child.nodeType == 1 && child.tagName != '!') results.push(child);
3084 }
3085 return results;
3086 },
3087
3088 adjacent: function(nodes) {
3089 for (var i = 0, results = [], node; node = nodes[i]; i++) {
3090 var next = this.nextElementSibling(node);
3091 if (next) results.push(next);
3092 }
3093 return results;
3094 },
3095
3096 laterSibling: function(nodes) {
3097 var h = Selector.handlers;
3098 for (var i = 0, results = [], node; node = nodes[i]; i++)
3099 h.concat(results, Element.nextSiblings(node));
3100 return results;
3101 },
3102
3103 nextElementSibling: function(node) {
3104 while (node = node.nextSibling)
3105 if (node.nodeType == 1) return node;
3106 return null;
3107 },
3108
3109 previousElementSibling: function(node) {
3110 while (node = node.previousSibling)
3111 if (node.nodeType == 1) return node;
3112 return null;
3113 },
3114
3115 // TOKEN FUNCTIONS
3116 tagName: function(nodes, root, tagName, combinator) {
3117 tagName = tagName.toUpperCase();
3118 var results = [], h = Selector.handlers;
3119 if (nodes) {
3120 if (combinator) {
3121 // fastlane for ordinary descendant combinators
3122 if (combinator == "descendant") {
3123 for (var i = 0, node; node = nodes[i]; i++)
3124 h.concat(results, node.getElementsByTagName(tagName));
3125 return results;
3126 } else nodes = this[combinator](nodes);
3127 if (tagName == "*") return nodes;
3128 }
3129 for (var i = 0, node; node = nodes[i]; i++)
3130 if (node.tagName.toUpperCase() == tagName) results.push(node);
3131 return results;
3132 } else return root.getElementsByTagName(tagName);
3133 },
3134
3135 id: function(nodes, root, id, combinator) {
3136 var targetNode = $(id), h = Selector.handlers;
3137 if (!targetNode) return [];
3138 if (!nodes && root == document) return [targetNode];
3139 if (nodes) {
3140 if (combinator) {
3141 if (combinator == 'child') {
3142 for (var i = 0, node; node = nodes[i]; i++)
3143 if (targetNode.parentNode == node) return [targetNode];
3144 } else if (combinator == 'descendant') {
3145 for (var i = 0, node; node = nodes[i]; i++)
3146 if (Element.descendantOf(targetNode, node)) return [targetNode];
3147 } else if (combinator == 'adjacent') {
3148 for (var i = 0, node; node = nodes[i]; i++)
3149 if (Selector.handlers.previousElementSibling(targetNode) == node)
3150 return [targetNode];
3151 } else nodes = h[combinator](nodes);
3152 }
3153 for (var i = 0, node; node = nodes[i]; i++)
3154 if (node == targetNode) return [targetNode];
3155 return [];
3156 }
3157 return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
3158 },
3159
3160 className: function(nodes, root, className, combinator) {
3161 if (nodes && combinator) nodes = this[combinator](nodes);
3162 return Selector.handlers.byClassName(nodes, root, className);
3163 },
3164
3165 byClassName: function(nodes, root, className) {
3166 if (!nodes) nodes = Selector.handlers.descendant([root]);
3167 var needle = ' ' + className + ' ';
3168 for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
3169 nodeClassName = node.className;
3170 if (nodeClassName.length == 0) continue;
3171 if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
3172 results.push(node);
3173 }
3174 return results;
3175 },
3176
3177 attrPresence: function(nodes, root, attr) {
3178 if (!nodes) nodes = root.getElementsByTagName("*");
3179 var results = [];
3180 for (var i = 0, node; node = nodes[i]; i++)
3181 if (Element.hasAttribute(node, attr)) results.push(node);
3182 return results;
3183 },
3184
3185 attr: function(nodes, root, attr, value, operator) {
3186 if (!nodes) nodes = root.getElementsByTagName("*");
3187 var handler = Selector.operators[operator], results = [];
3188 for (var i = 0, node; node = nodes[i]; i++) {
3189 var nodeValue = Element.readAttribute(node, attr);
3190 if (nodeValue === null) continue;
3191 if (handler(nodeValue, value)) results.push(node);
3192 }
3193 return results;
3194 },
3195
3196 pseudo: function(nodes, name, value, root, combinator) {
3197 if (nodes && combinator) nodes = this[combinator](nodes);
3198 if (!nodes) nodes = root.getElementsByTagName("*");
3199 return Selector.pseudos[name](nodes, value, root);
3200 }
3201 },
3202
3203 pseudos: {
3204 'first-child': function(nodes, value, root) {
3205 for (var i = 0, results = [], node; node = nodes[i]; i++) {
3206 if (Selector.handlers.previousElementSibling(node)) continue;
3207 results.push(node);
3208 }
3209 return results;
3210 },
3211 'last-child': function(nodes, value, root) {
3212 for (var i = 0, results = [], node; node = nodes[i]; i++) {
3213 if (Selector.handlers.nextElementSibling(node)) continue;
3214 results.push(node);
3215 }
3216 return results;
3217 },
3218 'only-child': function(nodes, value, root) {
3219 var h = Selector.handlers;
3220 for (var i = 0, results = [], node; node = nodes[i]; i++)
3221 if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
3222 results.push(node);
3223 return results;
3224 },
3225 'nth-child': function(nodes, formula, root) {
3226 return Selector.pseudos.nth(nodes, formula, root);
3227 },
3228 'nth-last-child': function(nodes, formula, root) {
3229 return Selector.pseudos.nth(nodes, formula, root, true);
3230 },
3231 'nth-of-type': function(nodes, formula, root) {
3232 return Selector.pseudos.nth(nodes, formula, root, false, true);
3233 },
3234 'nth-last-of-type': function(nodes, formula, root) {
3235 return Selector.pseudos.nth(nodes, formula, root, true, true);
3236 },
3237 'first-of-type': function(nodes, formula, root) {
3238 return Selector.pseudos.nth(nodes, "1", root, false, true);
3239 },
3240 'last-of-type': function(nodes, formula, root) {
3241 return Selector.pseudos.nth(nodes, "1", root, true, true);
3242 },
3243 'only-of-type': function(nodes, formula, root) {
3244 var p = Selector.pseudos;
3245 return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
3246 },
3247
3248 // handles the an+b logic
3249 getIndices: function(a, b, total) {
3250 if (a == 0) return b > 0 ? [b] : [];
3251 return $R(1, total).inject([], function(memo, i) {
3252 if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
3253 return memo;
3254 });
3255 },
3256
3257 // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
3258 nth: function(nodes, formula, root, reverse, ofType) {
3259 if (nodes.length == 0) return [];
3260 if (formula == 'even') formula = '2n+0';
3261 if (formula == 'odd') formula = '2n+1';
3262 var h = Selector.handlers, results = [], indexed = [], m;
3263 h.mark(nodes);
3264 for (var i = 0, node; node = nodes[i]; i++) {
3265 if (!node.parentNode._counted) {
3266 h.index(node.parentNode, reverse, ofType);
3267 indexed.push(node.parentNode);
3268 }
3269 }
3270 if (formula.match(/^\d+$/)) { // just a number
3271 formula = Number(formula);
3272 for (var i = 0, node; node = nodes[i]; i++)
3273 if (node.nodeIndex == formula) results.push(node);
3274 } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
3275 if (m[1] == "-") m[1] = -1;
3276 var a = m[1] ? Number(m[1]) : 1;
3277 var b = m[2] ? Number(m[2]) : 0;
3278 var indices = Selector.pseudos.getIndices(a, b, nodes.length);
3279 for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
3280 for (var j = 0; j < l; j++)
3281 if (node.nodeIndex == indices[j]) results.push(node);
3282 }
3283 }
3284 h.unmark(nodes);
3285 h.unmark(indexed);
3286 return results;
3287 },
3288
3289 'empty': function(nodes, value, root) {
3290 for (var i = 0, results = [], node; node = nodes[i]; i++) {
3291 // IE treats comments as element nodes
3292 if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
3293 results.push(node);
3294 }
3295 return results;
3296 },
3297
3298 'not': function(nodes, selector, root) {
3299 var h = Selector.handlers, selectorType, m;
3300 var exclusions = new Selector(selector).findElements(root);
3301 h.mark(exclusions);
3302 for (var i = 0, results = [], node; node = nodes[i]; i++)
3303 if (!node._counted) results.push(node);
3304 h.unmark(exclusions);
3305 return results;
3306 },
3307
3308 'enabled': function(nodes, value, root) {
3309 for (var i = 0, results = [], node; node = nodes[i]; i++)
3310 if (!node.disabled) results.push(node);
3311 return results;
3312 },
3313
3314 'disabled': function(nodes, value, root) {
3315 for (var i = 0, results = [], node; node = nodes[i]; i++)
3316 if (node.disabled) results.push(node);
3317 return results;
3318 },
3319
3320 'checked': function(nodes, value, root) {
3321 for (var i = 0, results = [], node; node = nodes[i]; i++)
3322 if (node.checked) results.push(node);
3323 return results;
3324 }
3325 },
3326
3327 operators: {
3328 '=': function(nv, v) { return nv == v; },
3329 '!=': function(nv, v) { return nv != v; },
3330 '^=': function(nv, v) { return nv.startsWith(v); },
3331 '$=': function(nv, v) { return nv.endsWith(v); },
3332 '*=': function(nv, v) { return nv.include(v); },
3333 '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
3334 '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
3335 },
3336
1848 3337 matchElements: function(elements, expression) {
1849 var selector = new Selector(expression);
1850 return elements.select(selector.match.bind(selector)).map(Element.extend);
3338 var matches = new Selector(expression).findElements(), h = Selector.handlers;
3339 h.mark(matches);
3340 for (var i = 0, results = [], element; element = elements[i]; i++)
3341 if (element._counted) results.push(element);
3342 h.unmark(matches);
3343 return results;
1851 3344 },
1852 3345
1853 3346 findElement: function(elements, expression, index) {
1854 if (typeof expression == 'number') index = expression, expression = false;
3347 if (Object.isNumber(expression)) {
3348 index = expression; expression = false;
3349 }
1855 3350 return Selector.matchElements(elements, expression || '*')[index || 0];
1856 3351 },
1857 3352
1858 3353 findChildElements: function(element, expressions) {
1859 return expressions.map(function(expression) {
1860 return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) {
1861 var selector = new Selector(expr);
1862 return results.inject([], function(elements, result) {
1863 return elements.concat(selector.findElements(result || element));
1864 });
1865 });
1866 }).flatten();
3354 var exprs = expressions.join(',');
3355 expressions = [];
3356 exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
3357 expressions.push(m[1].strip());
3358 });
3359 var results = [], h = Selector.handlers;
3360 for (var i = 0, l = expressions.length, selector; i < l; i++) {
3361 selector = new Selector(expressions[i].strip());
3362 h.concat(results, selector.findElements(element));
3363 }
3364 return (l > 1) ? h.unique(results) : results;
1867 3365 }
1868 3366 });
1869 3367
3368 if (Prototype.Browser.IE) {
3369 // IE returns comment nodes on getElementsByTagName("*").
3370 // Filter them out.
3371 Selector.handlers.concat = function(a, b) {
3372 for (var i = 0, node; node = b[i]; i++)
3373 if (node.tagName !== "!") a.push(node);
3374 return a;
3375 };
3376 }
3377
1870 3378 function $$() {
1871 3379 return Selector.findChildElements(document, $A(arguments));
1872 3380 }
@@ -1876,13 +3384,19 var Form = {
1876 3384 return form;
1877 3385 },
1878 3386
1879 serializeElements: function(elements, getHash) {
1880 var data = elements.inject({}, function(result, element) {
3387 serializeElements: function(elements, options) {
3388 if (typeof options != 'object') options = { hash: !!options };
3389 else if (Object.isUndefined(options.hash)) options.hash = true;
3390 var key, value, submitted = false, submit = options.submit;
3391
3392 var data = elements.inject({ }, function(result, element) {
1881 3393 if (!element.disabled && element.name) {
1882 var key = element.name, value = $(element).getValue();
1883 if (value != undefined) {
1884 if (result[key]) {
1885 if (result[key].constructor != Array) result[key] = [result[key]];
3394 key = element.name; value = $(element).getValue();
3395 if (value != null && (element.type != 'submit' || (!submitted &&
3396 submit !== false && (!submit || key == submit) && (submitted = true)))) {
3397 if (key in result) {
3398 // a key is already present; construct an array of values
3399 if (!Object.isArray(result[key])) result[key] = [result[key]];
1886 3400 result[key].push(value);
1887 3401 }
1888 3402 else result[key] = value;
@@ -1891,13 +3405,13 var Form = {
1891 3405 return result;
1892 3406 });
1893 3407
1894 return getHash ? data : Hash.toQueryString(data);
3408 return options.hash ? data : Object.toQueryString(data);
1895 3409 }
1896 3410 };
1897 3411
1898 3412 Form.Methods = {
1899 serialize: function(form, getHash) {
1900 return Form.serializeElements(Form.getElements(form), getHash);
3413 serialize: function(form, options) {
3414 return Form.serializeElements(Form.getElements(form), options);
1901 3415 },
1902 3416
1903 3417 getElements: function(form) {
@@ -1928,25 +3442,26 Form.Methods = {
1928 3442
1929 3443 disable: function(form) {
1930 3444 form = $(form);
1931 form.getElements().each(function(element) {
1932 element.blur();
1933 element.disabled = 'true';
1934 });
3445 Form.getElements(form).invoke('disable');
1935 3446 return form;
1936 3447 },
1937 3448
1938 3449 enable: function(form) {
1939 3450 form = $(form);
1940 form.getElements().each(function(element) {
1941 element.disabled = '';
1942 });
3451 Form.getElements(form).invoke('enable');
1943 3452 return form;
1944 3453 },
1945 3454
1946 3455 findFirstElement: function(form) {
1947 return $(form).getElements().find(function(element) {
1948 return element.type != 'hidden' && !element.disabled &&
1949 ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
3456 var elements = $(form).getElements().findAll(function(element) {
3457 return 'hidden' != element.type && !element.disabled;
3458 });
3459 var firstByIndex = elements.findAll(function(element) {
3460 return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
3461 }).sortBy(function(element) { return element.tabIndex }).first();
3462
3463 return firstByIndex ? firstByIndex : elements.find(function(element) {
3464 return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
1950 3465 });
1951 3466 },
1952 3467
@@ -1954,10 +3469,26 Form.Methods = {
1954 3469 form = $(form);
1955 3470 form.findFirstElement().activate();
1956 3471 return form;
1957 }
1958 }
3472 },
3473
3474 request: function(form, options) {
3475 form = $(form), options = Object.clone(options || { });
1959 3476
1960 Object.extend(Form, Form.Methods);
3477 var params = options.parameters, action = form.readAttribute('action') || '';
3478 if (action.blank()) action = window.location.href;
3479 options.parameters = form.serialize(true);
3480
3481 if (params) {
3482 if (Object.isString(params)) params = params.toQueryParams();
3483 Object.extend(options.parameters, params);
3484 }
3485
3486 if (form.hasAttribute('method') && !options.method)
3487 options.method = form.method;
3488
3489 return new Ajax.Request(action, options);
3490 }
3491 };
1961 3492
1962 3493 /*--------------------------------------------------------------------------*/
1963 3494
@@ -1971,7 +3502,7 Form.Element = {
1971 3502 $(element).select();
1972 3503 return element;
1973 3504 }
1974 }
3505 };
1975 3506
1976 3507 Form.Element.Methods = {
1977 3508 serialize: function(element) {
@@ -1979,9 +3510,9 Form.Element.Methods = {
1979 3510 if (!element.disabled && element.name) {
1980 3511 var value = element.getValue();
1981 3512 if (value != undefined) {
1982 var pair = {};
3513 var pair = { };
1983 3514 pair[element.name] = value;
1984 return Hash.toQueryString(pair);
3515 return Object.toQueryString(pair);
1985 3516 }
1986 3517 }
1987 3518 return '';
@@ -1993,6 +3524,13 Form.Element.Methods = {
1993 3524 return Form.Element.Serializers[method](element);
1994 3525 },
1995 3526
3527 setValue: function(element, value) {
3528 element = $(element);
3529 var method = element.tagName.toLowerCase();
3530 Form.Element.Serializers[method](element, value);
3531 return element;
3532 },
3533
1996 3534 clear: function(element) {
1997 3535 $(element).value = '';
1998 3536 return element;
@@ -2004,55 +3542,75 Form.Element.Methods = {
2004 3542
2005 3543 activate: function(element) {
2006 3544 element = $(element);
2007 element.focus();
2008 if (element.select && ( element.tagName.toLowerCase() != 'input' ||
2009 !['button', 'reset', 'submit'].include(element.type) ) )
2010 element.select();
3545 try {
3546 element.focus();
3547 if (element.select && (element.tagName.toLowerCase() != 'input' ||
3548 !['button', 'reset', 'submit'].include(element.type)))
3549 element.select();
3550 } catch (e) { }
2011 3551 return element;
2012 3552 },
2013 3553
2014 3554 disable: function(element) {
2015 3555 element = $(element);
3556 element.blur();
2016 3557 element.disabled = true;
2017 3558 return element;
2018 3559 },
2019 3560
2020 3561 enable: function(element) {
2021 3562 element = $(element);
2022 element.blur();
2023 3563 element.disabled = false;
2024 3564 return element;
2025 3565 }
2026 }
3566 };
3567
3568 /*--------------------------------------------------------------------------*/
2027 3569
2028 Object.extend(Form.Element, Form.Element.Methods);
2029 3570 var Field = Form.Element;
2030 var $F = Form.Element.getValue;
3571 var $F = Form.Element.Methods.getValue;
2031 3572
2032 3573 /*--------------------------------------------------------------------------*/
2033 3574
2034 3575 Form.Element.Serializers = {
2035 input: function(element) {
3576 input: function(element, value) {
2036 3577 switch (element.type.toLowerCase()) {
2037 3578 case 'checkbox':
2038 3579 case 'radio':
2039 return Form.Element.Serializers.inputSelector(element);
3580 return Form.Element.Serializers.inputSelector(element, value);
2040 3581 default:
2041 return Form.Element.Serializers.textarea(element);
3582 return Form.Element.Serializers.textarea(element, value);
2042 3583 }
2043 3584 },
2044 3585
2045 inputSelector: function(element) {
2046 return element.checked ? element.value : null;
3586 inputSelector: function(element, value) {
3587 if (Object.isUndefined(value)) return element.checked ? element.value : null;
3588 else element.checked = !!value;
2047 3589 },
2048 3590
2049 textarea: function(element) {
2050 return element.value;
3591 textarea: function(element, value) {
3592 if (Object.isUndefined(value)) return element.value;
3593 else element.value = value;
2051 3594 },
2052 3595
2053 select: function(element) {
2054 return this[element.type == 'select-one' ?
2055 'selectOne' : 'selectMany'](element);
3596 select: function(element, index) {
3597 if (Object.isUndefined(index))
3598 return this[element.type == 'select-one' ?
3599 'selectOne' : 'selectMany'](element);
3600 else {
3601 var opt, value, single = !Object.isArray(index);
3602 for (var i = 0, length = element.length; i < length; i++) {
3603 opt = element.options[i];
3604 value = this.optionValue(opt);
3605 if (single) {
3606 if (value == index) {
3607 opt.selected = true;
3608 return;
3609 }
3610 }
3611 else opt.selected = index.include(value);
3612 }
3613 }
2056 3614 },
2057 3615
2058 3616 selectOne: function(element) {
@@ -2075,219 +3633,438 Form.Element.Serializers = {
2075 3633 // extend element because hasAttribute may not be native
2076 3634 return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
2077 3635 }
2078 }
3636 };
2079 3637
2080 3638 /*--------------------------------------------------------------------------*/
2081 3639
2082 Abstract.TimedObserver = function() {}
2083 Abstract.TimedObserver.prototype = {
2084 initialize: function(element, frequency, callback) {
2085 this.frequency = frequency;
3640 Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
3641 initialize: function($super, element, frequency, callback) {
3642 $super(callback, frequency);
2086 3643 this.element = $(element);
2087 this.callback = callback;
2088
2089 3644 this.lastValue = this.getValue();
2090 this.registerCallback();
2091 3645 },
2092 3646
2093 registerCallback: function() {
2094 setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
3647 execute: function() {
3648 var value = this.getValue();
3649 if (Object.isString(this.lastValue) && Object.isString(value) ?
3650 this.lastValue != value : String(this.lastValue) != String(value)) {
3651 this.callback(this.element, value);
3652 this.lastValue = value;
3653 }
3654 }
3655 });
3656
3657 Form.Element.Observer = Class.create(Abstract.TimedObserver, {
3658 getValue: function() {
3659 return Form.Element.getValue(this.element);
3660 }
3661 });
3662
3663 Form.Observer = Class.create(Abstract.TimedObserver, {
3664 getValue: function() {
3665 return Form.serialize(this.element);
3666 }
3667 });
3668
3669 /*--------------------------------------------------------------------------*/
3670
3671 Abstract.EventObserver = Class.create({
3672 initialize: function(element, callback) {
3673 this.element = $(element);
3674 this.callback = callback;
3675
3676 this.lastValue = this.getValue();
3677 if (this.element.tagName.toLowerCase() == 'form')
3678 this.registerFormCallbacks();
3679 else
3680 this.registerCallback(this.element);
2095 3681 },
2096 3682
2097 onTimerEvent: function() {
3683 onElementEvent: function() {
2098 3684 var value = this.getValue();
2099 var changed = ('string' == typeof this.lastValue && 'string' == typeof value
2100 ? this.lastValue != value : String(this.lastValue) != String(value));
2101 if (changed) {
3685 if (this.lastValue != value) {
2102 3686 this.callback(this.element, value);
2103 3687 this.lastValue = value;
2104 3688 }
3689 },
3690
3691 registerFormCallbacks: function() {
3692 Form.getElements(this.element).each(this.registerCallback, this);
3693 },
3694
3695 registerCallback: function(element) {
3696 if (element.type) {
3697 switch (element.type.toLowerCase()) {
3698 case 'checkbox':
3699 case 'radio':
3700 Event.observe(element, 'click', this.onElementEvent.bind(this));
3701 break;
3702 default:
3703 Event.observe(element, 'change', this.onElementEvent.bind(this));
3704 break;
3705 }
3706 }
2105 3707 }
2106 }
3708 });
3709
3710 Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
3711 getValue: function() {
3712 return Form.Element.getValue(this.element);
3713 }
3714 });
3715
3716 Form.EventObserver = Class.create(Abstract.EventObserver, {
3717 getValue: function() {
3718 return Form.serialize(this.element);
3719 }
3720 });
3721 if (!window.Event) var Event = { };
3722
3723 Object.extend(Event, {
3724 KEY_BACKSPACE: 8,
3725 KEY_TAB: 9,
3726 KEY_RETURN: 13,
3727 KEY_ESC: 27,
3728 KEY_LEFT: 37,
3729 KEY_UP: 38,
3730 KEY_RIGHT: 39,
3731 KEY_DOWN: 40,
3732 KEY_DELETE: 46,
3733 KEY_HOME: 36,
3734 KEY_END: 35,
3735 KEY_PAGEUP: 33,
3736 KEY_PAGEDOWN: 34,
3737 KEY_INSERT: 45,
3738
3739 cache: { },
3740
3741 relatedTarget: function(event) {
3742 var element;
3743 switch(event.type) {
3744 case 'mouseover': element = event.fromElement; break;
3745 case 'mouseout': element = event.toElement; break;
3746 default: return null;
3747 }
3748 return Element.extend(element);
3749 }
3750 });
3751
3752 Event.Methods = (function() {
3753 var isButton;
3754
3755 if (Prototype.Browser.IE) {
3756 var buttonMap = { 0: 1, 1: 4, 2: 2 };
3757 isButton = function(event, code) {
3758 return event.button == buttonMap[code];
3759 };
3760
3761 } else if (Prototype.Browser.WebKit) {
3762 isButton = function(event, code) {
3763 switch (code) {
3764 case 0: return event.which == 1 && !event.metaKey;
3765 case 1: return event.which == 1 && event.metaKey;
3766 default: return false;
3767 }
3768 };
3769
3770 } else {
3771 isButton = function(event, code) {
3772 return event.which ? (event.which === code + 1) : (event.button === code);
3773 };
3774 }
3775
3776 return {
3777 isLeftClick: function(event) { return isButton(event, 0) },
3778 isMiddleClick: function(event) { return isButton(event, 1) },
3779 isRightClick: function(event) { return isButton(event, 2) },
3780
3781 element: function(event) {
3782 var node = Event.extend(event).target;
3783 return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
3784 },
3785
3786 findElement: function(event, expression) {
3787 var element = Event.element(event);
3788 if (!expression) return element;
3789 var elements = [element].concat(element.ancestors());
3790 return Selector.findElement(elements, expression, 0);
3791 },
3792
3793 pointer: function(event) {
3794 return {
3795 x: event.pageX || (event.clientX +
3796 (document.documentElement.scrollLeft || document.body.scrollLeft)),
3797 y: event.pageY || (event.clientY +
3798 (document.documentElement.scrollTop || document.body.scrollTop))
3799 };
3800 },
3801
3802 pointerX: function(event) { return Event.pointer(event).x },
3803 pointerY: function(event) { return Event.pointer(event).y },
3804
3805 stop: function(event) {
3806 Event.extend(event);
3807 event.preventDefault();
3808 event.stopPropagation();
3809 event.stopped = true;
3810 }
3811 };
3812 })();
3813
3814 Event.extend = (function() {
3815 var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
3816 m[name] = Event.Methods[name].methodize();
3817 return m;
3818 });
3819
3820 if (Prototype.Browser.IE) {
3821 Object.extend(methods, {
3822 stopPropagation: function() { this.cancelBubble = true },
3823 preventDefault: function() { this.returnValue = false },
3824 inspect: function() { return "[object Event]" }
3825 });
3826
3827 return function(event) {
3828 if (!event) return false;
3829 if (event._extendedByPrototype) return event;
3830
3831 event._extendedByPrototype = Prototype.emptyFunction;
3832 var pointer = Event.pointer(event);
3833 Object.extend(event, {
3834 target: event.srcElement,
3835 relatedTarget: Event.relatedTarget(event),
3836 pageX: pointer.x,
3837 pageY: pointer.y
3838 });
3839 return Object.extend(event, methods);
3840 };
3841
3842 } else {
3843 Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
3844 Object.extend(Event.prototype, methods);
3845 return Prototype.K;
3846 }
3847 })();
3848
3849 Object.extend(Event, (function() {
3850 var cache = Event.cache;
3851
3852 function getEventID(element) {
3853 if (element._eventID) return element._eventID;
3854 arguments.callee.id = arguments.callee.id || 1;
3855 return element._eventID = ++arguments.callee.id;
3856 }
3857
3858 function getDOMEventName(eventName) {
3859 if (eventName && eventName.include(':')) return "dataavailable";
3860 return eventName;
3861 }
3862
3863 function getCacheForID(id) {
3864 return cache[id] = cache[id] || { };
3865 }
3866
3867 function getWrappersForEventName(id, eventName) {
3868 var c = getCacheForID(id);
3869 return c[eventName] = c[eventName] || [];
3870 }
3871
3872 function createWrapper(element, eventName, handler) {
3873 var id = getEventID(element);
3874 var c = getWrappersForEventName(id, eventName);
3875 if (c.pluck("handler").include(handler)) return false;
3876
3877 var wrapper = function(event) {
3878 if (!Event || !Event.extend ||
3879 (event.eventName && event.eventName != eventName))
3880 return false;
3881
3882 Event.extend(event);
3883 handler.call(element, event)
3884 };
3885
3886 wrapper.handler = handler;
3887 c.push(wrapper);
3888 return wrapper;
3889 }
3890
3891 function findWrapper(id, eventName, handler) {
3892 var c = getWrappersForEventName(id, eventName);
3893 return c.find(function(wrapper) { return wrapper.handler == handler });
3894 }
3895
3896 function destroyWrapper(id, eventName, handler) {
3897 var c = getCacheForID(id);
3898 if (!c[eventName]) return false;
3899 c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
3900 }
3901
3902 function destroyCache() {
3903 for (var id in cache)
3904 for (var eventName in cache[id])
3905 cache[id][eventName] = null;
3906 }
3907
3908 if (window.attachEvent) {
3909 window.attachEvent("onunload", destroyCache);
3910 }
3911
3912 return {
3913 observe: function(element, eventName, handler) {
3914 element = $(element);
3915 var name = getDOMEventName(eventName);
3916
3917 var wrapper = createWrapper(element, eventName, handler);
3918 if (!wrapper) return element;
3919
3920 if (element.addEventListener) {
3921 element.addEventListener(name, wrapper, false);
3922 } else {
3923 element.attachEvent("on" + name, wrapper);
3924 }
3925
3926 return element;
3927 },
3928
3929 stopObserving: function(element, eventName, handler) {
3930 element = $(element);
3931 var id = getEventID(element), name = getDOMEventName(eventName);
3932
3933 if (!handler && eventName) {
3934 getWrappersForEventName(id, eventName).each(function(wrapper) {
3935 element.stopObserving(eventName, wrapper.handler);
3936 });
3937 return element;
2107 3938
2108 Form.Element.Observer = Class.create();
2109 Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
2110 getValue: function() {
2111 return Form.Element.getValue(this.element);
2112 }
2113 });
3939 } else if (!eventName) {
3940 Object.keys(getCacheForID(id)).each(function(eventName) {
3941 element.stopObserving(eventName);
3942 });
3943 return element;
3944 }
2114 3945
2115 Form.Observer = Class.create();
2116 Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
2117 getValue: function() {
2118 return Form.serialize(this.element);
2119 }
2120 });
3946 var wrapper = findWrapper(id, eventName, handler);
3947 if (!wrapper) return element;
2121 3948
2122 /*--------------------------------------------------------------------------*/
3949 if (element.removeEventListener) {
3950 element.removeEventListener(name, wrapper, false);
3951 } else {
3952 element.detachEvent("on" + name, wrapper);
3953 }
2123 3954
2124 Abstract.EventObserver = function() {}
2125 Abstract.EventObserver.prototype = {
2126 initialize: function(element, callback) {
2127 this.element = $(element);
2128 this.callback = callback;
3955 destroyWrapper(id, eventName, handler);
2129 3956
2130 this.lastValue = this.getValue();
2131 if (this.element.tagName.toLowerCase() == 'form')
2132 this.registerFormCallbacks();
2133 else
2134 this.registerCallback(this.element);
2135 },
3957 return element;
3958 },
2136 3959
2137 onElementEvent: function() {
2138 var value = this.getValue();
2139 if (this.lastValue != value) {
2140 this.callback(this.element, value);
2141 this.lastValue = value;
2142 }
2143 },
3960 fire: function(element, eventName, memo) {
3961 element = $(element);
3962 if (element == document && document.createEvent && !element.dispatchEvent)
3963 element = document.documentElement;
2144 3964
2145 registerFormCallbacks: function() {
2146 Form.getElements(this.element).each(this.registerCallback.bind(this));
2147 },
3965 if (document.createEvent) {
3966 var event = document.createEvent("HTMLEvents");
3967 event.initEvent("dataavailable", true, true);
3968 } else {
3969 var event = document.createEventObject();
3970 event.eventType = "ondataavailable";
3971 }
2148 3972
2149 registerCallback: function(element) {
2150 if (element.type) {
2151 switch (element.type.toLowerCase()) {
2152 case 'checkbox':
2153 case 'radio':
2154 Event.observe(element, 'click', this.onElementEvent.bind(this));
2155 break;
2156 default:
2157 Event.observe(element, 'change', this.onElementEvent.bind(this));
2158 break;
3973 event.eventName = eventName;
3974 event.memo = memo || { };
3975
3976 if (document.createEvent) {
3977 element.dispatchEvent(event);
3978 } else {
3979 element.fireEvent(event.eventType, event);
2159 3980 }
3981
3982 return Event.extend(event);
2160 3983 }
2161 }
2162 }
3984 };
3985 })());
2163 3986
2164 Form.Element.EventObserver = Class.create();
2165 Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
2166 getValue: function() {
2167 return Form.Element.getValue(this.element);
2168 }
3987 Object.extend(Event, Event.Methods);
3988
3989 Element.addMethods({
3990 fire: Event.fire,
3991 observe: Event.observe,
3992 stopObserving: Event.stopObserving
2169 3993 });
2170 3994
2171 Form.EventObserver = Class.create();
2172 Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
2173 getValue: function() {
2174 return Form.serialize(this.element);
2175 }
3995 Object.extend(document, {
3996 fire: Element.Methods.fire.methodize(),
3997 observe: Element.Methods.observe.methodize(),
3998 stopObserving: Element.Methods.stopObserving.methodize()
2176 3999 });
2177 if (!window.Event) {
2178 var Event = new Object();
2179 }
2180 4000
2181 Object.extend(Event, {
2182 KEY_BACKSPACE: 8,
2183 KEY_TAB: 9,
2184 KEY_RETURN: 13,
2185 KEY_ESC: 27,
2186 KEY_LEFT: 37,
2187 KEY_UP: 38,
2188 KEY_RIGHT: 39,
2189 KEY_DOWN: 40,
2190 KEY_DELETE: 46,
2191 KEY_HOME: 36,
2192 KEY_END: 35,
2193 KEY_PAGEUP: 33,
2194 KEY_PAGEDOWN: 34,
4001 (function() {
4002 /* Support for the DOMContentLoaded event is based on work by Dan Webb,
4003 Matthias Miller, Dean Edwards and John Resig. */
2195 4004
2196 element: function(event) {
2197 return event.target || event.srcElement;
2198 },
4005 var timer, fired = false;
2199 4006
2200 isLeftClick: function(event) {
2201 return (((event.which) && (event.which == 1)) ||
2202 ((event.button) && (event.button == 1)));
2203 },
4007 function fireContentLoadedEvent() {
4008 if (fired) return;
4009 if (timer) window.clearInterval(timer);
4010 document.fire("dom:loaded");
4011 fired = true;
4012 }
2204 4013
2205 pointerX: function(event) {
2206 return event.pageX || (event.clientX +
2207 (document.documentElement.scrollLeft || document.body.scrollLeft));
2208 },
4014 if (document.addEventListener) {
4015 if (Prototype.Browser.WebKit) {
4016 timer = window.setInterval(function() {
4017 if (/loaded|complete/.test(document.readyState))
4018 fireContentLoadedEvent();
4019 }, 0);
2209 4020
2210 pointerY: function(event) {
2211 return event.pageY || (event.clientY +
2212 (document.documentElement.scrollTop || document.body.scrollTop));
2213 },
4021 Event.observe(window, "load", fireContentLoadedEvent);
2214 4022
2215 stop: function(event) {
2216 if (event.preventDefault) {
2217 event.preventDefault();
2218 event.stopPropagation();
2219 4023 } else {
2220 event.returnValue = false;
2221 event.cancelBubble = true;
4024 document.addEventListener("DOMContentLoaded",
4025 fireContentLoadedEvent, false);
2222 4026 }
2223 },
2224
2225 // find the first node with the given tagName, starting from the
2226 // node the event was triggered on; traverses the DOM upwards
2227 findElement: function(event, tagName) {
2228 var element = Event.element(event);
2229 while (element.parentNode && (!element.tagName ||
2230 (element.tagName.toUpperCase() != tagName.toUpperCase())))
2231 element = element.parentNode;
2232 return element;
2233 },
2234 4027
2235 observers: false,
2236
2237 _observeAndCache: function(element, name, observer, useCapture) {
2238 if (!this.observers) this.observers = [];
2239 if (element.addEventListener) {
2240 this.observers.push([element, name, observer, useCapture]);
2241 element.addEventListener(name, observer, useCapture);
2242 } else if (element.attachEvent) {
2243 this.observers.push([element, name, observer, useCapture]);
2244 element.attachEvent('on' + name, observer);
2245 }
2246 },
4028 } else {
4029 document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
4030 $("__onDOMContentLoaded").onreadystatechange = function() {
4031 if (this.readyState == "complete") {
4032 this.onreadystatechange = null;
4033 fireContentLoadedEvent();
4034 }
4035 };
4036 }
4037 })();
4038 /*------------------------------- DEPRECATED -------------------------------*/
2247 4039
2248 unloadCache: function() {
2249 if (!Event.observers) return;
2250 for (var i = 0, length = Event.observers.length; i < length; i++) {
2251 Event.stopObserving.apply(this, Event.observers[i]);
2252 Event.observers[i][0] = null;
2253 }
2254 Event.observers = false;
2255 },
4040 Hash.toQueryString = Object.toQueryString;
2256 4041
2257 observe: function(element, name, observer, useCapture) {
2258 element = $(element);
2259 useCapture = useCapture || false;
4042 var Toggle = { display: Element.toggle };
2260 4043
2261 if (name == 'keypress' &&
2262 (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
2263 || element.attachEvent))
2264 name = 'keydown';
4044 Element.Methods.childOf = Element.Methods.descendantOf;
2265 4045
2266 Event._observeAndCache(element, name, observer, useCapture);
4046 var Insertion = {
4047 Before: function(element, content) {
4048 return Element.insert(element, {before:content});
2267 4049 },
2268 4050
2269 stopObserving: function(element, name, observer, useCapture) {
2270 element = $(element);
2271 useCapture = useCapture || false;
4051 Top: function(element, content) {
4052 return Element.insert(element, {top:content});
4053 },
2272 4054
2273 if (name == 'keypress' &&
2274 (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
2275 || element.detachEvent))
2276 name = 'keydown';
4055 Bottom: function(element, content) {
4056 return Element.insert(element, {bottom:content});
4057 },
2277 4058
2278 if (element.removeEventListener) {
2279 element.removeEventListener(name, observer, useCapture);
2280 } else if (element.detachEvent) {
2281 try {
2282 element.detachEvent('on' + name, observer);
2283 } catch (e) {}
2284 }
4059 After: function(element, content) {
4060 return Element.insert(element, {after:content});
2285 4061 }
2286 });
4062 };
4063
4064 var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
2287 4065
2288 /* prevent memory leaks in IE */
2289 if (navigator.appVersion.match(/\bMSIE\b/))
2290 Event.observe(window, 'unload', Event.unloadCache, false);
4066 // This should be moved to script.aculo.us; notice the deprecated methods
4067 // further below, that map to the newer Element methods.
2291 4068 var Position = {
2292 4069 // set to true if needed, warning: firefox performance problems
2293 4070 // NOT neeeded for page scrolling, only if draggable contained in
@@ -2307,59 +4084,13 var Position = {
2307 4084 || 0;
2308 4085 },
2309 4086
2310 realOffset: function(element) {
2311 var valueT = 0, valueL = 0;
2312 do {
2313 valueT += element.scrollTop || 0;
2314 valueL += element.scrollLeft || 0;
2315 element = element.parentNode;
2316 } while (element);
2317 return [valueL, valueT];
2318 },
2319
2320 cumulativeOffset: function(element) {
2321 var valueT = 0, valueL = 0;
2322 do {
2323 valueT += element.offsetTop || 0;
2324 valueL += element.offsetLeft || 0;
2325 element = element.offsetParent;
2326 } while (element);
2327 return [valueL, valueT];
2328 },
2329
2330 positionedOffset: function(element) {
2331 var valueT = 0, valueL = 0;
2332 do {
2333 valueT += element.offsetTop || 0;
2334 valueL += element.offsetLeft || 0;
2335 element = element.offsetParent;
2336 if (element) {
2337 if(element.tagName=='BODY') break;
2338 var p = Element.getStyle(element, 'position');
2339 if (p == 'relative' || p == 'absolute') break;
2340 }
2341 } while (element);
2342 return [valueL, valueT];
2343 },
2344
2345 offsetParent: function(element) {
2346 if (element.offsetParent) return element.offsetParent;
2347 if (element == document.body) return element;
2348
2349 while ((element = element.parentNode) && element != document.body)
2350 if (Element.getStyle(element, 'position') != 'static')
2351 return element;
2352
2353 return document.body;
2354 },
2355
2356 4087 // caches x/y coordinate pair to use with overlap
2357 4088 within: function(element, x, y) {
2358 4089 if (this.includeScrollOffsets)
2359 4090 return this.withinIncludingScrolloffsets(element, x, y);
2360 4091 this.xcomp = x;
2361 4092 this.ycomp = y;
2362 this.offset = this.cumulativeOffset(element);
4093 this.offset = Element.cumulativeOffset(element);
2363 4094
2364 4095 return (y >= this.offset[1] &&
2365 4096 y < this.offset[1] + element.offsetHeight &&
@@ -2368,11 +4099,11 var Position = {
2368 4099 },
2369 4100
2370 4101 withinIncludingScrolloffsets: function(element, x, y) {
2371 var offsetcache = this.realOffset(element);
4102 var offsetcache = Element.cumulativeScrollOffset(element);
2372 4103
2373 4104 this.xcomp = x + offsetcache[0] - this.deltaX;
2374 4105 this.ycomp = y + offsetcache[1] - this.deltaY;
2375 this.offset = this.cumulativeOffset(element);
4106 this.offset = Element.cumulativeOffset(element);
2376 4107
2377 4108 return (this.ycomp >= this.offset[1] &&
2378 4109 this.ycomp < this.offset[1] + element.offsetHeight &&
@@ -2391,125 +4122,104 var Position = {
2391 4122 element.offsetWidth;
2392 4123 },
2393 4124
2394 page: function(forElement) {
2395 var valueT = 0, valueL = 0;
2396
2397 var element = forElement;
2398 do {
2399 valueT += element.offsetTop || 0;
2400 valueL += element.offsetLeft || 0;
4125 // Deprecation layer -- use newer Element methods now (1.5.2).
2401 4126
2402 // Safari fix
2403 if (element.offsetParent==document.body)
2404 if (Element.getStyle(element,'position')=='absolute') break;
4127 cumulativeOffset: Element.Methods.cumulativeOffset,
2405 4128
2406 } while (element = element.offsetParent);
4129 positionedOffset: Element.Methods.positionedOffset,
2407 4130
2408 element = forElement;
2409 do {
2410 if (!window.opera || element.tagName=='BODY') {
2411 valueT -= element.scrollTop || 0;
2412 valueL -= element.scrollLeft || 0;
2413 }
2414 } while (element = element.parentNode);
4131 absolutize: function(element) {
4132 Position.prepare();
4133 return Element.absolutize(element);
4134 },
2415 4135
2416 return [valueL, valueT];
4136 relativize: function(element) {
4137 Position.prepare();
4138 return Element.relativize(element);
2417 4139 },
2418 4140
2419 clone: function(source, target) {
2420 var options = Object.extend({
2421 setLeft: true,
2422 setTop: true,
2423 setWidth: true,
2424 setHeight: true,
2425 offsetTop: 0,
2426 offsetLeft: 0
2427 }, arguments[2] || {})
4141 realOffset: Element.Methods.cumulativeScrollOffset,
2428 4142
2429 // find page position of source
2430 source = $(source);
2431 var p = Position.page(source);
4143 offsetParent: Element.Methods.getOffsetParent,
2432 4144
2433 // find coordinate system to use
2434 target = $(target);
2435 var delta = [0, 0];
2436 var parent = null;
2437 // delta [0,0] will do fine with position: fixed elements,
2438 // position:absolute needs offsetParent deltas
2439 if (Element.getStyle(target,'position') == 'absolute') {
2440 parent = Position.offsetParent(target);
2441 delta = Position.page(parent);
2442 }
4145 page: Element.Methods.viewportOffset,
2443 4146
2444 // correct by body offsets (fixes Safari)
2445 if (parent == document.body) {
2446 delta[0] -= document.body.offsetLeft;
2447 delta[1] -= document.body.offsetTop;
4147 clone: function(source, target, options) {
4148 options = options || { };
4149 return Element.clonePosition(target, source, options);
4150 }
4151 };
4152
4153 /*--------------------------------------------------------------------------*/
4154
4155 if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
4156 function iter(name) {
4157 return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
4158 }
4159
4160 instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
4161 function(element, className) {
4162 className = className.toString().strip();
4163 var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
4164 return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
4165 } : function(element, className) {
4166 className = className.toString().strip();
4167 var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
4168 if (!classNames && !className) return elements;
4169
4170 var nodes = $(element).getElementsByTagName('*');
4171 className = ' ' + className + ' ';
4172
4173 for (var i = 0, child, cn; child = nodes[i]; i++) {
4174 if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
4175 (classNames && classNames.all(function(name) {
4176 return !name.toString().blank() && cn.include(' ' + name + ' ');
4177 }))))
4178 elements.push(Element.extend(child));
2448 4179 }
4180 return elements;
4181 };
2449 4182
2450 // set position
2451 if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
2452 if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
2453 if(options.setWidth) target.style.width = source.offsetWidth + 'px';
2454 if(options.setHeight) target.style.height = source.offsetHeight + 'px';
2455 },
4183 return function(className, parentElement) {
4184 return $(parentElement || document.body).getElementsByClassName(className);
4185 };
4186 }(Element.Methods);
2456 4187
2457 absolutize: function(element) {
2458 element = $(element);
2459 if (element.style.position == 'absolute') return;
2460 Position.prepare();
4188 /*--------------------------------------------------------------------------*/
2461 4189
2462 var offsets = Position.positionedOffset(element);
2463 var top = offsets[1];
2464 var left = offsets[0];
2465 var width = element.clientWidth;
2466 var height = element.clientHeight;
4190 Element.ClassNames = Class.create();
4191 Element.ClassNames.prototype = {
4192 initialize: function(element) {
4193 this.element = $(element);
4194 },
2467 4195
2468 element._originalLeft = left - parseFloat(element.style.left || 0);
2469 element._originalTop = top - parseFloat(element.style.top || 0);
2470 element._originalWidth = element.style.width;
2471 element._originalHeight = element.style.height;
4196 _each: function(iterator) {
4197 this.element.className.split(/\s+/).select(function(name) {
4198 return name.length > 0;
4199 })._each(iterator);
4200 },
2472 4201
2473 element.style.position = 'absolute';
2474 element.style.top = top + 'px';
2475 element.style.left = left + 'px';
2476 element.style.width = width + 'px';
2477 element.style.height = height + 'px';
4202 set: function(className) {
4203 this.element.className = className;
2478 4204 },
2479 4205
2480 relativize: function(element) {
2481 element = $(element);
2482 if (element.style.position == 'relative') return;
2483 Position.prepare();
4206 add: function(classNameToAdd) {
4207 if (this.include(classNameToAdd)) return;
4208 this.set($A(this).concat(classNameToAdd).join(' '));
4209 },
2484 4210
2485 element.style.position = 'relative';
2486 var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
2487 var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
4211 remove: function(classNameToRemove) {
4212 if (!this.include(classNameToRemove)) return;
4213 this.set($A(this).without(classNameToRemove).join(' '));
4214 },
2488 4215
2489 element.style.top = top + 'px';
2490 element.style.left = left + 'px';
2491 element.style.height = element._originalHeight;
2492 element.style.width = element._originalWidth;
4216 toString: function() {
4217 return $A(this).join(' ');
2493 4218 }
2494 }
2495
2496 // Safari returns margins on body which is incorrect if the child is absolutely
2497 // positioned. For performance reasons, redefine Position.cumulativeOffset for
2498 // KHTML/WebKit only.
2499 if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
2500 Position.cumulativeOffset = function(element) {
2501 var valueT = 0, valueL = 0;
2502 do {
2503 valueT += element.offsetTop || 0;
2504 valueL += element.offsetLeft || 0;
2505 if (element.offsetParent == document.body)
2506 if (Element.getStyle(element, 'position') == 'absolute') break;
4219 };
2507 4220
2508 element = element.offsetParent;
2509 } while (element);
4221 Object.extend(Element.ClassNames.prototype, Enumerable);
2510 4222
2511 return [valueL, valueT];
2512 }
2513 }
4223 /*--------------------------------------------------------------------------*/
2514 4224
2515 4225 Element.addMethods(); No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now