Changeset 226 for trunk/trax/data

Show
Ignore:
Timestamp:
06/21/06 11:53:01 (6 years ago)
Author:
john
Message:

updated scriptaculous js files

Location:
trunk/trax/data/public/javascripts
Files:
4 added
4 modified

Legend:

Unmodified
Added
Removed
  • trunk/trax/data/public/javascripts/controls.js

    r182 r226  
    1 // $Id$ 
    21// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 
    32//           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) 
    4 //  
    5 // Permission is hereby granted, free of charge, to any person obtaining 
    6 // a copy of this software and associated documentation files (the 
    7 // "Software"), to deal in the Software without restriction, including 
    8 // without limitation the rights to use, copy, modify, merge, publish, 
    9 // distribute, sublicense, and/or sell copies of the Software, and to 
    10 // permit persons to whom the Software is furnished to do so, subject to 
    11 // the following conditions: 
    12 //  
    13 // The above copyright notice and this permission notice shall be 
    14 // included in all copies or substantial portions of the Software. 
    15 //  
    16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
    17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
    18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
    19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
    20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
    21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
    22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
    23  
    24 Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { 
    25   var children = $(element).childNodes; 
    26   var text     = ""; 
    27   var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i"); 
    28    
    29   for (var i = 0; i < children.length; i++) { 
    30     if(children[i].nodeType==3) { 
    31       text+=children[i].nodeValue; 
    32     } else { 
    33       if((!children[i].className.match(classtest)) && children[i].hasChildNodes()) 
    34         text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass); 
    35     } 
    36   } 
    37    
    38   return text; 
    39 } 
     3//           (c) 2005 Jon Tirsen (http://www.tirsen.com) 
     4// Contributors: 
     5//  Richard Livsey 
     6//  Rahul Bhargava 
     7//  Rob Wills 
     8//   
     9// See scriptaculous.js for full license. 
    4010 
    4111// Autocompleter.Base handles all the autocompletion functionality  
     
    4818// the text inside the monitored textbox changes. This method  
    4919// should get the text for which to provide autocompletion by 
    50 // invoking this.getEntry(), NOT by directly accessing 
     20// invoking this.getToken(), NOT by directly accessing 
    5121// this.element.value. This is to allow incremental tokenized 
    5222// autocompletion. Specific auto-completion logic (AJAX, etc) 
     
    5929// will incrementally autocomplete with a comma as the token. 
    6030// Additionally, ',' in the above example can be replaced with 
    61 // a token array, e.g. { tokens: new Array (',', '\n') } which 
     31// a token array, e.g. { tokens: [',', '\n'] } which 
    6232// enables autocompletion on multiple tokens. This is most  
    6333// useful when one of the tokens is \n (a newline), as it  
     
    6737Autocompleter.Base = function() {}; 
    6838Autocompleter.Base.prototype = { 
    69   base_initialize: function(element, update, options) { 
     39  baseInitialize: function(element, update, options) { 
    7040    this.element     = $(element);  
    7141    this.update      = $(update);   
    72     this.has_focus   = false;  
     42    this.hasFocus    = false;  
    7343    this.changed     = false;  
    7444    this.active      = false;  
    7545    this.index       = 0;      
    76     this.entry_count = 0; 
     46    this.entryCount = 0; 
    7747 
    7848    if (this.setOptions) 
     
    8050    else 
    8151      this.options = options || {}; 
    82       
    83     this.options.tokens       = this.options.tokens || new Array(); 
     52 
     53    this.options.paramName    = this.options.paramName || this.element.name; 
     54    this.options.tokens       = this.options.tokens || []; 
    8455    this.options.frequency    = this.options.frequency || 0.4; 
    85     this.options.min_chars    = this.options.min_chars || 1; 
     56    this.options.minChars     = this.options.minChars || 1; 
    8657    this.options.onShow       = this.options.onShow ||  
    8758    function(element, update){  
    8859      if(!update.style.position || update.style.position=='absolute') { 
    8960        update.style.position = 'absolute'; 
    90           var offsets = Position.cumulativeOffset(element); 
    91           update.style.left = offsets[0] + 'px'; 
    92           update.style.top  = (offsets[1] + element.offsetHeight) + 'px'; 
    93           update.style.width = element.offsetWidth + 'px'; 
     61        Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight}); 
    9462      } 
    95       new Effect.Appear(update,{duration:0.15}); 
     63      Effect.Appear(update,{duration:0.15}); 
    9664    }; 
    9765    this.options.onHide = this.options.onHide ||  
    9866    function(element, update){ new Effect.Fade(update,{duration:0.15}) }; 
    99      
    100     if(this.options.indicator) 
    101       this.indicator = $(this.options.indicator); 
    10267 
    10368    if (typeof(this.options.tokens) == 'string')  
    10469      this.options.tokens = new Array(this.options.tokens); 
    105         
     70 
    10671    this.observer = null; 
    10772     
     73    this.element.setAttribute('autocomplete','off'); 
     74 
    10875    Element.hide(this.update); 
    109      
     76 
    11077    Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); 
    11178    Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); 
     
    11380 
    11481  show: function() { 
    115     if(this.update.style.display=='none') this.options.onShow(this.element, this.update); 
    116     if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') { 
     82    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); 
     83    if(!this.iefix &&  
     84      (navigator.appVersion.indexOf('MSIE')>0) && 
     85      (navigator.userAgent.indexOf('Opera')<0) && 
     86      (Element.getStyle(this.update, 'position')=='absolute')) { 
    11787      new Insertion.After(this.update,  
    11888       '<iframe id="' + this.update.id + '_iefix" '+ 
    119        'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + 
     89       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + 
    12090       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); 
    12191      this.iefix = $(this.update.id+'_iefix'); 
    12292    } 
    123     if(this.iefix) { 
    124       Position.clone(this.update, this.iefix); 
    125       this.iefix.style.zIndex = 1; 
    126       this.update.style.zIndex = 2; 
    127       Element.show(this.iefix); 
    128     } 
     93    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); 
    12994  }, 
    13095   
     96  fixIEOverlapping: function() { 
     97    Position.clone(this.update, this.iefix); 
     98    this.iefix.style.zIndex = 1; 
     99    this.update.style.zIndex = 2; 
     100    Element.show(this.iefix); 
     101  }, 
     102 
    131103  hide: function() { 
    132     if(this.update.style.display=='') this.options.onHide(this.element, this.update); 
     104    this.stopIndicator(); 
     105    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); 
    133106    if(this.iefix) Element.hide(this.iefix); 
    134107  }, 
    135    
     108 
    136109  startIndicator: function() { 
    137     if(this.indicator) Element.show(this.indicator); 
    138   }, 
    139    
     110    if(this.options.indicator) Element.show(this.options.indicator); 
     111  }, 
     112 
    140113  stopIndicator: function() { 
    141     if(this.indicator) Element.hide(this.indicator); 
     114    if(this.options.indicator) Element.hide(this.options.indicator); 
    142115  }, 
    143116 
     
    147120       case Event.KEY_TAB: 
    148121       case Event.KEY_RETURN: 
    149          this.select_entry(); 
     122         this.selectEntry(); 
    150123         Event.stop(event); 
    151124       case Event.KEY_ESC: 
    152125         this.hide(); 
    153126         this.active = false; 
     127         Event.stop(event); 
    154128         return; 
    155129       case Event.KEY_LEFT: 
     
    157131         return; 
    158132       case Event.KEY_UP: 
    159          this.mark_previous(); 
     133         this.markPrevious(); 
    160134         this.render(); 
    161135         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); 
    162136         return; 
    163137       case Event.KEY_DOWN: 
    164          this.mark_next(); 
     138         this.markNext(); 
    165139         this.render(); 
    166140         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); 
     
    168142      } 
    169143     else  
    170       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)  
    171         return; 
    172      
     144       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||  
     145         (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; 
     146 
    173147    this.changed = true; 
    174     this.has_focus = true; 
    175      
     148    this.hasFocus = true; 
     149 
    176150    if(this.observer) clearTimeout(this.observer); 
    177151      this.observer =  
    178152        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); 
    179153  }, 
    180    
     154 
     155  activate: function() { 
     156    this.changed = false; 
     157    this.hasFocus = true; 
     158    this.getUpdatedChoices(); 
     159  }, 
     160 
    181161  onHover: function(event) { 
    182162    var element = Event.findElement(event, 'LI'); 
     
    192172    var element = Event.findElement(event, 'LI'); 
    193173    this.index = element.autocompleteIndex; 
    194     this.select_entry(); 
    195     Event.stop(event); 
     174    this.selectEntry(); 
     175    this.hide(); 
    196176  }, 
    197177   
     
    199179    // needed to make click events working 
    200180    setTimeout(this.hide.bind(this), 250); 
    201     this.has_focus = false; 
     181    this.hasFocus = false; 
    202182    this.active = false;      
    203183  },  
    204184   
    205185  render: function() { 
    206     if(this.entry_count > 0) { 
    207       for (var i = 0; i < this.entry_count; i++) 
     186    if(this.entryCount > 0) { 
     187      for (var i = 0; i < this.entryCount; i++) 
    208188        this.index==i ?  
    209           Element.addClassName(this.get_entry(i),"selected") :  
    210           Element.removeClassName(this.get_entry(i),"selected"); 
     189          Element.addClassName(this.getEntry(i),"selected") :  
     190          Element.removeClassName(this.getEntry(i),"selected"); 
    211191         
    212       if(this.has_focus) {  
    213         if(this.get_current_entry().scrollIntoView)  
    214           this.get_current_entry().scrollIntoView(false); 
    215          
     192      if(this.hasFocus) {  
    216193        this.show(); 
    217194        this.active = true; 
    218195      } 
    219     } else this.hide(); 
     196    } else { 
     197      this.active = false; 
     198      this.hide(); 
     199    } 
    220200  }, 
    221201   
    222   mark_previous: function() { 
     202  markPrevious: function() { 
    223203    if(this.index > 0) this.index-- 
    224       else this.index = this.entry_count-1; 
     204      else this.index = this.entryCount-1; 
    225205  }, 
    226206   
    227   mark_next: function() { 
    228     if(this.index < this.entry_count-1) this.index++ 
     207  markNext: function() { 
     208    if(this.index < this.entryCount-1) this.index++ 
    229209      else this.index = 0; 
    230210  }, 
    231211   
    232   get_entry: function(index) { 
     212  getEntry: function(index) { 
    233213    return this.update.firstChild.childNodes[index]; 
    234214  }, 
    235215   
    236   get_current_entry: function() { 
    237     return this.get_entry(this.index); 
     216  getCurrentEntry: function() { 
     217    return this.getEntry(this.index); 
    238218  }, 
    239219   
    240   select_entry: function() { 
     220  selectEntry: function() { 
    241221    this.active = false; 
    242     value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML(); 
    243     this.updateElement(value); 
    244     this.element.focus(); 
    245   }, 
    246  
    247   updateElement: function(value) { 
    248     var last_token_pos = this.findLastToken(); 
    249     if (last_token_pos != -1) { 
    250       var new_value = this.element.value.substr(0, last_token_pos + 1); 
    251       var whitespace = this.element.value.substr(last_token_pos + 1).match(/^\s+/); 
     222    this.updateElement(this.getCurrentEntry()); 
     223  }, 
     224 
     225  updateElement: function(selectedElement) { 
     226    if (this.options.updateElement) { 
     227      this.options.updateElement(selectedElement); 
     228      return; 
     229    } 
     230    var value = ''; 
     231    if (this.options.select) { 
     232      var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; 
     233      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); 
     234    } else 
     235      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); 
     236     
     237    var lastTokenPos = this.findLastToken(); 
     238    if (lastTokenPos != -1) { 
     239      var newValue = this.element.value.substr(0, lastTokenPos + 1); 
     240      var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); 
    252241      if (whitespace) 
    253         new_value += whitespace[0]; 
    254       this.element.value = new_value + value; 
     242        newValue += whitespace[0]; 
     243      this.element.value = newValue + value; 
    255244    } else { 
    256245      this.element.value = value; 
    257     }  
    258   }, 
    259    
     246    } 
     247    this.element.focus(); 
     248     
     249    if (this.options.afterUpdateElement) 
     250      this.options.afterUpdateElement(this.element, selectedElement); 
     251  }, 
     252 
    260253  updateChoices: function(choices) { 
    261     if(!this.changed && this.has_focus) { 
     254    if(!this.changed && this.hasFocus) { 
    262255      this.update.innerHTML = choices; 
    263256      Element.cleanWhitespace(this.update); 
     
    265258 
    266259      if(this.update.firstChild && this.update.firstChild.childNodes) { 
    267         this.entry_count =  
     260        this.entryCount =  
    268261          this.update.firstChild.childNodes.length; 
    269         for (var i = 0; i < this.entry_count; i++) { 
    270           entry = this.get_entry(i); 
     262        for (var i = 0; i < this.entryCount; i++) { 
     263          var entry = this.getEntry(i); 
    271264          entry.autocompleteIndex = i; 
    272265          this.addObservers(entry); 
    273266        } 
    274267      } else {  
    275         this.entry_count = 0; 
     268        this.entryCount = 0; 
    276269      } 
    277        
     270 
    278271      this.stopIndicator(); 
    279        
     272 
    280273      this.index = 0; 
    281274      this.render(); 
     
    290283  onObserverEvent: function() { 
    291284    this.changed = false;    
    292     if(this.getEntry().length>=this.options.min_chars) { 
     285    if(this.getToken().length>=this.options.minChars) { 
    293286      this.startIndicator(); 
    294287      this.getUpdatedChoices(); 
     
    299292  }, 
    300293 
    301   getEntry: function() { 
    302     var token_pos = this.findLastToken(); 
    303     if (token_pos != -1) 
    304       var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,''); 
     294  getToken: function() { 
     295    var tokenPos = this.findLastToken(); 
     296    if (tokenPos != -1) 
     297      var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); 
    305298    else 
    306299      var ret = this.element.value; 
    307      
     300 
    308301    return /\n/.test(ret) ? '' : ret; 
    309302  }, 
    310303 
    311304  findLastToken: function() { 
    312     var last_token_pos = -1; 
     305    var lastTokenPos = -1; 
    313306 
    314307    for (var i=0; i<this.options.tokens.length; i++) { 
    315       var this_token_pos = this.element.value.lastIndexOf(this.options.tokens[i]); 
    316       if (this_token_pos > last_token_pos) 
    317         last_token_pos = this_token_pos; 
    318     } 
    319     return last_token_pos; 
     308      var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]); 
     309      if (thisTokenPos > lastTokenPos) 
     310        lastTokenPos = thisTokenPos; 
     311    } 
     312    return lastTokenPos; 
    320313  } 
    321314} 
     
    324317Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { 
    325318  initialize: function(element, update, url, options) { 
    326       this.base_initialize(element, update, options); 
     319    this.baseInitialize(element, update, options); 
    327320    this.options.asynchronous  = true; 
    328     this.options.onComplete    = this.onComplete.bind(this) 
    329     this.options.method        = 'post'; 
     321    this.options.onComplete    = this.onComplete.bind(this); 
    330322    this.options.defaultParams = this.options.parameters || null; 
    331323    this.url                   = url; 
    332324  }, 
    333    
     325 
    334326  getUpdatedChoices: function() { 
    335     entry = encodeURIComponent(this.element.name) + '=' +  
    336       encodeURIComponent(this.getEntry()); 
    337        
     327    entry = encodeURIComponent(this.options.paramName) + '=' +  
     328      encodeURIComponent(this.getToken()); 
     329 
    338330    this.options.parameters = this.options.callback ? 
    339331      this.options.callback(this.element, entry) : entry; 
    340          
     332 
    341333    if(this.options.defaultParams)  
    342334      this.options.parameters += '&' + this.options.defaultParams; 
    343      
     335 
    344336    new Ajax.Request(this.url, this.options); 
    345337  }, 
    346    
     338 
    347339  onComplete: function(request) { 
    348340    this.updateChoices(request.responseText); 
     
    363355// - choices - How many autocompletion choices to offer 
    364356// 
    365 // - partial_search - If false, the autocompleter will match entered 
     357// - partialSearch - If false, the autocompleter will match entered 
    366358//                    text only at the beginning of strings in the  
    367359//                    autocomplete array. Defaults to true, which will 
     
    369361//                    strings in the autocomplete array. If you want to 
    370362//                    search anywhere in the string, additionally set 
    371 //                    the option full_search to true (default: off). 
    372 // 
    373 // - full_search - Search anywhere in autocomplete array strings. 
    374 // 
    375 // - partial_chars - How many characters to enter before triggering 
    376 //                   a partial match (unlike min_chars, which defines 
     363//                    the option fullSearch to true (default: off). 
     364// 
     365// - fullSsearch - Search anywhere in autocomplete array strings. 
     366// 
     367// - partialChars - How many characters to enter before triggering 
     368//                   a partial match (unlike minChars, which defines 
    377369//                   how many characters are required to do any match 
    378370//                   at all). Defaults to 2. 
    379371// 
    380 // - ignore_case - Whether to ignore case when autocompleting. 
     372// - ignoreCase - Whether to ignore case when autocompleting. 
    381373//                 Defaults to true. 
    382374// 
     
    389381Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { 
    390382  initialize: function(element, update, array, options) { 
    391     this.base_initialize(element, update, options); 
     383    this.baseInitialize(element, update, options); 
    392384    this.options.array = array; 
    393385  }, 
     
    400392    this.options = Object.extend({ 
    401393      choices: 10, 
    402       partial_search: true, 
    403       partial_chars: 2, 
    404       ignore_case: true, 
    405       full_search: false, 
     394      partialSearch: true, 
     395      partialChars: 2, 
     396      ignoreCase: true, 
     397      fullSearch: false, 
    406398      selector: function(instance) { 
    407         var ret       = new Array(); // Beginning matches 
    408         var partial   = new Array(); // Inside matches 
    409         var entry     = instance.getEntry(); 
     399        var ret       = []; // Beginning matches 
     400        var partial   = []; // Inside matches 
     401        var entry     = instance.getToken(); 
    410402        var count     = 0; 
    411          
     403 
    412404        for (var i = 0; i < instance.options.array.length &&   
    413             ret.length < instance.options.choices ; i++) {  
     405          ret.length < instance.options.choices ; i++) {  
     406 
    414407          var elem = instance.options.array[i]; 
    415           var found_pos = instance.options.ignore_case ?  
     408          var foundPos = instance.options.ignoreCase ?  
    416409            elem.toLowerCase().indexOf(entry.toLowerCase()) :  
    417410            elem.indexOf(entry); 
    418411 
    419           while (found_pos != -1) { 
    420             if (found_pos == 0 && elem.length != entry.length) {  
     412          while (foundPos != -1) { 
     413            if (foundPos == 0 && elem.length != entry.length) {  
    421414              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +  
    422415                elem.substr(entry.length) + "</li>"); 
    423416              break; 
    424             } else if (entry.length >= instance.options.partial_chars &&  
    425               instance.options.partial_search && found_pos != -1) { 
    426               if (instance.options.full_search || /\s/.test(elem.substr(found_pos-1,1))) { 
    427                 partial.push("<li>" + elem.substr(0, found_pos) + "<strong>" + 
    428                   elem.substr(found_pos, entry.length) + "</strong>" + elem.substr( 
    429                   found_pos + entry.length) + "</li>"); 
     417            } else if (entry.length >= instance.options.partialChars &&  
     418              instance.options.partialSearch && foundPos != -1) { 
     419              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { 
     420                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" + 
     421                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr( 
     422                  foundPos + entry.length) + "</li>"); 
    430423                break; 
    431424              } 
    432425            } 
    433426 
    434             found_pos = instance.options.ignore_case ?  
    435               elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) :  
    436               elem.indexOf(entry, found_pos + 1); 
     427            foundPos = instance.options.ignoreCase ?  
     428              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :  
     429              elem.indexOf(entry, foundPos + 1); 
    437430 
    438431          } 
     
    445438  } 
    446439}); 
     440 
     441// AJAX in-place editor 
     442// 
     443// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor 
     444 
     445// Use this if you notice weird scrolling problems on some browsers, 
     446// the DOM might be a bit confused when this gets called so do this 
     447// waits 1 ms (with setTimeout) until it does the activation 
     448Field.scrollFreeActivate = function(field) { 
     449  setTimeout(function() { 
     450    Field.activate(field); 
     451  }, 1); 
     452} 
     453 
     454Ajax.InPlaceEditor = Class.create(); 
     455Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; 
     456Ajax.InPlaceEditor.prototype = { 
     457  initialize: function(element, url, options) { 
     458    this.url = url; 
     459    this.element = $(element); 
     460 
     461    this.options = Object.extend({ 
     462      okButton: true, 
     463      okText: "ok", 
     464      cancelLink: true, 
     465      cancelText: "cancel", 
     466      savingText: "Saving...", 
     467      clickToEditText: "Click to edit", 
     468      okText: "ok", 
     469      rows: 1, 
     470      onComplete: function(transport, element) { 
     471        new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); 
     472      }, 
     473      onFailure: function(transport) { 
     474        alert("Error communicating with the server: " + transport.responseText.stripTags()); 
     475      }, 
     476      callback: function(form) { 
     477        return Form.serialize(form); 
     478      }, 
     479      handleLineBreaks: true, 
     480      loadingText: 'Loading...', 
     481      savingClassName: 'inplaceeditor-saving', 
     482      loadingClassName: 'inplaceeditor-loading', 
     483      formClassName: 'inplaceeditor-form', 
     484      highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, 
     485      highlightendcolor: "#FFFFFF", 
     486      externalControl: null, 
     487      submitOnBlur: false, 
     488      ajaxOptions: {}, 
     489      evalScripts: false 
     490    }, options || {}); 
     491 
     492    if(!this.options.formId && this.element.id) { 
     493      this.options.formId = this.element.id + "-inplaceeditor"; 
     494      if ($(this.options.formId)) { 
     495        // there's already a form with that name, don't specify an id 
     496        this.options.formId = null; 
     497      } 
     498    } 
     499     
     500    if (this.options.externalControl) { 
     501      this.options.externalControl = $(this.options.externalControl); 
     502    } 
     503     
     504    this.originalBackground = Element.getStyle(this.element, 'background-color'); 
     505    if (!this.originalBackground) { 
     506      this.originalBackground = "transparent"; 
     507    } 
     508     
     509    this.element.title = this.options.clickToEditText; 
     510     
     511    this.onclickListener = this.enterEditMode.bindAsEventListener(this); 
     512    this.mouseoverListener = this.enterHover.bindAsEventListener(this); 
     513    this.mouseoutListener = this.leaveHover.bindAsEventListener(this); 
     514    Event.observe(this.element, 'click', this.onclickListener); 
     515    Event.observe(this.element, 'mouseover', this.mouseoverListener); 
     516    Event.observe(this.element, 'mouseout', this.mouseoutListener); 
     517    if (this.options.externalControl) { 
     518      Event.observe(this.options.externalControl, 'click', this.onclickListener); 
     519      Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); 
     520      Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); 
     521    } 
     522  }, 
     523  enterEditMode: function(evt) { 
     524    if (this.saving) return; 
     525    if (this.editing) return; 
     526    this.editing = true; 
     527    this.onEnterEditMode(); 
     528    if (this.options.externalControl) { 
     529      Element.hide(this.options.externalControl); 
     530    } 
     531    Element.hide(this.element); 
     532    this.createForm(); 
     533    this.element.parentNode.insertBefore(this.form, this.element); 
     534    Field.scrollFreeActivate(this.editField); 
     535    // stop the event to avoid a page refresh in Safari 
     536    if (evt) { 
     537      Event.stop(evt); 
     538    } 
     539    return false; 
     540  }, 
     541  createForm: function() { 
     542    this.form = document.createElement("form"); 
     543    this.form.id = this.options.formId; 
     544    Element.addClassName(this.form, this.options.formClassName) 
     545    this.form.onsubmit = this.onSubmit.bind(this); 
     546 
     547    this.createEditField(); 
     548 
     549    if (this.options.textarea) { 
     550      var br = document.createElement("br"); 
     551      this.form.appendChild(br); 
     552    } 
     553 
     554    if (this.options.okButton) { 
     555      okButton = document.createElement("input"); 
     556      okButton.type = "submit"; 
     557      okButton.value = this.options.okText; 
     558      okButton.className = 'editor_ok_button'; 
     559      this.form.appendChild(okButton); 
     560    } 
     561 
     562    if (this.options.cancelLink) { 
     563      cancelLink = document.createElement("a"); 
     564      cancelLink.href = "#"; 
     565      cancelLink.appendChild(document.createTextNode(this.options.cancelText)); 
     566      cancelLink.onclick = this.onclickCancel.bind(this); 
     567      cancelLink.className = 'editor_cancel';       
     568      this.form.appendChild(cancelLink); 
     569    } 
     570  }, 
     571  hasHTMLLineBreaks: function(string) { 
     572    if (!this.options.handleLineBreaks) return false; 
     573    return string.match(/<br/i) || string.match(/<p>/i); 
     574  }, 
     575  convertHTMLLineBreaks: function(string) { 
     576    return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, ""); 
     577  }, 
     578  createEditField: function() { 
     579    var text; 
     580    if(this.options.loadTextURL) { 
     581      text = this.options.loadingText; 
     582    } else { 
     583      text = this.getText(); 
     584    } 
     585 
     586    var obj = this; 
     587     
     588    if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { 
     589      this.options.textarea = false; 
     590      var textField = document.createElement("input"); 
     591      textField.obj = this; 
     592      textField.type = "text"; 
     593      textField.name = "value"; 
     594      textField.value = text; 
     595      textField.style.backgroundColor = this.options.highlightcolor; 
     596      textField.className = 'editor_field'; 
     597      var size = this.options.size || this.options.cols || 0; 
     598      if (size != 0) textField.size = size; 
     599      if (this.options.submitOnBlur) 
     600        textField.onblur = this.onSubmit.bind(this); 
     601      this.editField = textField; 
     602    } else { 
     603      this.options.textarea = true; 
     604      var textArea = document.createElement("textarea"); 
     605      textArea.obj = this; 
     606      textArea.name = "value"; 
     607      textArea.value = this.convertHTMLLineBreaks(text); 
     608      textArea.rows = this.options.rows; 
     609      textArea.cols = this.options.cols || 40; 
     610      textArea.className = 'editor_field';       
     611      if (this.options.submitOnBlur) 
     612        textArea.onblur = this.onSubmit.bind(this); 
     613      this.editField = textArea; 
     614    } 
     615     
     616    if(this.options.loadTextURL) { 
     617      this.loadExternalText(); 
     618    } 
     619    this.form.appendChild(this.editField); 
     620  }, 
     621  getText: function() { 
     622    return this.element.innerHTML; 
     623  }, 
     624  loadExternalText: function() { 
     625    Element.addClassName(this.form, this.options.loadingClassName); 
     626    this.editField.disabled = true; 
     627    new Ajax.Request( 
     628      this.options.loadTextURL, 
     629      Object.extend({ 
     630        asynchronous: true, 
     631        onComplete: this.onLoadedExternalText.bind(this) 
     632      }, this.options.ajaxOptions) 
     633    ); 
     634  }, 
     635  onLoadedExternalText: function(transport) { 
     636    Element.removeClassName(this.form, this.options.loadingClassName); 
     637    this.editField.disabled = false; 
     638    this.editField.value = transport.responseText.stripTags(); 
     639  }, 
     640  onclickCancel: function() { 
     641    this.onComplete(); 
     642    this.leaveEditMode(); 
     643    return false; 
     644  }, 
     645  onFailure: function(transport) { 
     646    this.options.onFailure(transport); 
     647    if (this.oldInnerHTML) { 
     648      this.element.innerHTML = this.oldInnerHTML; 
     649      this.oldInnerHTML = null; 
     650    } 
     651    return false; 
     652  }, 
     653  onSubmit: function() { 
     654    // onLoading resets these so we need to save them away for the Ajax call 
     655    var form = this.form; 
     656    var value = this.editField.value; 
     657     
     658    // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... 
     659    // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... 
     660    // to be displayed indefinitely 
     661    this.onLoading(); 
     662     
     663    if (this.options.evalScripts) { 
     664      new Ajax.Request( 
     665        this.url, Object.extend({ 
     666          parameters: this.options.callback(form, value), 
     667          onComplete: this.onComplete.bind(this), 
     668          onFailure: this.onFailure.bind(this), 
     669          asynchronous:true,  
     670          evalScripts:true 
     671        }, this.options.ajaxOptions)); 
     672    } else  { 
     673      new Ajax.Updater( 
     674        { success: this.element, 
     675          // don't update on failure (this could be an option) 
     676          failure: null },  
     677        this.url, Object.extend({ 
     678          parameters: this.options.callback(form, value), 
     679          onComplete: this.onComplete.bind(this), 
     680          onFailure: this.onFailure.bind(this) 
     681        }, this.options.ajaxOptions)); 
     682    } 
     683    // stop the event to avoid a page refresh in Safari 
     684    if (arguments.length > 1) { 
     685      Event.stop(arguments[0]); 
     686    } 
     687    return false; 
     688  }, 
     689  onLoading: function() { 
     690    this.saving = true; 
     691    this.removeForm(); 
     692    this.leaveHover(); 
     693    this.showSaving(); 
     694  }, 
     695  showSaving: function() { 
     696    this.oldInnerHTML = this.element.innerHTML; 
     697    this.element.innerHTML = this.options.savingText; 
     698    Element.addClassName(this.element, this.options.savingClassName); 
     699    this.element.style.backgroundColor = this.originalBackground; 
     700    Element.show(this.element); 
     701  }, 
     702  removeForm: function() { 
     703    if(this.form) { 
     704      if (this.form.parentNode) Element.remove(this.form); 
     705      this.form = null; 
     706    } 
     707  }, 
     708  enterHover: function() { 
     709    if (this.saving) return; 
     710    this.element.style.backgroundColor = this.options.highlightcolor; 
     711    if (this.effect) { 
     712      this.effect.cancel(); 
     713    } 
     714    Element.addClassName(this.element, this.options.hoverClassName) 
     715  }, 
     716  leaveHover: function() { 
     717    if (this.options.backgroundColor) { 
     718      this.element.style.backgroundColor = this.oldBackground; 
     719    } 
     720    Element.removeClassName(this.element, this.options.hoverClassName) 
     721    if (this.saving) return; 
     722    this.effect = new Effect.Highlight(this.element, { 
     723      startcolor: this.options.highlightcolor, 
     724      endcolor: this.options.highlightendcolor, 
     725      restorecolor: this.originalBackground 
     726    }); 
     727  }, 
     728  leaveEditMode: function() { 
     729    Element.removeClassName(this.element, this.options.savingClassName); 
     730    this.removeForm(); 
     731    this.leaveHover(); 
     732    this.element.style.backgroundColor = this.originalBackground; 
     733    Element.show(this.element); 
     734    if (this.options.externalControl) { 
     735      Element.show(this.options.externalControl); 
     736    } 
     737    this.editing = false; 
     738    this.saving = false; 
     739    this.oldInnerHTML = null; 
     740    this.onLeaveEditMode(); 
     741  }, 
     742  onComplete: function(transport) { 
     743    this.leaveEditMode(); 
     744    this.options.onComplete.bind(this)(transport, this.element); 
     745  }, 
     746  onEnterEditMode: function() {}, 
     747  onLeaveEditMode: function() {}, 
     748  dispose: function() { 
     749    if (this.oldInnerHTML) { 
     750      this.element.innerHTML = this.oldInnerHTML; 
     751    } 
     752    this.leaveEditMode(); 
     753    Event.stopObserving(this.element, 'click', this.onclickListener); 
     754    Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); 
     755    Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); 
     756    if (this.options.externalControl) { 
     757      Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); 
     758      Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); 
     759      Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); 
     760    } 
     761  } 
     762}; 
     763 
     764Ajax.InPlaceCollectionEditor = Class.create(); 
     765Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); 
     766Object.extend(Ajax.InPlaceCollectionEditor.prototype, { 
     767  createEditField: function() { 
     768    if (!this.cached_selectTag) { 
     769      var selectTag = document.createElement("select"); 
     770      var collection = this.options.collection || []; 
     771      var optionTag; 
     772      collection.each(function(e,i) { 
     773        optionTag = document.createElement("option"); 
     774        optionTag.value = (e instanceof Array) ? e[0] : e; 
     775        if(this.options.value==optionTag.value) optionTag.selected = true; 
     776        optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); 
     777        selectTag.appendChild(optionTag); 
     778      }.bind(this)); 
     779      this.cached_selectTag = selectTag; 
     780    } 
     781 
     782    this.editField = this.cached_selectTag; 
     783    if(this.options.loadTextURL) this.loadExternalText(); 
     784    this.form.appendChild(this.editField); 
     785    this.options.callback = function(form, value) { 
     786      return "value=" + encodeURIComponent(value); 
     787    } 
     788  } 
     789}); 
     790 
     791// Delayed observer, like Form.Element.Observer,  
     792// but waits for delay after last key input 
     793// Ideal for live-search fields 
     794 
     795Form.Element.DelayedObserver = Class.create(); 
     796Form.Element.DelayedObserver.prototype = { 
     797  initialize: function(element, delay, callback) { 
     798    this.delay     = delay || 0.5; 
     799    this.element   = $(element); 
     800    this.callback  = callback; 
     801    this.timer     = null; 
     802    this.lastValue = $F(this.element);  
     803    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); 
     804  }, 
     805  delayedListener: function(event) { 
     806    if(this.lastValue == $F(this.element)) return; 
     807    if(this.timer) clearTimeout(this.timer); 
     808    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); 
     809    this.lastValue = $F(this.element); 
     810  }, 
     811  onTimerEvent: function() { 
     812    this.timer = null; 
     813    this.callback(this.element, $F(this.element)); 
     814  } 
     815}; 
  • trunk/trax/data/public/javascripts/dragdrop.js

    r182 r226  
    1 // $Id$ 
    21// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 
    3 //  
    4 // Element.Class part Copyright (c) 2005 by Rick Olson 
    5 //  
    6 // Permission is hereby granted, free of charge, to any person obtaining 
    7 // a copy of this software and associated documentation files (the 
    8 // "Software"), to deal in the Software without restriction, including 
    9 // without limitation the rights to use, copy, modify, merge, publish, 
    10 // distribute, sublicense, and/or sell copies of the Software, and to 
    11 // permit persons to whom the Software is furnished to do so, subject to 
    12 // the following conditions: 
    13 //  
    14 // The above copyright notice and this permission notice shall be 
    15 // included in all copies or substantial portions of the Software. 
    16 //  
    17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
    18 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
    19 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
    20 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
    21 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
    22 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
    23 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
    24  
    25 Element.Class = { 
    26     // Element.toggleClass(element, className) toggles the class being on/off 
    27     // Element.toggleClass(element, className1, className2) toggles between both classes, 
    28     //   defaulting to className1 if neither exist 
    29     toggle: function(element, className) { 
    30       if(Element.Class.has(element, className)) { 
    31         Element.Class.remove(element, className); 
    32         if(arguments.length == 3) Element.Class.add(element, arguments[2]); 
    33       } else { 
    34         Element.Class.add(element, className); 
    35         if(arguments.length == 3) Element.Class.remove(element, arguments[2]); 
    36       } 
    37     }, 
    38  
    39     // gets space-delimited classnames of an element as an array 
    40     get: function(element) { 
    41       element = $(element); 
    42       return element.className.split(' '); 
    43     }, 
    44  
    45     // functions adapted from original functions by Gavin Kistner 
    46     remove: function(element) { 
    47       element = $(element); 
    48       var regEx; 
    49       for(var i = 1; i < arguments.length; i++) { 
    50         regEx = new RegExp("^" + arguments[i] + "\\b\\s*|\\s*\\b" + arguments[i] + "\\b", 'g'); 
    51         element.className = element.className.replace(regEx, '') 
    52       } 
    53     }, 
    54  
    55     add: function(element) { 
    56       element = $(element); 
    57       for(var i = 1; i < arguments.length; i++) { 
    58         Element.Class.remove(element, arguments[i]); 
    59         element.className += (element.className.length > 0 ? ' ' : '') + arguments[i]; 
    60       } 
    61     }, 
    62  
    63     // returns true if all given classes exist in said element 
    64     has: function(element) { 
    65       element = $(element); 
    66       if(!element || !element.className) return false; 
    67       var regEx; 
    68       for(var i = 1; i < arguments.length; i++) { 
    69         regEx = new RegExp("\\b" + arguments[i] + "\\b"); 
    70         if(!regEx.test(element.className)) return false; 
    71       } 
    72       return true; 
    73     }, 
    74      
    75     // expects arrays of strings and/or strings as optional paramters 
    76     // Element.Class.has_any(element, ['classA','classB','classC'], 'classD') 
    77     has_any: function(element) { 
    78       element = $(element); 
    79       if(!element || !element.className) return false; 
    80       var regEx; 
    81       for(var i = 1; i < arguments.length; i++) { 
    82         if((typeof arguments[i] == 'object') &&  
    83           (arguments[i].constructor == Array)) { 
    84           for(var j = 0; j < arguments[i].length; j++) { 
    85             regEx = new RegExp("\\b" + arguments[i][j] + "\\b"); 
    86             if(regEx.test(element.className)) return true; 
    87           } 
    88         } else { 
    89           regEx = new RegExp("\\b" + arguments[i] + "\\b"); 
    90           if(regEx.test(element.className)) return true; 
    91         } 
    92       } 
    93       return false; 
    94     }, 
    95      
    96     childrenWith: function(element, className) { 
    97       var children = $(element).getElementsByTagName('*'); 
    98       var elements = new Array(); 
    99        
    100       for (var i = 0; i < children.length; i++) { 
    101         if (Element.Class.has(children[i], className)) { 
    102           elements.push(children[i]); 
    103           break; 
    104         } 
    105       } 
    106        
    107       return elements; 
    108     } 
    109 } 
     2//           (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) 
     3//   
     4// See scriptaculous.js for full license. 
    1105 
    1116/*--------------------------------------------------------------------------*/ 
    1127 
    1138var Droppables = { 
    114   drops: false, 
    115    
     9  drops: [], 
     10 
    11611  remove: function(element) { 
    117     for(var i = 0; i < this.drops.length; i++) 
    118       if(this.drops[i].element == element) 
    119         this.drops.splice(i,1); 
    120   }, 
    121    
     12    this.drops = this.drops.reject(function(d) { return d.element==$(element) }); 
     13  }, 
     14 
    12215  add: function(element) { 
    123     var element = $(element); 
     16    element = $(element); 
    12417    var options = Object.extend({ 
    12518      greedy:     true, 
    126       hoverclass: null   
     19      hoverclass: null, 
     20      tree:       false 
    12721    }, arguments[1] || {}); 
    128      
     22 
    12923    // cache containers 
    13024    if(options.containment) { 
    131       options._containers = new Array(); 
     25      options._containers = []; 
    13226      var containment = options.containment; 
    13327      if((typeof containment == 'object') &&  
    13428        (containment.constructor == Array)) { 
    135         for(var i=0; i<containment.length; i++) 
    136           options._containers.push($(containment[i])); 
     29        containment.each( function(c) { options._containers.push($(c)) }); 
    13730      } else { 
    13831        options._containers.push($(containment)); 
    13932      } 
    140       options._containers_length =  
    141         options._containers.length-1; 
    142     } 
    143      
     33    } 
     34     
     35    if(options.accept) options.accept = [options.accept].flatten(); 
     36 
    14437    Element.makePositioned(element); // fix IE 
    145      
    14638    options.element = element; 
    147      
    148     // activate the droppable     
    149     if(!this.drops) this.drops = []; 
     39 
    15040    this.drops.push(options); 
    15141  }, 
    15242   
    153   is_contained: function(element, drop) { 
    154     var containers = drop._containers; 
    155     var parentNode = element.parentNode; 
    156     var i = drop._containers_length; 
    157     do { if(parentNode==containers[i]) return true; } while (i--); 
    158     return false; 
    159   }, 
    160    
    161   is_affected: function(pX, pY, element, drop) { 
     43  findDeepestChild: function(drops) { 
     44    deepest = drops[0]; 
     45       
     46    for (i = 1; i < drops.length; ++i) 
     47      if (Element.isParent(drops[i].element, deepest.element)) 
     48        deepest = drops[i]; 
     49     
     50    return deepest; 
     51  }, 
     52 
     53  isContained: function(element, drop) { 
     54    var containmentNode; 
     55    if(drop.tree) { 
     56      containmentNode = element.treeNode;  
     57    } else { 
     58      containmentNode = element.parentNode; 
     59    } 
     60    return drop._containers.detect(function(c) { return containmentNode == c }); 
     61  }, 
     62   
     63  isAffected: function(point, element, drop) { 
    16264    return ( 
    16365      (drop.element!=element) && 
    16466      ((!drop._containers) || 
    165         this.is_contained(element, drop)) && 
     67        this.isContained(element, drop)) && 
    16668      ((!drop.accept) || 
    167         (Element.Class.has_any(element, drop.accept))) && 
    168       Position.within(drop.element, pX, pY) ); 
    169   }, 
    170    
     69        (Element.classNames(element).detect(  
     70          function(v) { return drop.accept.include(v) } ) )) && 
     71      Position.within(drop.element, point[0], point[1]) ); 
     72  }, 
     73 
    17174  deactivate: function(drop) { 
    172     Element.Class.remove(drop.element, drop.hoverclass); 
     75    if(drop.hoverclass) 
     76      Element.removeClassName(drop.element, drop.hoverclass); 
    17377    this.last_active = null; 
    17478  }, 
    175    
     79 
    17680  activate: function(drop) { 
     81    if(drop.hoverclass) 
     82      Element.addClassName(drop.element, drop.hoverclass); 
     83    this.last_active = drop; 
     84  }, 
     85 
     86  show: function(point, element) { 
     87    if(!this.drops.length) return; 
     88    var affected = []; 
     89     
    17790    if(this.last_active) this.deactivate(this.last_active); 
    178     if(drop.hoverclass) { 
    179       Element.Class.add(drop.element, drop.hoverclass); 
    180       this.last_active = drop; 
    181     } 
    182   }, 
    183    
    184   show: function(event, element) { 
    185     if(!this.drops) return; 
    186     var pX = Event.pointerX(event); 
    187     var pY = Event.pointerY(event); 
    188     Position.prepare(); 
    189      
    190     var i = this.drops.length-1; do { 
    191       var drop = this.drops[i]; 
    192       if(this.is_affected(pX, pY, element, drop)) { 
    193         if(drop.onHover) 
    194            drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); 
    195         if(drop.greedy) {  
    196           this.activate(drop); 
    197           return; 
    198         } 
    199       } 
    200     } while (i--); 
    201   }, 
    202    
     91    this.drops.each( function(drop) { 
     92      if(Droppables.isAffected(point, element, drop)) 
     93        affected.push(drop); 
     94    }); 
     95         
     96    if(affected.length>0) { 
     97      drop = Droppables.findDeepestChild(affected); 
     98      Position.within(drop.element, point[0], point[1]); 
     99      if(drop.onHover) 
     100        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); 
     101       
     102      Droppables.activate(drop); 
     103    } 
     104  }, 
     105 
    203106  fire: function(event, element) { 
    204107    if(!this.last_active) return; 
    205108    Position.prepare(); 
    206      
    207     if (this.is_affected(Event.pointerX(event), Event.pointerY(event), element, this.last_active)) 
     109 
     110    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) 
    208111      if (this.last_active.onDrop)  
    209         this.last_active.onDrop(element, this.last_active); 
    210      
    211   }, 
    212    
     112        this.last_active.onDrop(element, this.last_active.element, event); 
     113  }, 
     114 
    213115  reset: function() { 
    214116    if(this.last_active) 
     
    217119} 
    218120 
    219 Draggables = { 
    220   observers: new Array(), 
     121var Draggables = { 
     122  drags: [], 
     123  observers: [], 
     124   
     125  register: function(draggable) { 
     126    if(this.drags.length == 0) { 
     127      this.eventMouseUp   = this.endDrag.bindAsEventListener(this); 
     128      this.eventMouseMove = this.updateDrag.bindAsEventListener(this); 
     129      this.eventKeypress  = this.keyPress.bindAsEventListener(this); 
     130       
     131      Event.observe(document, "mouseup", this.eventMouseUp); 
     132      Event.observe(document, "mousemove", this.eventMouseMove); 
     133      Event.observe(document, "keypress", this.eventKeypress); 
     134    } 
     135    this.drags.push(draggable); 
     136  }, 
     137   
     138  unregister: function(draggable) { 
     139    this.drags = this.drags.reject(function(d) { return d==draggable }); 
     140    if(this.drags.length == 0) { 
     141      Event.stopObserving(document, "mouseup", this.eventMouseUp); 
     142      Event.stopObserving(document, "mousemove", this.eventMouseMove); 
     143      Event.stopObserving(document, "keypress", this.eventKeypress); 
     144    } 
     145  }, 
     146   
     147  activate: function(draggable) { 
     148    window.focus(); // allows keypress events if window isn't currently focused, fails for Safari 
     149    this.activeDraggable = draggable; 
     150  }, 
     151   
     152  deactivate: function() { 
     153    this.activeDraggable = null; 
     154  }, 
     155   
     156  updateDrag: function(event) { 
     157    if(!this.activeDraggable) return; 
     158    var pointer = [Event.pointerX(event), Event.pointerY(event)]; 
     159    // Mozilla-based browsers fire successive mousemove events with 
     160    // the same coordinates, prevent needless redrawing (moz bug?) 
     161    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; 
     162    this._lastPointer = pointer; 
     163    this.activeDraggable.updateDrag(event, pointer); 
     164  }, 
     165   
     166  endDrag: function(event) { 
     167    if(!this.activeDraggable) return; 
     168    this._lastPointer = null; 
     169    this.activeDraggable.endDrag(event); 
     170    this.activeDraggable = null; 
     171  }, 
     172   
     173  keyPress: function(event) { 
     174    if(this.activeDraggable) 
     175      this.activeDraggable.keyPress(event); 
     176  }, 
     177   
    221178  addObserver: function(observer) { 
    222     this.observers.push(observer);     
    223   }, 
    224   removeObserver: function(element) {  // element instead of obsever fixes mem leaks 
    225     for(var i = 0; i < this.observers.length; i++) 
    226       if(this.observers[i].element && (this.observers[i].element == element)) 
    227         this.observers.splice(i,1); 
    228   }, 
    229   notify: function(eventName, draggable) {  // 'onStart', 'onEnd' 
    230     for(var i = 0; i < this.observers.length; i++) 
    231       this.observers[i][eventName](draggable); 
     179    this.observers.push(observer); 
     180    this._cacheObserverCallbacks(); 
     181  }, 
     182   
     183  removeObserver: function(element) {  // element instead of observer fixes mem leaks 
     184    this.observers = this.observers.reject( function(o) { return o.element==element }); 
     185    this._cacheObserverCallbacks(); 
     186  }, 
     187   
     188  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag' 
     189    if(this[eventName+'Count'] > 0) 
     190      this.observers.each( function(o) { 
     191        if(o[eventName]) o[eventName](eventName, draggable, event); 
     192      }); 
     193  }, 
     194   
     195  _cacheObserverCallbacks: function() { 
     196    ['onStart','onEnd','onDrag'].each( function(eventName) { 
     197      Draggables[eventName+'Count'] = Draggables.observers.select( 
     198        function(o) { return o[eventName]; } 
     199      ).length; 
     200    }); 
    232201  } 
    233202} 
     
    235204/*--------------------------------------------------------------------------*/ 
    236205 
    237 Draggable = Class.create(); 
     206var Draggable = Class.create(); 
    238207Draggable.prototype = { 
    239208  initialize: function(element) { 
    240209    var options = Object.extend({ 
    241210      handle: false, 
    242       starteffect: function(element) {  
    243         new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});  
     211      starteffect: function(element) { 
     212        element._opacity = Element.getOpacity(element);  
     213        new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});  
    244214      }, 
    245215      reverteffect: function(element, top_offset, left_offset) { 
    246         new Effect.MoveBy(element, -top_offset, -left_offset, {duration:0.4}); 
     216        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; 
     217        element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur}); 
    247218      }, 
    248       endeffect: function(element) {  
    249          new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});  
     219      endeffect: function(element) { 
     220        var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0 
     221        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity});  
    250222      }, 
    251223      zindex: 1000, 
    252       revert: false 
     224      revert: false, 
     225      scroll: false, 
     226      scrollSensitivity: 20, 
     227      scrollSpeed: 15, 
     228      snap: false   // false, or xy or [x,y] or function(x,y){ return [x,y] } 
    253229    }, arguments[1] || {}); 
    254      
    255     this.element      = $(element); 
    256     this.handle       = options.handle ? $(options.handle) : this.element; 
    257      
    258     Element.makePositioned(this.element); // fix IE 
    259      
    260     this.offsetX      = 0; 
    261     this.offsetY      = 0; 
    262     this.originalLeft = this.currentLeft(); 
    263     this.originalTop  = this.currentTop(); 
    264     this.originalX    = this.element.offsetLeft; 
    265     this.originalY    = this.element.offsetTop; 
    266     this.originalZ    = parseInt(this.element.style.zIndex || "0"); 
    267      
    268     this.options      = options; 
    269      
    270     this.active       = false; 
    271     this.dragging     = false;    
    272      
    273     this.eventMouseDown = this.startDrag.bindAsEventListener(this); 
    274     this.eventMouseUp   = this.endDrag.bindAsEventListener(this); 
    275     this.eventMouseMove = this.update.bindAsEventListener(this); 
    276     this.eventKeypress  = this.keyPress.bindAsEventListener(this); 
    277      
     230 
     231    this.element = $(element); 
     232     
     233    if(options.handle && (typeof options.handle == 'string')) { 
     234      var h = Element.childrenWithClassName(this.element, options.handle, true); 
     235      if(h.length>0) this.handle = h[0]; 
     236    } 
     237    if(!this.handle) this.handle = $(options.handle); 
     238    if(!this.handle) this.handle = this.element; 
     239     
     240    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) 
     241      options.scroll = $(options.scroll); 
     242 
     243    Element.makePositioned(this.element); // fix IE     
     244 
     245    this.delta    = this.currentDelta(); 
     246    this.options  = options; 
     247    this.dragging = false;    
     248 
     249    this.eventMouseDown = this.initDrag.bindAsEventListener(this); 
    278250    Event.observe(this.handle, "mousedown", this.eventMouseDown); 
    279     Event.observe(document, "mouseup", this.eventMouseUp); 
    280     Event.observe(document, "mousemove", this.eventMouseMove); 
    281     Event.observe(document, "keypress", this.eventKeypress); 
    282   }, 
     251     
     252    Draggables.register(this); 
     253  }, 
     254   
    283255  destroy: function() { 
    284256    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); 
    285     Event.stopObserving(document, "mouseup", this.eventMouseUp); 
    286     Event.stopObserving(document, "mousemove", this.eventMouseMove); 
    287     Event.stopObserving(document, "keypress", this.eventKeypress); 
    288   }, 
    289   currentLeft: function() { 
    290     return parseInt(this.element.style.left || '0'); 
    291   }, 
    292   currentTop: function() { 
    293     return parseInt(this.element.style.top || '0') 
    294   }, 
     257    Draggables.unregister(this); 
     258  }, 
     259   
     260  currentDelta: function() { 
     261    return([ 
     262      parseInt(Element.getStyle(this.element,'left') || '0'), 
     263      parseInt(Element.getStyle(this.element,'top') || '0')]); 
     264  }, 
     265   
     266  initDrag: function(event) { 
     267    if(Event.isLeftClick(event)) {     
     268      // abort on form elements, fixes a Firefox issue 
     269      var src = Event.element(event); 
     270      if(src.tagName && ( 
     271        src.tagName=='INPUT' || 
     272        src.tagName=='SELECT' || 
     273        src.tagName=='OPTION' || 
     274        src.tagName=='BUTTON' || 
     275        src.tagName=='TEXTAREA')) return; 
     276         
     277      if(this.element._revert) { 
     278        this.element._revert.cancel(); 
     279        this.element._revert = null; 
     280      } 
     281       
     282      var pointer = [Event.pointerX(event), Event.pointerY(event)]; 
     283      var pos     = Position.cumulativeOffset(this.element); 
     284      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); 
     285       
     286      Draggables.activate(this); 
     287      Event.stop(event); 
     288    } 
     289  }, 
     290   
    295291  startDrag: function(event) { 
    296     if(Event.isLeftClick(event)) { 
    297       this.active = true; 
    298        
    299       var style = this.element.style; 
    300       this.originalY = this.element.offsetTop  - this.currentTop()  - this.originalTop; 
    301       this.originalX = this.element.offsetLeft - this.currentLeft() - this.originalLeft; 
    302       this.offsetY =  event.clientY - this.originalY - this.originalTop; 
    303       this.offsetX =  event.clientX - this.originalX - this.originalLeft; 
    304        
    305       Event.stop(event); 
    306     } 
    307   }, 
     292    this.dragging = true; 
     293     
     294    if(this.options.zindex) { 
     295      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); 
     296      this.element.style.zIndex = this.options.zindex; 
     297    } 
     298     
     299    if(this.options.ghosting) { 
     300      this._clone = this.element.cloneNode(true); 
     301      Position.absolutize(this.element); 
     302      this.element.parentNode.insertBefore(this._clone, this.element); 
     303    } 
     304     
     305    if(this.options.scroll) { 
     306      if (this.options.scroll == window) { 
     307        var where = this._getWindowScroll(this.options.scroll); 
     308        this.originalScrollLeft = where.left; 
     309        this.originalScrollTop = where.top; 
     310      } else { 
     311        this.originalScrollLeft = this.options.scroll.scrollLeft; 
     312        this.originalScrollTop = this.options.scroll.scrollTop; 
     313      } 
     314    } 
     315     
     316    Draggables.notify('onStart', this, event); 
     317    if(this.options.starteffect) this.options.starteffect(this.element); 
     318  }, 
     319   
     320  updateDrag: function(event, pointer) { 
     321    if(!this.dragging) this.startDrag(event); 
     322    Position.prepare(); 
     323    Droppables.show(pointer, this.element); 
     324    Draggables.notify('onDrag', this, event); 
     325    this.draw(pointer); 
     326    if(this.options.change) this.options.change(this); 
     327     
     328    if(this.options.scroll) { 
     329      this.stopScrolling(); 
     330       
     331      var p; 
     332      if (this.options.scroll == window) { 
     333        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } 
     334      } else { 
     335        p = Position.page(this.options.scroll); 
     336        p[0] += this.options.scroll.scrollLeft; 
     337        p[1] += this.options.scroll.scrollTop; 
     338        p.push(p[0]+this.options.scroll.offsetWidth); 
     339        p.push(p[1]+this.options.scroll.offsetHeight); 
     340      } 
     341      var speed = [0,0]; 
     342      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); 
     343      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); 
     344      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); 
     345      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); 
     346      this.startScrolling(speed); 
     347    } 
     348     
     349    // fix AppleWebKit rendering 
     350    if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 
     351     
     352    Event.stop(event); 
     353  }, 
     354   
    308355  finishDrag: function(event, success) { 
    309     this.active = false; 
    310356    this.dragging = false; 
    311      
     357 
     358    if(this.options.ghosting) { 
     359      Position.relativize(this.element); 
     360      Element.remove(this._clone); 
     361      this._clone = null; 
     362    } 
     363 
    312364    if(success) Droppables.fire(event, this.element); 
    313     Draggables.notify('onEnd', this); 
    314      
     365    Draggables.notify('onEnd', this, event); 
     366 
    315367    var revert = this.options.revert; 
    316368    if(revert && typeof revert == 'function') revert = revert(this.element); 
    317        
     369     
     370    var d = this.currentDelta(); 
    318371    if(revert && this.options.reverteffect) { 
    319372      this.options.reverteffect(this.element,  
    320       this.currentTop()-this.originalTop, 
    321       this.currentLeft()-this.originalLeft); 
     373        d[1]-this.delta[1], d[0]-this.delta[0]); 
    322374    } else { 
    323       this.originalLeft = this.currentLeft(); 
    324       this.originalTop  = this.currentTop(); 
    325     } 
    326      
    327     this.element.style.zIndex = this.originalZ; 
    328        
     375      this.delta = d; 
     376    } 
     377 
     378    if(this.options.zindex) 
     379      this.element.style.zIndex = this.originalZ; 
     380 
    329381    if(this.options.endeffect)  
    330382      this.options.endeffect(this.element); 
    331        
     383 
     384    Draggables.deactivate(this); 
    332385    Droppables.reset(); 
    333386  }, 
     387   
    334388  keyPress: function(event) { 
    335     if(this.active) { 
    336       if(event.keyCode==Event.KEY_ESC) { 
    337         this.finishDrag(event, false); 
    338         Event.stop(event); 
    339       } 
    340     } 
    341   }, 
     389    if(event.keyCode!=Event.KEY_ESC) return; 
     390    this.finishDrag(event, false); 
     391    Event.stop(event); 
     392  }, 
     393   
    342394  endDrag: function(event) { 
    343     if(this.active && this.dragging) { 
    344       this.finishDrag(event, true); 
    345       Event.stop(event); 
    346     } 
    347     this.active = false; 
    348     this.dragging = false; 
    349   }, 
    350   draw: function(event) { 
     395    if(!this.dragging) return; 
     396    this.stopScrolling(); 
     397    this.finishDrag(event, true); 
     398    Event.stop(event); 
     399  }, 
     400   
     401  draw: function(point) { 
     402    var pos = Position.cumulativeOffset(this.element); 
     403    var d = this.currentDelta(); 
     404    pos[0] -= d[0]; pos[1] -= d[1]; 
     405     
     406    if(this.options.scroll && (this.options.scroll != window)) { 
     407      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; 
     408      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; 
     409    } 
     410     
     411    var p = [0,1].map(function(i){  
     412      return (point[i]-pos[i]-this.offset[i])  
     413    }.bind(this)); 
     414     
     415    if(this.options.snap) { 
     416      if(typeof this.options.snap == 'function') { 
     417        p = this.options.snap(p[0],p[1],this); 
     418      } else { 
     419      if(this.options.snap instanceof Array) { 
     420        p = p.map( function(v, i) { 
     421          return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) 
     422      } else { 
     423        p = p.map( function(v) { 
     424          return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) 
     425      } 
     426    }} 
     427     
    351428    var style = this.element.style; 
    352     this.originalX = this.element.offsetLeft - this.currentLeft() - this.originalLeft; 
    353     this.originalY = this.element.offsetTop  - this.currentTop()  - this.originalTop; 
    354429    if((!this.options.constraint) || (this.options.constraint=='horizontal')) 
    355       style.left = ((event.clientX - this.originalX) - this.offsetX) + "px"; 
     430      style.left = p[0] + "px"; 
    356431    if((!this.options.constraint) || (this.options.constraint=='vertical')) 
    357       style.top  = ((event.clientY - this.originalY) - this.offsetY) + "px"; 
     432      style.top  = p[1] + "px"; 
    358433    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering 
    359434  }, 
    360   update: function(event) { 
    361    if(this.active) { 
    362       if(!this.dragging) { 
    363         var style = this.element.style; 
    364         this.dragging = true; 
    365         if(style.position=="") style.position = "relative"; 
    366         style.zIndex = this.options.zindex; 
    367         Draggables.notify('onStart', this); 
    368         if(this.options.starteffect) this.options.starteffect(this.element); 
    369       } 
    370        
    371       Droppables.show(event, this.element); 
    372       this.draw(event); 
    373       if(this.options.change) this.options.change(this); 
    374        
    375       // fix AppleWebKit rendering 
    376       if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);  
    377        
    378       Event.stop(event); 
    379    } 
     435   
     436  stopScrolling: function() { 
     437    if(this.scrollInterval) { 
     438      clearInterval(this.scrollInterval); 
     439      this.scrollInterval = null; 
     440      Draggables._lastScrollPointer = null; 
     441    } 
     442  }, 
     443   
     444  startScrolling: function(speed) { 
     445    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; 
     446    this.lastScrolled = new Date(); 
     447    this.scrollInterval = setInterval(this.scroll.bind(this), 10); 
     448  }, 
     449   
     450  scroll: function() { 
     451    var current = new Date(); 
     452    var delta = current - this.lastScrolled; 
     453    this.lastScrolled = current; 
     454    if(this.options.scroll == window) { 
     455      with (this._getWindowScroll(this.options.scroll)) { 
     456        if (this.scrollSpeed[0] || this.scrollSpeed[1]) { 
     457          var d = delta / 1000; 
     458          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); 
     459        } 
     460      } 
     461    } else { 
     462      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; 
     463      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000; 
     464    } 
     465     
     466    Position.prepare(); 
     467    Droppables.show(Draggables._lastPointer, this.element); 
     468    Draggables.notify('onDrag', this); 
     469    Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); 
     470    Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; 
     471    Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; 
     472    if (Draggables._lastScrollPointer[0] < 0) 
     473      Draggables._lastScrollPointer[0] = 0; 
     474    if (Draggables._lastScrollPointer[1] < 0) 
     475      Draggables._lastScrollPointer[1] = 0; 
     476    this.draw(Draggables._lastScrollPointer); 
     477     
     478    if(this.options.change) this.options.change(this); 
     479  }, 
     480   
     481  _getWindowScroll: function(w) { 
     482    var T, L, W, H; 
     483    with (w.document) { 
     484      if (w.document.documentElement && documentElement.scrollTop) { 
     485        T = documentElement.scrollTop; 
     486        L = documentElement.scrollLeft; 
     487      } else if (w.document.body) { 
     488        T = body.scrollTop; 
     489        L = body.scrollLeft; 
     490      } 
     491      if (w.innerWidth) { 
     492        W = w.innerWidth; 
     493        H = w.innerHeight; 
     494      } else if (w.document.documentElement && documentElement.clientWidth) { 
     495        W = documentElement.clientWidth; 
     496        H = documentElement.clientHeight; 
     497      } else { 
     498        W = body.offsetWidth; 
     499        H = body.offsetHeight 
     500      } 
     501    } 
     502    return { top: T, left: L, width: W, height: H }; 
    380503  } 
    381504} 
     
    383506/*--------------------------------------------------------------------------*/ 
    384507 
    385 SortableObserver = Class.create(); 
     508var SortableObserver = Class.create(); 
    386509SortableObserver.prototype = { 
    387510  initialize: function(element, observer) { 
     
    390513    this.lastValue = Sortable.serialize(this.element); 
    391514  }, 
     515   
    392516  onStart: function() { 
    393517    this.lastValue = Sortable.serialize(this.element); 
    394518  }, 
    395   onEnd: function() {     
     519   
     520  onEnd: function() { 
     521    Sortable.unmark(); 
    396522    if(this.lastValue != Sortable.serialize(this.element)) 
    397523      this.observer(this.element) 
     
    399525} 
    400526 
    401 Sortable = { 
    402   sortables: new Array(), 
    403   options: function(element){ 
    404     var element = $(element); 
    405     for(var i=0;i<this.sortables.length;i++) 
    406       if(this.sortables[i].element == element) 
    407         return this.sortables[i]; 
    408     return null;         
    409   }, 
     527var Sortable = { 
     528  sortables: {}, 
     529   
     530  _findRootElement: function(element) { 
     531    while (element.tagName != "BODY") {   
     532      if(element.id && Sortable.sortables[element.id]) return element; 
     533      element = element.parentNode; 
     534    } 
     535  }, 
     536 
     537  options: function(element) { 
     538    element = Sortable._findRootElement($(element)); 
     539    if(!element) return; 
     540    return Sortable.sortables[element.id]; 
     541  }, 
     542   
    410543  destroy: function(element){ 
    411     var element = $(element); 
    412     for(var i=0;i<this.sortables.length;i++) { 
    413       if(this.sortables[i].element == element) { 
    414         var s = this.sortables[i]; 
    415         Draggables.removeObserver(s.element); 
    416         for(var j=0;j<s.droppables.length;j++) 
    417           Droppables.remove(s.droppables[j]); 
    418         for(var j=0;j<s.draggables.length;j++) 
    419           s.draggables[j].destroy(); 
    420         this.sortables.splice(i,1); 
    421       } 
    422     } 
    423   }, 
     544    var s = Sortable.options(element); 
     545     
     546    if(s) { 
     547      Draggables.removeObserver(s.element); 
     548      s.droppables.each(function(d){ Droppables.remove(d) }); 
     549      s.draggables.invoke('destroy'); 
     550       
     551      delete Sortable.sortables[s.element.id]; 
     552    } 
     553  }, 
     554 
    424555  create: function(element) { 
    425     var element = $(element); 
     556    element = $(element); 
    426557    var options = Object.extend({  
    427558      element:     element, 
    428559      tag:         'li',       // assumes li children, override with tag: 'tagname' 
     560      dropOnEmpty: false, 
     561      tree:        false, 
     562      treeTag:     'ul', 
    429563      overlap:     'vertical', // one of 'vertical', 'horizontal' 
    430564      constraint:  'vertical', // one of 'vertical', 'horizontal', false 
     
    433567      only:        false, 
    434568      hoverclass:  null, 
    435       onChange:    function() {}, 
    436       onUpdate:    function() {} 
     569      ghosting:    false, 
     570      scroll:      false, 
     571      scrollSensitivity: 20, 
     572      scrollSpeed: 15, 
     573      format:      /^[^_]*_(.*)$/, 
     574      onChange:    Prototype.emptyFunction, 
     575      onUpdate:    Prototype.emptyFunction 
    437576    }, arguments[1] || {}); 
    438      
     577 
    439578    // clear any old sortable with same element 
    440579    this.destroy(element); 
    441      
     580 
    442581    // build options for the draggables 
    443582    var options_for_draggable = { 
    444583      revert:      true, 
     584      scroll:      options.scroll, 
     585      scrollSpeed: options.scrollSpeed, 
     586      scrollSensitivity: options.scrollSensitivity, 
     587      ghosting:    options.ghosting, 
    445588      constraint:  options.constraint, 
    446       handle:      handle }; 
     589      handle:      options.handle }; 
     590 
    447591    if(options.starteffect) 
    448592      options_for_draggable.starteffect = options.starteffect; 
     593 
    449594    if(options.reverteffect) 
    450595      options_for_draggable.reverteffect = options.reverteffect; 
     596    else 
     597      if(options.ghosting) options_for_draggable.reverteffect = function(element) { 
     598        element.style.top  = 0; 
     599        element.style.left = 0; 
     600      }; 
     601 
    451602    if(options.endeffect) 
    452603      options_for_draggable.endeffect = options.endeffect; 
     604 
    453605    if(options.zindex) 
    454606      options_for_draggable.zindex = options.zindex; 
    455      
     607 
    456608    // build options for the droppables   
    457609    var options_for_droppable = { 
    458610      overlap:     options.overlap, 
    459611      containment: options.containment, 
     612      tree:        options.tree, 
    460613      hoverclass:  options.hoverclass, 
    461       onHover: function(element, dropon, overlap) {  
    462         if(overlap>0.5) { 
    463           if(dropon.previousSibling != element) { 
    464             var oldParentNode = element.parentNode; 
    465             element.style.visibility = "hidden"; // fix gecko rendering 
    466             dropon.parentNode.insertBefore(element, dropon); 
    467             if(dropon.parentNode!=oldParentNode && oldParentNode.sortable)  
    468               oldParentNode.sortable.onChange(element); 
    469             if(dropon.parentNode.sortable) 
    470               dropon.parentNode.sortable.onChange(element); 
    471           } 
    472         } else {                 
    473           var nextElement = dropon.nextSibling || null; 
    474           if(nextElement != element) { 
    475             var oldParentNode = element.parentNode; 
    476             element.style.visibility = "hidden"; // fix gecko rendering 
    477             dropon.parentNode.insertBefore(element, nextElement); 
    478             if(dropon.parentNode!=oldParentNode && oldParentNode.sortable)  
    479               oldParentNode.sortable.onChange(element); 
    480             if(dropon.parentNode.sortable) 
    481               dropon.parentNode.sortable.onChange(element); 
     614      onHover:     Sortable.onHover 
     615      //greedy:      !options.dropOnEmpty 
     616    } 
     617     
     618    var options_for_tree = { 
     619      onHover:      Sortable.onEmptyHover, 
     620      overlap:      options.overlap, 
     621      containment:  options.containment, 
     622      hoverclass:   options.hoverclass 
     623    } 
     624 
     625    // fix for gecko engine 
     626    Element.cleanWhitespace(element);  
     627 
     628    options.draggables = []; 
     629    options.droppables = []; 
     630 
     631    // drop on empty handling 
     632    if(options.dropOnEmpty || options.tree) { 
     633      Droppables.add(element, options_for_tree); 
     634      options.droppables.push(element); 
     635    } 
     636 
     637    (this.findElements(element, options) || []).each( function(e) { 
     638      // handles are per-draggable 
     639      var handle = options.handle ?  
     640        Element.childrenWithClassName(e, options.handle)[0] : e;     
     641      options.draggables.push( 
     642        new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); 
     643      Droppables.add(e, options_for_droppable); 
     644      if(options.tree) e.treeNode = element; 
     645      options.droppables.push(e);       
     646    }); 
     647     
     648    if(options.tree) { 
     649      (Sortable.findTreeElements(element, options) || []).each( function(e) { 
     650        Droppables.add(e, options_for_tree); 
     651        e.treeNode = element; 
     652        options.droppables.push(e); 
     653      }); 
     654    } 
     655 
     656    // keep reference 
     657    this.sortables[element.id] = options; 
     658 
     659    // for onupdate 
     660    Draggables.addObserver(new SortableObserver(element, options.onUpdate)); 
     661 
     662  }, 
     663 
     664  // return all suitable-for-sortable elements in a guaranteed order 
     665  findElements: function(element, options) { 
     666    return Element.findChildren( 
     667      element, options.only, options.tree ? true : false, options.tag); 
     668  }, 
     669   
     670  findTreeElements: function(element, options) { 
     671    return Element.findChildren( 
     672      element, options.only, options.tree ? true : false, options.treeTag); 
     673  }, 
     674 
     675  onHover: function(element, dropon, overlap) { 
     676    if(Element.isParent(dropon, element)) return; 
     677 
     678    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { 
     679      return; 
     680    } else if(overlap>0.5) { 
     681      Sortable.mark(dropon, 'before'); 
     682      if(dropon.previousSibling != element) { 
     683        var oldParentNode = element.parentNode; 
     684        element.style.visibility = "hidden"; // fix gecko rendering 
     685        dropon.parentNode.insertBefore(element, dropon); 
     686        if(dropon.parentNode!=oldParentNode)  
     687          Sortable.options(oldParentNode).onChange(element); 
     688        Sortable.options(dropon.parentNode).onChange(element); 
     689      } 
     690    } else { 
     691      Sortable.mark(dropon, 'after'); 
     692      var nextElement = dropon.nextSibling || null; 
     693      if(nextElement != element) { 
     694        var oldParentNode = element.parentNode; 
     695        element.style.visibility = "hidden"; // fix gecko rendering 
     696        dropon.parentNode.insertBefore(element, nextElement); 
     697        if(dropon.parentNode!=oldParentNode)  
     698          Sortable.options(oldParentNode).onChange(element); 
     699        Sortable.options(dropon.parentNode).onChange(element); 
     700      } 
     701    } 
     702  }, 
     703   
     704  onEmptyHover: function(element, dropon, overlap) { 
     705    var oldParentNode = element.parentNode; 
     706    var droponOptions = Sortable.options(dropon); 
     707         
     708    if(!Element.isParent(dropon, element)) { 
     709      var index; 
     710       
     711      var children = Sortable.findElements(dropon, {tag: droponOptions.tag}); 
     712      var child = null; 
     713             
     714      if(children) { 
     715        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); 
     716         
     717        for (index = 0; index < children.length; index += 1) { 
     718          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { 
     719            offset -= Element.offsetSize (children[index], droponOptions.overlap); 
     720          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { 
     721            child = index + 1 < children.length ? children[index + 1] : null; 
     722            break; 
     723          } else { 
     724            child = children[index]; 
     725            break; 
    482726          } 
    483727        } 
    484728      } 
    485     } 
    486  
    487     // fix for gecko engine 
    488     Element.cleanWhitespace(element);  
    489      
    490     options.draggables = []; 
    491     options.droppables = []; 
    492      
    493     // make it so  
    494     var elements = element.childNodes; 
    495     for (var i = 0; i < elements.length; i++)  
    496       if(elements[i].tagName && elements[i].tagName==options.tag.toUpperCase() && 
    497         (!options.only || (Element.Class.has(elements[i], options.only)))) { 
    498          
    499         // handles are per-draggable 
    500         var handle = options.handle ?  
    501           Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i]; 
    502          
    503         options.draggables.push(new Draggable(elements[i], Object.extend(options_for_draggable, { handle: handle }))); 
    504          
    505         Droppables.add(elements[i], options_for_droppable); 
    506         options.droppables.push(elements[i]); 
    507          
    508       } 
    509        
    510     // keep reference 
    511     this.sortables.push(options); 
    512      
    513     // for onupdate 
    514     Draggables.addObserver(new SortableObserver(element, options.onUpdate)); 
    515  
    516   }, 
    517   serialize: function(element) { 
    518     var element = $(element); 
     729       
     730      dropon.insertBefore(element, child); 
     731       
     732      Sortable.options(oldParentNode).onChange(element); 
     733      droponOptions.onChange(element); 
     734    } 
     735  }, 
     736 
     737  unmark: function() { 
     738    if(Sortable._marker) Element.hide(Sortable._marker); 
     739  }, 
     740 
     741  mark: function(dropon, position) { 
     742    // mark on ghosting only 
     743    var sortable = Sortable.options(dropon.parentNode); 
     744    if(sortable && !sortable.ghosting) return;  
     745 
     746    if(!Sortable._marker) { 
     747      Sortable._marker = $('dropmarker') || document.createElement('DIV'); 
     748      Element.hide(Sortable._marker); 
     749      Element.addClassName(Sortable._marker, 'dropmarker'); 
     750      Sortable._marker.style.position = 'absolute'; 
     751      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); 
     752    }     
     753    var offsets = Position.cumulativeOffset(dropon); 
     754    Sortable._marker.style.left = offsets[0] + 'px'; 
     755    Sortable._marker.style.top = offsets[1] + 'px'; 
     756     
     757    if(position=='after') 
     758      if(sortable.overlap == 'horizontal')  
     759        Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px'; 
     760      else 
     761        Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; 
     762     
     763    Element.show(Sortable._marker); 
     764  }, 
     765   
     766  _tree: function(element, options, parent) { 
     767    var children = Sortable.findElements(element, options) || []; 
     768   
     769    for (var i = 0; i < children.length; ++i) { 
     770      var match = children[i].id.match(options.format); 
     771 
     772      if (!match) continue; 
     773       
     774      var child = { 
     775        id: encodeURIComponent(match ? match[1] : null), 
     776        element: element, 
     777        parent: parent, 
     778        children: new Array, 
     779        position: parent.children.length, 
     780        container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase()) 
     781      } 
     782       
     783      /* Get the element containing the children and recurse over it */ 
     784      if (child.container) 
     785        this._tree(child.container, options, child) 
     786       
     787      parent.children.push (child); 
     788    } 
     789 
     790    return parent;  
     791  }, 
     792 
     793  /* Finds the first element of the given tag type within a parent element. 
     794    Used for finding the first LI[ST] within a L[IST]I[TEM].*/ 
     795  _findChildrenElement: function (element, containerTag) { 
     796    if (element && element.hasChildNodes) 
     797      for (var i = 0; i < element.childNodes.length; ++i) 
     798        if (element.childNodes[i].tagName == containerTag) 
     799          return element.childNodes[i]; 
     800   
     801    return null; 
     802  }, 
     803 
     804  tree: function(element) { 
     805    element = $(element); 
    519806    var sortableOptions = this.options(element); 
    520807    var options = Object.extend({ 
    521       tag:  sortableOptions.tag, 
     808      tag: sortableOptions.tag, 
     809      treeTag: sortableOptions.treeTag, 
    522810      only: sortableOptions.only, 
    523       name: element.id 
     811      name: element.id, 
     812      format: sortableOptions.format 
    524813    }, arguments[1] || {}); 
    525814     
    526     var items = $(element).childNodes; 
    527     var queryComponents = new Array(); 
    528   
    529     for(var i=0; i<items.length; i++) 
    530       if(items[i].tagName && items[i].tagName==options.tag.toUpperCase() && 
    531         (!options.only || (Element.Class.has(items[i], options.only)))) 
    532         queryComponents.push( 
    533           encodeURIComponent(options.name) + "[]=" +  
    534           encodeURIComponent(items[i].id.split("_")[1])); 
    535  
    536     return queryComponents.join("&"); 
     815    var root = { 
     816      id: null, 
     817      parent: null, 
     818      children: new Array, 
     819      container: element, 
     820      position: 0 
     821    } 
     822     
     823    return Sortable._tree (element, options, root); 
     824  }, 
     825 
     826  /* Construct a [i] index for a particular node */ 
     827  _constructIndex: function(node) { 
     828    var index = ''; 
     829    do { 
     830      if (node.id) index = '[' + node.position + ']' + index; 
     831    } while ((node = node.parent) != null); 
     832    return index; 
     833  }, 
     834 
     835  sequence: function(element) { 
     836    element = $(element); 
     837    var options = Object.extend(this.options(element), arguments[1] || {}); 
     838     
     839    return $(this.findElements(element, options) || []).map( function(item) { 
     840      return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; 
     841    }); 
     842  }, 
     843 
     844  setSequence: function(element, new_sequence) { 
     845    element = $(element); 
     846    var options = Object.extend(this.options(element), arguments[2] || {}); 
     847     
     848    var nodeMap = {}; 
     849    this.findElements(element, options).each( function(n) { 
     850        if (n.id.match(options.format)) 
     851            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; 
     852        n.parentNode.removeChild(n); 
     853    }); 
     854    
     855    new_sequence.each(function(ident) { 
     856      var n = nodeMap[ident]; 
     857      if (n) { 
     858        n[1].appendChild(n[0]); 
     859        delete nodeMap[ident]; 
     860      } 
     861    }); 
     862  }, 
     863   
     864  serialize: function(element) { 
     865    element = $(element); 
     866    var options = Object.extend(Sortable.options(element), arguments[1] || {}); 
     867    var name = encodeURIComponent( 
     868      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); 
     869     
     870    if (options.tree) { 
     871      return Sortable.tree(element, arguments[1]).children.map( function (item) { 
     872        return [name + Sortable._constructIndex(item) + "=" +  
     873                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); 
     874      }).flatten().join('&'); 
     875    } else { 
     876      return Sortable.sequence(element, arguments[1]).map( function(item) { 
     877        return name + "[]=" + encodeURIComponent(item); 
     878      }).join('&'); 
     879    } 
    537880  } 
    538 }  
     881} 
     882 
     883/* Returns true if child is contained within element */ 
     884Element.isParent = function(child, element) { 
     885  if (!child.parentNode || child == element) return false; 
     886 
     887  if (child.parentNode == element) return true; 
     888 
     889  return Element.isParent(child.parentNode, element); 
     890} 
     891 
     892Element.findChildren = function(element, only, recursive, tagName) {     
     893  if(!element.hasChildNodes()) return null; 
     894  tagName = tagName.toUpperCase(); 
     895  if(only) only = [only].flatten(); 
     896  var elements = []; 
     897  $A(element.childNodes).each( function(e) { 
     898    if(e.tagName && e.tagName.toUpperCase()==tagName && 
     899      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) 
     900        elements.push(e); 
     901    if(recursive) { 
     902      var grandchildren = Element.findChildren(e, only, recursive, tagName); 
     903      if(grandchildren) elements.push(grandchildren); 
     904    } 
     905  }); 
     906 
     907  return (elements.length>0 ? elements.flatten() : []); 
     908} 
     909 
     910Element.offsetSize = function (element, type) { 
     911  if (type == 'vertical' || type == 'height') 
     912    return element.offsetHeight; 
     913  else 
     914    return element.offsetWidth; 
     915} 
  • trunk/trax/data/public/javascripts/effects.js

    r182 r226  
    1 // $Id$ 
    21// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 
    3 // 
    4 // Parts (c) 2005 Justin Palmer (http://encytemedia.com/) 
    5 // Parts (c) 2005 Mark Pilgrim (http://diveintomark.org/) 
    6 //  
    7 // Permission is hereby granted, free of charge, to any person obtaining 
    8 // a copy of this software and associated documentation files (the 
    9 // "Software"), to deal in the Software without restriction, including 
    10 // without limitation the rights to use, copy, modify, merge, publish, 
    11 // distribute, sublicense, and/or sell copies of the Software, and to 
    12 // permit persons to whom the Software is furnished to do so, subject to 
    13 // the following conditions: 
    14 //  
    15 // The above copyright notice and this permission notice shall be 
    16 // included in all copies or substantial portions of the Software. 
    17 //  
    18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
    19 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
    20 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
    21 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
    22 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
    23 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
    24 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
    25  
    26  
    27 Effect = {} 
    28 Effect2 = Effect; // deprecated 
     2// Contributors: 
     3//  Justin Palmer (http://encytemedia.com/) 
     4//  Mark Pilgrim (http://diveintomark.org/) 
     5//  Martin Bialasinki 
     6//   
     7// See scriptaculous.js for full license.   
     8 
     9// converts rgb() and #xxx to #xxxxxx format,   
     10// returns self (or first argument) if not convertable   
     11String.prototype.parseColor = function() {   
     12  var color = '#';   
     13  if(this.slice(0,4) == 'rgb(') {   
     14    var cols = this.slice(4,this.length-1).split(',');   
     15    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);   
     16  } else {   
     17    if(this.slice(0,1) == '#') {   
     18      if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();   
     19      if(this.length==7) color = this.toLowerCase();   
     20    }   
     21  }   
     22  return(color.length==7 ? color : (arguments[0] || this));   
     23} 
     24 
     25/*--------------------------------------------------------------------------*/ 
     26 
     27Element.collectTextNodes = function(element) {   
     28  return $A($(element).childNodes).collect( function(node) { 
     29    return (node.nodeType==3 ? node.nodeValue :  
     30      (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); 
     31  }).flatten().join(''); 
     32} 
     33 
     34Element.collectTextNodesIgnoreClass = function(element, className) {   
     35  return $A($(element).childNodes).collect( function(node) { 
     36    return (node.nodeType==3 ? node.nodeValue :  
     37      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?  
     38        Element.collectTextNodesIgnoreClass(node, className) : '')); 
     39  }).flatten().join(''); 
     40} 
     41 
     42Element.setContentZoom = function(element, percent) { 
     43  element = $(element);   
     44  Element.setStyle(element, {fontSize: (percent/100) + 'em'});    
     45  if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 
     46} 
     47 
     48Element.getOpacity = function(element){   
     49  var opacity; 
     50  if (opacity = Element.getStyle(element, 'opacity'))   
     51    return parseFloat(opacity);   
     52  if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))   
     53    if(opacity[1]) return parseFloat(opacity[1]) / 100;   
     54  return 1.0;   
     55} 
     56 
     57Element.setOpacity = function(element, value){   
     58  element= $(element);   
     59  if (value == 1){ 
     60    Element.setStyle(element, { opacity:  
     61      (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ?  
     62      0.999999 : null }); 
     63    if(/MSIE/.test(navigator.userAgent))   
     64      Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});   
     65  } else {   
     66    if(value < 0.00001) value = 0;   
     67    Element.setStyle(element, {opacity: value}); 
     68    if(/MSIE/.test(navigator.userAgent))   
     69     Element.setStyle(element,  
     70       { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + 
     71                 'alpha(opacity='+value*100+')' });   
     72  } 
     73 
     74  
     75Element.getInlineOpacity = function(element){   
     76  return $(element).style.opacity || ''; 
     77 
     78 
     79Element.childrenWithClassName = function(element, className, findFirst) { 
     80  var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)"); 
     81  var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) {  
     82    return (c.className && c.className.match(classNameRegExp)); 
     83  }); 
     84  if(!results) results = []; 
     85  return results; 
     86} 
     87 
     88Element.forceRerendering = function(element) { 
     89  try { 
     90    element = $(element); 
     91    var n = document.createTextNode(' '); 
     92    element.appendChild(n); 
     93    element.removeChild(n); 
     94  } catch(e) { } 
     95}; 
     96 
     97/*--------------------------------------------------------------------------*/ 
     98 
     99Array.prototype.call = function() { 
     100  var args = arguments; 
     101  this.each(function(f){ f.apply(this, args) }); 
     102} 
     103 
     104/*--------------------------------------------------------------------------*/ 
     105 
     106var Effect = { 
     107  tagifyText: function(element) { 
     108    var tagifyStyle = 'position:relative'; 
     109    if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1'; 
     110    element = $(element); 
     111    $A(element.childNodes).each( function(child) { 
     112      if(child.nodeType==3) { 
     113        child.nodeValue.toArray().each( function(character) { 
     114          element.insertBefore( 
     115            Builder.node('span',{style: tagifyStyle}, 
     116              character == ' ' ? String.fromCharCode(160) : character),  
     117              child); 
     118        }); 
     119        Element.remove(child); 
     120      } 
     121    }); 
     122  }, 
     123  multiple: function(element, effect) { 
     124    var elements; 
     125    if(((typeof element == 'object') ||  
     126        (typeof element == 'function')) &&  
     127       (element.length)) 
     128      elements = element; 
     129    else 
     130      elements = $(element).childNodes; 
     131       
     132    var options = Object.extend({ 
     133      speed: 0.1, 
     134      delay: 0.0 
     135    }, arguments[2] || {}); 
     136    var masterDelay = options.delay; 
     137 
     138    $A(elements).each( function(element, index) { 
     139      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); 
     140    }); 
     141  }, 
     142  PAIRS: { 
     143    'slide':  ['SlideDown','SlideUp'], 
     144    'blind':  ['BlindDown','BlindUp'], 
     145    'appear': ['Appear','Fade'] 
     146  }, 
     147  toggle: function(element, effect) { 
     148    element = $(element); 
     149    effect = (effect || 'appear').toLowerCase(); 
     150    var options = Object.extend({ 
     151      queue: { position:'end', scope:(element.id || 'global'), limit: 1 } 
     152    }, arguments[2] || {}); 
     153    Effect[element.visible() ?  
     154      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); 
     155  } 
     156}; 
     157 
     158var Effect2 = Effect; // deprecated 
    29159 
    30160/* ------------- transitions ------------- */ 
     
    42172} 
    43173Effect.Transitions.flicker = function(pos) { 
    44   return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random(0.25); 
     174  return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; 
    45175} 
    46176Effect.Transitions.wobble = function(pos) { 
     
    58188} 
    59189 
    60 /* ------------- element ext -------------- */ 
    61  
    62 Element.makePositioned = function(element) { 
    63   element = $(element); 
    64   if(element.style.position == "") 
    65     element.style.position = "relative"; 
    66 } 
    67  
    68 Element.makeClipping = function(element) { 
    69   element = $(element); 
    70   element._overflow = element.style.overflow || 'visible'; 
    71   if(element._overflow!='hidden') element.style.overflow = 'hidden'; 
    72 } 
    73  
    74 Element.undoClipping = function(element) { 
    75   element = $(element); 
    76   if(element._overflow!='hidden') element.style.overflow = element._overflow; 
    77 } 
    78  
    79190/* ------------- core effects ------------- */ 
     191 
     192Effect.ScopedQueue = Class.create(); 
     193Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { 
     194  initialize: function() { 
     195    this.effects  = []; 
     196    this.interval = null; 
     197  }, 
     198  _each: function(iterator) { 
     199    this.effects._each(iterator); 
     200  }, 
     201  add: function(effect) { 
     202    var timestamp = new Date().getTime(); 
     203     
     204    var position = (typeof effect.options.queue == 'string') ?  
     205      effect.options.queue : effect.options.queue.position; 
     206     
     207    switch(position) { 
     208      case 'front': 
     209        // move unstarted effects after this effect   
     210        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { 
     211            e.startOn  += effect.finishOn; 
     212            e.finishOn += effect.finishOn; 
     213          }); 
     214        break; 
     215      case 'end': 
     216        // start effect after last queued effect has finished 
     217        timestamp = this.effects.pluck('finishOn').max() || timestamp; 
     218        break; 
     219    } 
     220     
     221    effect.startOn  += timestamp; 
     222    effect.finishOn += timestamp; 
     223 
     224    if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) 
     225      this.effects.push(effect); 
     226     
     227    if(!this.interval)  
     228      this.interval = setInterval(this.loop.bind(this), 40); 
     229  }, 
     230  remove: function(effect) { 
     231    this.effects = this.effects.reject(function(e) { return e==effect }); 
     232    if(this.effects.length == 0) { 
     233      clearInterval(this.interval); 
     234      this.interval = null; 
     235    } 
     236  }, 
     237  loop: function() { 
     238    var timePos = new Date().getTime(); 
     239    this.effects.invoke('loop', timePos); 
     240  } 
     241}); 
     242 
     243Effect.Queues = { 
     244  instances: $H(), 
     245  get: function(queueName) { 
     246    if(typeof queueName != 'string') return queueName; 
     247     
     248    if(!this.instances[queueName]) 
     249      this.instances[queueName] = new Effect.ScopedQueue(); 
     250       
     251    return this.instances[queueName]; 
     252  } 
     253} 
     254Effect.Queue = Effect.Queues.get('global'); 
     255 
     256Effect.DefaultOptions = { 
     257  transition: Effect.Transitions.sinoidal, 
     258  duration:   1.0,   // seconds 
     259  fps:        25.0,  // max. 25fps due to Effect.Queue implementation 
     260  sync:       false, // true for combining 
     261  from:       0.0, 
     262  to:         1.0, 
     263  delay:      0.0, 
     264  queue:      'parallel' 
     265} 
    80266 
    81267Effect.Base = function() {}; 
    82268Effect.Base.prototype = { 
    83   setOptions: function(options) { 
    84     this.options = Object.extend({ 
    85       transition: Effect.Transitions.sinoidal, 
    86       duration:   1.0,   // seconds 
    87       fps:        25.0,  // max. 100fps 
    88       sync:       false, // true for combining 
    89       from:       0.0, 
    90       to:         1.0 
    91     }, options || {}); 
    92   }, 
     269  position: null, 
    93270  start: function(options) { 
    94     this.setOptions(options || {}); 
     271    this.options      = Object.extend(Object.extend({},Effect.DefaultOptions), options || {}); 
    95272    this.currentFrame = 0; 
    96     this.startOn      = new Date().getTime(); 
     273    this.state        = 'idle'; 
     274    this.startOn      = this.options.delay*1000; 
    97275    this.finishOn     = this.startOn + (this.options.duration*1000); 
    98     if(this.options.beforeStart) this.options.beforeStart(this); 
    99     if(!this.options.sync) this.loop();   
    100   }, 
    101   loop: function() { 
    102     var timePos = new Date().getTime(); 
    103     if(timePos >= this.finishOn) { 
    104       this.render(1.0); 
    105       if(this.finish) this.finish();  
    106       if(this.options.afterFinish) this.options.afterFinish(this); 
    107       return;   
     276    this.event('beforeStart'); 
     277    if(!this.options.sync) 
     278      Effect.Queues.get(typeof this.options.queue == 'string' ?  
     279        'global' : this.options.queue.scope).add(this); 
     280  }, 
     281  loop: function(timePos) { 
     282    if(timePos >= this.startOn) { 
     283      if(timePos >= this.finishOn) { 
     284        this.render(1.0); 
     285        this.cancel(); 
     286        this.event('beforeFinish'); 
     287        if(this.finish) this.finish();  
     288        this.event('afterFinish'); 
     289        return;   
     290      } 
     291      var pos   = (timePos - this.startOn) / (this.finishOn - this.startOn); 
     292      var frame = Math.round(pos * this.options.fps * this.options.duration); 
     293      if(frame > this.currentFrame) { 
     294        this.render(pos); 
     295        this.currentFrame = frame; 
     296      } 
    108297    } 
    109     var pos   = (timePos - this.startOn) / (this.finishOn - this.startOn); 
    110     var frame = Math.round(pos * this.options.fps * this.options.duration); 
    111     if(frame > this.currentFrame) { 
    112       this.render(pos); 
    113       this.currentFrame = frame; 
     298  }, 
     299  render: function(pos) { 
     300    if(this.state == 'idle') { 
     301      this.state = 'running'; 
     302      this.event('beforeSetup'); 
     303      if(this.setup) this.setup(); 
     304      this.event('afterSetup'); 
    114305    } 
    115     this.timeout = setTimeout(this.loop.bind(this), 10); 
    116   }, 
    117   render: function(pos) { 
    118     if(this.options.transition) pos = this.options.transition(pos); 
    119     pos *= (this.options.to-this.options.from); 
    120     pos += this.options.from;  
    121     if(this.options.beforeUpdate) this.options.beforeUpdate(this); 
    122     if(this.update) this.update(pos); 
    123     if(this.options.afterUpdate) this.options.afterUpdate(this);   
     306    if(this.state == 'running') { 
     307      if(this.options.transition) pos = this.options.transition(pos); 
     308      pos *= (this.options.to-this.options.from); 
     309      pos += this.options.from; 
     310      this.position = pos; 
     311      this.event('beforeUpdate'); 
     312      if(this.update) this.update(pos); 
     313      this.event('afterUpdate'); 
     314    } 
    124315  }, 
    125316  cancel: function() { 
    126     if(this.timeout) clearTimeout(this.timeout); 
     317    if(!this.options.sync) 
     318      Effect.Queues.get(typeof this.options.queue == 'string' ?  
     319        'global' : this.options.queue.scope).remove(this); 
     320    this.state = 'finished'; 
     321  }, 
     322  event: function(eventName) { 
     323    if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); 
     324    if(this.options[eventName]) this.options[eventName](this); 
     325  }, 
     326  inspect: function() { 
     327    return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>'; 
    127328  } 
    128329} 
     
    135336  }, 
    136337  update: function(position) { 
    137     for (var i = 0; i < this.effects.length; i++) 
    138       this.effects[i].render(position);   
     338    this.effects.invoke('render', position); 
    139339  }, 
    140340  finish: function(position) { 
    141     for (var i = 0; i < this.effects.length; i++) 
    142       if(this.effects[i].finish) this.effects[i].finish(position); 
     341    this.effects.each( function(effect) { 
     342      effect.render(1.0); 
     343      effect.cancel(); 
     344      effect.event('beforeFinish'); 
     345      if(effect.finish) effect.finish(position); 
     346      effect.event('afterFinish'); 
     347    }); 
    143348  } 
    144349}); 
    145350 
    146 // Internet Explorer caveat: works only on elements the have 
    147 // a 'layout', meaning having a given width or height.  
    148 // There is no way to safely set this automatically. 
    149351Effect.Opacity = Class.create(); 
    150352Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { 
    151353  initialize: function(element) { 
    152354    this.element = $(element); 
    153     options = Object.extend({ 
    154       from: 0.0, 
     355    // make this work on IE on elements without 'layout' 
     356    if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout)) 
     357      this.element.setStyle({zoom: 1}); 
     358    var options = Object.extend({ 
     359      from: this.element.getOpacity() || 0.0, 
    155360      to:   1.0 
    156361    }, arguments[1] || {}); 
     
    158363  }, 
    159364  update: function(position) { 
    160     this.setOpacity(position); 
    161   },  
    162   setOpacity: function(opacity) { 
    163     opacity = (opacity == 1) ? 0.99999 : opacity; 
    164     this.element.style.opacity = opacity; 
    165     this.element.style.filter = "alpha(opacity:"+opacity*100+")"; 
     365    this.element.setOpacity(position); 
    166366  } 
    167367}); 
    168368 
    169 Effect.MoveBy = Class.create(); 
    170 Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), { 
    171   initialize: function(element, toTop, toLeft) { 
    172     this.element      = $(element); 
    173     this.originalTop  = parseFloat(this.element.style.top || '0'); 
    174     this.originalLeft = parseFloat(this.element.style.left || '0'); 
    175     this.toTop        = toTop; 
    176     this.toLeft       = toLeft; 
    177     Element.makePositioned(this.element); 
    178     this.start(arguments[3]); 
     369Effect.Move = Class.create(); 
     370Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { 
     371  initialize: function(element) { 
     372    this.element = $(element); 
     373    var options = Object.extend({ 
     374      x:    0, 
     375      y:    0, 
     376      mode: 'relative' 
     377    }, arguments[1] || {}); 
     378    this.start(options); 
     379  }, 
     380  setup: function() { 
     381    // Bug in Opera: Opera returns the "real" position of a static element or 
     382    // relative element that does not have top/left explicitly set. 
     383    // ==> Always set top and left for position relative elements in your stylesheets  
     384    // (to 0 if you do not need them)  
     385    this.element.makePositioned(); 
     386    this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); 
     387    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0'); 
     388    if(this.options.mode == 'absolute') { 
     389      // absolute movement, so we need to calc deltaX and deltaY 
     390      this.options.x = this.options.x - this.originalLeft; 
     391      this.options.y = this.options.y - this.originalTop; 
     392    } 
    179393  }, 
    180394  update: function(position) { 
    181     topd  = this.toTop  * position + this.originalTop; 
    182     leftd = this.toLeft * position + this.originalLeft; 
    183     this.setPosition(topd, leftd); 
    184   }, 
    185   setPosition: function(topd, leftd) { 
    186     this.element.style.top  = topd  + "px"; 
    187     this.element.style.left = leftd + "px"; 
     395    this.element.setStyle({ 
     396      left: this.options.x  * position + this.originalLeft + 'px', 
     397      top:  this.options.y  * position + this.originalTop  + 'px' 
     398    }); 
    188399  } 
    189400}); 
     401 
     402// for backwards compatibility 
     403Effect.MoveBy = function(element, toTop, toLeft) { 
     404  return new Effect.Move(element,  
     405    Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); 
     406}; 
    190407 
    191408Effect.Scale = Class.create(); 
     
    193410  initialize: function(element, percent) { 
    194411    this.element = $(element) 
    195     options = Object.extend({ 
     412    var options = Object.extend({ 
    196413      scaleX: true, 
    197414      scaleY: true, 
     
    199416      scaleFromCenter: false, 
    200417      scaleMode: 'box',        // 'box' or 'contents' or {} with provided values 
    201       scaleFrom: 100.0 
     418      scaleFrom: 100.0, 
     419      scaleTo:   percent 
    202420    }, arguments[2] || {}); 
    203     this.originalTop    = this.element.offsetTop; 
    204     this.originalLeft   = this.element.offsetLeft; 
    205     if(this.element.style.fontSize=="") this.sizeEm = 1.0; 
    206     if(this.element.style.fontSize && this.element.style.fontSize.indexOf("em")>0) 
    207       this.sizeEm      = parseFloat(this.element.style.fontSize); 
    208     this.factor = (percent/100.0) - (options.scaleFrom/100.0); 
    209     if(options.scaleMode=='box') { 
    210       this.originalHeight = this.element.clientHeight; 
    211       this.originalWidth  = this.element.clientWidth;  
    212     } else  
    213     if(options.scaleMode=='contents') { 
    214       this.originalHeight = this.element.scrollHeight; 
    215       this.originalWidth  = this.element.scrollWidth; 
    216     } else { 
    217       this.originalHeight = options.scaleMode.originalHeight; 
    218       this.originalWidth  = options.scaleMode.originalWidth; 
    219     } 
    220421    this.start(options); 
    221422  }, 
    222  
     423  setup: function() { 
     424    this.restoreAfterFinish = this.options.restoreAfterFinish || false; 
     425    this.elementPositioning = this.element.getStyle('position'); 
     426     
     427    this.originalStyle = {}; 
     428    ['top','left','width','height','fontSize'].each( function(k) { 
     429      this.originalStyle[k] = this.element.style[k]; 
     430    }.bind(this)); 
     431       
     432    this.originalTop  = this.element.offsetTop; 
     433    this.originalLeft = this.element.offsetLeft; 
     434     
     435    var fontSize = this.element.getStyle('font-size') || '100%'; 
     436    ['em','px','%'].each( function(fontSizeType) { 
     437      if(fontSize.indexOf(fontSizeType)>0) { 
     438        this.fontSize     = parseFloat(fontSize); 
     439        this.fontSizeType = fontSizeType; 
     440      } 
     441    }.bind(this)); 
     442     
     443    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; 
     444     
     445    this.dims = null; 
     446    if(this.options.scaleMode=='box') 
     447      this.dims = [this.element.offsetHeight, this.element.offsetWidth]; 
     448    if(/^content/.test(this.options.scaleMode)) 
     449      this.dims = [this.element.scrollHeight, this.element.scrollWidth]; 
     450    if(!this.dims) 
     451      this.dims = [this.options.scaleMode.originalHeight, 
     452                   this.options.scaleMode.originalWidth]; 
     453  }, 
    223454  update: function(position) { 
    224     currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); 
    225     if(this.options.scaleContent && this.sizeEm)  
    226       this.element.style.fontSize = this.sizeEm*currentScale + "em"; 
    227     this.setDimensions( 
    228       this.originalWidth * currentScale,  
    229       this.originalHeight * currentScale); 
    230   }, 
    231  
    232   setDimensions: function(width, height) { 
    233     if(this.options.scaleX) this.element.style.width = width + 'px'; 
    234     if(this.options.scaleY) this.element.style.height = height + 'px'; 
     455    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); 
     456    if(this.options.scaleContent && this.fontSize) 
     457      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); 
     458    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); 
     459  }, 
     460  finish: function(position) { 
     461    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); 
     462  }, 
     463  setDimensions: function(height, width) { 
     464    var d = {}; 
     465    if(this.options.scaleX) d.width = width + 'px'; 
     466    if(this.options.scaleY) d.height = height + 'px'; 
    235467    if(this.options.scaleFromCenter) { 
    236       topd  = (height - this.originalHeight)/2; 
    237       leftd = (width  - this.originalWidth)/2; 
    238       if(this.element.style.position=='absolute') { 
    239         if(this.options.scaleY) this.element.style.top = this.originalTop-topd + "px"; 
    240         if(this.options.scaleX) this.element.style.left = this.originalLeft-leftd + "px"; 
     468      var topd  = (height - this.dims[0])/2; 
     469      var leftd = (width  - this.dims[1])/2; 
     470      if(this.elementPositioning == 'absolute') { 
     471        if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; 
     472        if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; 
    241473      } else { 
    242         if(this.options.scaleY) this.element.style.top = -topd + "px"; 
    243         if(this.options.scaleX) this.element.style.left = -leftd + "px"; 
     474        if(this.options.scaleY) d.top = -topd + 'px'; 
     475        if(this.options.scaleX) d.left = -leftd + 'px'; 
    244476      } 
    245477    } 
     478    this.element.setStyle(d); 
    246479  } 
    247480}); 
     
    251484  initialize: function(element) { 
    252485    this.element = $(element); 
    253      
    254     // try to parse current background color as default for endcolor 
    255     // browser stores this as: "rgb(255, 255, 255)", convert to "#ffffff" format 
    256     var endcolor = "#ffffff"; 
    257     var current = this.element.style.backgroundColor; 
    258     if(current && current.slice(0,4) == "rgb(") { 
    259       endcolor = "#"; 
    260       var cols = current.slice(4,current.length-1).split(','); 
    261       var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3); } 
    262        
    263     var options = Object.extend({ 
    264       startcolor:   "#ffff99", 
    265       endcolor:     endcolor, 
    266       restorecolor: current  
    267     }, arguments[1] || {}); 
    268      
     486    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); 
     487    this.start(options); 
     488  }, 
     489  setup: function() { 
     490    // Prevent executing on elements not in the layout flow 
     491    if(this.element.getStyle('display')=='none') { this.cancel(); return; } 
     492    // Disable background image during the effect 
     493    this.oldStyle = { 
     494      backgroundImage: this.element.getStyle('background-image') }; 
     495    this.element.setStyle({backgroundImage: 'none'}); 
     496    if(!this.options.endcolor) 
     497      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); 
     498    if(!this.options.restorecolor) 
     499      this.options.restorecolor = this.element.getStyle('background-color'); 
    269500    // init color calculations 
    270     this.colors_base = [ 
    271       parseInt(options.startcolor.slice(1,3),16), 
    272       parseInt(options.startcolor.slice(3,5),16), 
    273       parseInt(options.startcolor.slice(5),16) ]; 
    274     this.colors_delta = [ 
    275       parseInt(options.endcolor.slice(1,3),16)-this.colors_base[0], 
    276       parseInt(options.endcolor.slice(3,5),16)-this.colors_base[1], 
    277       parseInt(options.endcolor.slice(5),16)-this.colors_base[2] ]; 
    278  
    279     this.start(options); 
     501    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); 
     502    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); 
    280503  }, 
    281504  update: function(position) { 
    282     var colors = [ 
    283       Math.round(this.colors_base[0]+(this.colors_delta[0]*position)), 
    284       Math.round(this.colors_base[1]+(this.colors_delta[1]*position)), 
    285       Math.round(this.colors_base[2]+(this.colors_delta[2]*position)) ]; 
    286     this.element.style.backgroundColor = "#" + 
    287       colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart(); 
     505    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ 
     506      return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); 
    288507  }, 
    289508  finish: function() { 
    290     this.element.style.backgroundColor = this.options.restorecolor; 
     509    this.element.setStyle(Object.extend(this.oldStyle, { 
     510      backgroundColor: this.options.restorecolor 
     511    })); 
    291512  } 
    292513}); 
     
    296517  initialize: function(element) { 
    297518    this.element = $(element); 
     519    this.start(arguments[1] || {}); 
     520  }, 
     521  setup: function() { 
    298522    Position.prepare(); 
    299523    var offsets = Position.cumulativeOffset(this.element); 
     524    if(this.options.offset) offsets[1] += this.options.offset; 
    300525    var max = window.innerHeight ?  
    301526      window.height - window.innerHeight : 
     
    304529          document.documentElement.clientHeight : document.body.clientHeight); 
    305530    this.scrollStart = Position.deltaY; 
    306     this.delta  = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; 
    307     this.start(arguments[1] || {}); 
     531    this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; 
    308532  }, 
    309533  update: function(position) { 
     
    314538}); 
    315539 
    316 /* ------------- prepackaged effects ------------- */ 
     540/* ------------- combination effects ------------- */ 
    317541 
    318542Effect.Fade = function(element) { 
    319   options = Object.extend({ 
    320   from: 1.0, 
     543  element = $(element); 
     544  var oldOpacity = element.getInlineOpacity(); 
     545  var options = Object.extend({ 
     546  from: element.getOpacity() || 1.0, 
    321547  to:   0.0, 
    322   afterFinish: function(effect)  
    323     { Element.hide(effect.element); 
    324       effect.setOpacity(1); }  
    325   }, arguments[1] || {}); 
     548  afterFinishInternal: function(effect) {  
     549    if(effect.options.to!=0) return; 
     550    effect.element.hide(); 
     551    effect.element.setStyle({opacity: oldOpacity});  
     552  }}, arguments[1] || {}); 
    326553  return new Effect.Opacity(element,options); 
    327554} 
    328555 
    329556Effect.Appear = function(element) { 
    330   options = Object.extend({ 
    331   from: 0.0, 
     557  element = $(element); 
     558  var options = Object.extend({ 
     559  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), 
    332560  to:   1.0, 
    333   beforeStart: function(effect)   
    334     { effect.setOpacity(0); 
    335       Element.show(effect.element); }, 
    336   afterUpdate: function(effect)   
    337     { Element.show(effect.element); } 
    338   }, arguments[1] || {}); 
     561  // force Safari to render floated elements properly 
     562  afterFinishInternal: function(effect) { 
     563    effect.element.forceRerendering(); 
     564  }, 
     565  beforeSetup: function(effect) { 
     566    effect.element.setOpacity(effect.options.from); 
     567    effect.element.show();  
     568  }}, arguments[1] || {}); 
    339569  return new Effect.Opacity(element,options); 
    340570} 
    341571 
    342572Effect.Puff = function(element) { 
     573  element = $(element); 
     574  var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position') }; 
    343575  return new Effect.Parallel( 
    344    [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true }),  
    345      new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ],  
    346      { duration: 1.0,  
    347       afterUpdate: function(effect)  
    348        { effect.effects[0].element.style.position = 'absolute'; }, 
    349       afterFinish: function(effect) 
    350        { Element.hide(effect.effects[0].element); } 
    351      } 
     576   [ new Effect.Scale(element, 200,  
     577      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),  
     578     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],  
     579     Object.extend({ duration: 1.0,  
     580      beforeSetupInternal: function(effect) { 
     581        effect.effects[0].element.setStyle({position: 'absolute'}); }, 
     582      afterFinishInternal: function(effect) { 
     583         effect.effects[0].element.hide(); 
     584         effect.effects[0].element.setStyle(oldStyle); } 
     585     }, arguments[1] || {}) 
    352586   ); 
    353587} 
    354588 
    355589Effect.BlindUp = function(element) { 
    356   Element.makeClipping(element); 
     590  element = $(element); 
     591  element.makeClipping(); 
    357592  return new Effect.Scale(element, 0,  
    358593    Object.extend({ scaleContent: false,  
    359594      scaleX: false,  
    360       afterFinish: function(effect)  
    361         {  
    362           Element.hide(effect.element); 
    363           Element.undoClipping(effect.element); 
    364         }  
     595      restoreAfterFinish: true, 
     596      afterFinishInternal: function(effect) { 
     597        effect.element.hide(); 
     598        effect.element.undoClipping(); 
     599      }  
    365600    }, arguments[1] || {}) 
    366601  ); 
     
    368603 
    369604Effect.BlindDown = function(element) { 
    370   $(element).style.height   = '0px'; 
    371   Element.makeClipping(element); 
    372   Element.show(element); 
     605  element = $(element); 
     606  var elementDimensions = element.getDimensions(); 
    373607  return new Effect.Scale(element, 100,  
    374608    Object.extend({ scaleContent: false,  
    375       scaleX: false,  
    376       scaleMode: 'contents', 
     609      scaleX: false, 
    377610      scaleFrom: 0, 
    378       afterFinish: function(effect) { 
    379         Element.undoClipping(effect.element); 
     611      scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, 
     612      restoreAfterFinish: true, 
     613      afterSetup: function(effect) { 
     614        effect.element.makeClipping(); 
     615        effect.element.setStyle({height: '0px'}); 
     616        effect.element.show();  
     617      },   
     618      afterFinishInternal: function(effect) { 
     619        effect.element.undoClipping(); 
    380620      } 
    381621    }, arguments[1] || {}) 
     
    384624 
    385625Effect.SwitchOff = function(element) { 
    386   return new Effect.Appear(element, 
    387     { duration: 0.4, 
    388      transition: Effect.Transitions.flicker, 
    389      afterFinish: function(effect) 
    390       { effect.element.style.overflow = 'hidden'; 
    391         new Effect.Scale(effect.element, 1,  
    392          { duration: 0.3, scaleFromCenter: true, 
    393           scaleX: false, scaleContent: false, 
    394           afterUpdate: function(effect) {  
    395            if(effect.element.style.position=="") 
    396              effect.element.style.position = 'relative'; }, 
    397           afterFinish: function(effect) { Element.hide(effect.element); } 
    398          } ) 
     626  element = $(element); 
     627  var oldOpacity = element.getInlineOpacity(); 
     628  return new Effect.Appear(element, {  
     629    duration: 0.4, 
     630    from: 0, 
     631    transition: Effect.Transitions.flicker, 
     632    afterFinishInternal: function(effect) { 
     633      new Effect.Scale(effect.element, 1, {  
     634        duration: 0.3, scaleFromCenter: true, 
     635        scaleX: false, scaleContent: false, restoreAfterFinish: true, 
     636        beforeSetup: function(effect) {  
     637          effect.element.makePositioned(); 
     638          effect.element.makeClipping(); 
     639        }, 
     640        afterFinishInternal: function(effect) { 
     641          effect.element.hide(); 
     642          effect.element.undoClipping(); 
     643          effect.element.undoPositioned(); 
     644          effect.element.setStyle({opacity: oldOpacity}); 
     645        } 
     646      }) 
     647    } 
     648  }); 
     649} 
     650 
     651Effect.DropOut = function(element) { 
     652  element = $(element); 
     653  var oldStyle = { 
     654    top: element.getStyle('top'), 
     655    left: element.getStyle('left'), 
     656    opacity: element.getInlineOpacity() }; 
     657  return new Effect.Parallel( 
     658    [ new Effect.Move(element, {x: 0, y: 100, sync: true }),  
     659      new Effect.Opacity(element, { sync: true, to: 0.0 }) ], 
     660    Object.extend( 
     661      { duration: 0.5, 
     662        beforeSetup: function(effect) { 
     663          effect.effects[0].element.makePositioned();  
     664        }, 
     665        afterFinishInternal: function(effect) { 
     666          effect.effects[0].element.hide(); 
     667          effect.effects[0].element.undoPositioned(); 
     668          effect.effects[0].element.setStyle(oldStyle); 
     669        }  
     670      }, arguments[1] || {})); 
     671} 
     672 
     673Effect.Shake = function(element) { 
     674  element = $(element); 
     675  var oldStyle = { 
     676    top: element.getStyle('top'), 
     677    left: element.getStyle('left') }; 
     678    return new Effect.Move(element,  
     679      { x:  20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { 
     680    new Effect.Move(effect.element, 
     681      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) { 
     682    new Effect.Move(effect.element, 
     683      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) { 
     684    new Effect.Move(effect.element, 
     685      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) { 
     686    new Effect.Move(effect.element, 
     687      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) { 
     688    new Effect.Move(effect.element, 
     689      { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { 
     690        effect.element.undoPositioned(); 
     691        effect.element.setStyle(oldStyle); 
     692  }}) }}) }}) }}) }}) }}); 
     693} 
     694 
     695Effect.SlideDown = function(element) { 
     696  element = $(element); 
     697  element.cleanWhitespace(); 
     698  // SlideDown need to have the content of the element wrapped in a container element with fixed height! 
     699  var oldInnerBottom = $(element.firstChild).getStyle('bottom'); 
     700  var elementDimensions = element.getDimensions(); 
     701  return new Effect.Scale(element, 100, Object.extend({  
     702    scaleContent: false,  
     703    scaleX: false,  
     704    scaleFrom: window.opera ? 0 : 1, 
     705    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, 
     706    restoreAfterFinish: true, 
     707    afterSetup: function(effect) { 
     708      effect.element.makePositioned(); 
     709      effect.element.firstChild.makePositioned(); 
     710      if(window.opera) effect.element.setStyle({top: ''}); 
     711      effect.element.makeClipping(); 
     712      effect.element.setStyle({height: '0px'}); 
     713      effect.element.show(); }, 
     714    afterUpdateInternal: function(effect) { 
     715      effect.element.firstChild.setStyle({bottom: 
     716        (effect.dims[0] - effect.element.clientHeight) + 'px' });  
     717    }, 
     718    afterFinishInternal: function(effect) { 
     719      effect.element.undoClipping();  
     720      // IE will crash if child is undoPositioned first 
     721      if(/MSIE/.test(navigator.userAgent)){ 
     722        effect.element.undoPositioned(); 
     723        effect.element.firstChild.undoPositioned(); 
     724      }else{ 
     725        effect.element.firstChild.undoPositioned(); 
     726        effect.element.undoPositioned(); 
    399727      } 
    400     } ); 
    401 } 
    402  
    403 Effect.DropOut = function(element) { 
    404   return new Effect.Parallel( 
    405     [ new Effect.MoveBy(element, 100, 0, { sync: true }),  
    406       new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ],  
    407     { duration: 0.5,  
    408      afterFinish: function(effect) 
    409        { Element.hide(effect.effects[0].element); }  
    410     }); 
    411 } 
    412  
    413 Effect.Shake = function(element) { 
    414   return new Effect.MoveBy(element, 0, 20,  
    415     { duration: 0.05, afterFinish: function(effect) { 
    416   new Effect.MoveBy(effect.element, 0, -40,  
    417     { duration: 0.1, afterFinish: function(effect) {  
    418   new Effect.MoveBy(effect.element, 0, 40,  
    419     { duration: 0.1, afterFinish: function(effect) {   
    420   new Effect.MoveBy(effect.element, 0, -40,  
    421     { duration: 0.1, afterFinish: function(effect) {   
    422   new Effect.MoveBy(effect.element, 0, 40,  
    423     { duration: 0.1, afterFinish: function(effect) {   
    424   new Effect.MoveBy(effect.element, 0, -20,  
    425     { duration: 0.05, afterFinish: function(effect) {   
    426   }}) }}) }}) }}) }}) }}); 
    427 } 
    428  
    429 Effect.SlideDown = function(element) { 
    430   element = $(element); 
    431   element.style.height   = '0px'; 
    432   Element.makeClipping(element); 
    433   Element.cleanWhitespace(element); 
    434   Element.makePositioned(element.firstChild); 
    435   Element.show(element); 
    436   return new Effect.Scale(element, 100,  
     728      effect.element.firstChild.setStyle({bottom: oldInnerBottom}); } 
     729    }, arguments[1] || {}) 
     730  ); 
     731} 
     732   
     733Effect.SlideUp = function(element) { 
     734  element = $(element); 
     735  element.cleanWhitespace(); 
     736  var oldInnerBottom = $(element.firstChild).getStyle('bottom'); 
     737  return new Effect.Scale(element, window.opera ? 0 : 1, 
    437738   Object.extend({ scaleContent: false,  
    438739    scaleX: false,  
    439     scaleMode: 'contents', 
    440     scaleFrom: 0, 
    441     afterUpdate: function(effect)  
    442       { effect.element.firstChild.style.bottom =  
    443           (effect.originalHeight - effect.element.clientHeight) + 'px'; }, 
    444     afterFinish: function(effect)  
    445       {  Element.undoClipping(effect.element); } 
    446     }, arguments[1] || {}) 
    447   ); 
    448 } 
    449    
    450 Effect.SlideUp = function(element) { 
    451   element = $(element); 
    452   Element.makeClipping(element); 
    453   Element.cleanWhitespace(element); 
    454   Element.makePositioned(element.firstChild); 
    455   Element.show(element); 
    456   return new Effect.Scale(element, 0,  
    457    Object.extend({ scaleContent: false,  
    458     scaleX: false,  
    459     afterUpdate: function(effect)  
    460       { effect.element.firstChild.style.bottom =  
    461           (effect.originalHeight - effect.element.clientHeight) + 'px'; }, 
    462     afterFinish: function(effect) 
    463       {  
    464         Element.hide(effect.element); 
    465         Element.undoClipping(effect.element); 
    466       } 
     740    scaleMode: 'box', 
     741    scaleFrom: 100, 
     742    restoreAfterFinish: true, 
     743    beforeStartInternal: function(effect) { 
     744      effect.element.makePositioned(); 
     745      effect.element.firstChild.makePositioned(); 
     746      if(window.opera) effect.element.setStyle({top: ''}); 
     747      effect.element.makeClipping(); 
     748      effect.element.show(); },   
     749    afterUpdateInternal: function(effect) { 
     750      effect.element.firstChild.setStyle({bottom: 
     751        (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, 
     752    afterFinishInternal: function(effect) { 
     753      effect.element.hide(); 
     754      effect.element.undoClipping(); 
     755      effect.element.firstChild.undoPositioned(); 
     756      effect.element.undoPositioned(); 
     757      effect.element.setStyle({bottom: oldInnerBottom}); } 
    467758   }, arguments[1] || {}) 
    468759  ); 
    469760} 
    470761 
     762// Bug in opera makes the TD containing this element expand for a instance after finish  
    471763Effect.Squish = function(element) { 
    472  return new Effect.Scale(element, 0,  
    473    { afterFinish: function(effect) { Element.hide(effect.element); } }); 
     764  return new Effect.Scale(element, window.opera ? 1 : 0,  
     765    { restoreAfterFinish: true, 
     766      beforeSetup: function(effect) { 
     767        effect.element.makeClipping(effect.element); },   
     768      afterFinishInternal: function(effect) { 
     769        effect.element.hide(effect.element);  
     770        effect.element.undoClipping(effect.element); } 
     771  }); 
    474772} 
    475773 
    476774Effect.Grow = function(element) { 
    477775  element = $(element); 
    478   var options = arguments[1] || {}; 
    479    
    480   var originalWidth = element.clientWidth; 
    481   var originalHeight = element.clientHeight; 
    482   element.style.overflow = 'hidden'; 
    483   Element.show(element); 
    484    
    485   var direction = options.direction || 'center'; 
    486   var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; 
    487   var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; 
    488   var opacityTransition = options.opacityTransition || Effect.Transitions.full; 
    489    
     776  var options = Object.extend({ 
     777    direction: 'center', 
     778    moveTransition: Effect.Transitions.sinoidal, 
     779    scaleTransition: Effect.Transitions.sinoidal, 
     780    opacityTransition: Effect.Transitions.full 
     781  }, arguments[1] || {}); 
     782  var oldStyle = { 
     783    top: element.style.top, 
     784    left: element.style.left, 
     785    height: element.style.height, 
     786    width: element.style.width, 
     787    opacity: element.getInlineOpacity() }; 
     788 
     789  var dims = element.getDimensions();     
    490790  var initialMoveX, initialMoveY; 
    491791  var moveX, moveY; 
    492792   
    493   switch (direction) { 
     793  switch (options.direction) { 
    494794    case 'top-left': 
    495795      initialMoveX = initialMoveY = moveX = moveY = 0;  
    496796      break; 
    497797    case 'top-right': 
    498       initialMoveX = originalWidth; 
     798      initialMoveX = dims.width; 
    499799      initialMoveY = moveY = 0; 
    500       moveX = -originalWidth; 
     800      moveX = -dims.width; 
    501801      break; 
    502802    case 'bottom-left': 
    503803      initialMoveX = moveX = 0; 
    504       initialMoveY = originalHeight; 
    505       moveY = -originalHeight; 
     804      initialMoveY = dims.height; 
     805      moveY = -dims.height; 
    506806      break; 
    507807    case 'bottom-right': 
    508       initialMoveX = originalWidth; 
    509       initialMoveY = originalHeight; 
    510       moveX = -originalWidth; 
    511       moveY = -originalHeight; 
     808      initialMoveX = dims.width; 
     809      initialMoveY = dims.height; 
     810      moveX = -dims.width; 
     811      moveY = -dims.height; 
    512812      break; 
    513813    case 'center': 
    514       initialMoveX = originalWidth / 2; 
    515       initialMoveY = originalHeight / 2; 
    516       moveX = -originalWidth / 2; 
    517       moveY = -originalHeight / 2; 
     814      initialMoveX = dims.width / 2; 
     815      initialMoveY = dims.height / 2; 
     816      moveX = -dims.width / 2; 
     817      moveY = -dims.height / 2; 
    518818      break; 
    519819  } 
    520820   
    521   return new Effect.MoveBy(element, initialMoveY, initialMoveX, {  
     821  return new Effect.Move(element, { 
     822    x: initialMoveX, 
     823    y: initialMoveY, 
    522824    duration: 0.01,  
    523     beforeUpdate: function(effect) { $(element).style.height = '0px'; }, 
    524     afterFinish: function(effect) { 
     825    beforeSetup: function(effect) { 
     826      effect.element.hide(); 
     827      effect.element.makeClipping(); 
     828      effect.element.makePositioned(); 
     829    }, 
     830    afterFinishInternal: function(effect) { 
    525831      new Effect.Parallel( 
    526         [ new Effect.Opacity(element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }), 
    527           new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }), 
    528           new Effect.Scale(element, 100, {  
    529             scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth },  
    530             sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })], 
    531         options); } 
    532     }); 
     832        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), 
     833          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), 
     834          new Effect.Scale(effect.element, 100, { 
     835            scaleMode: { originalHeight: dims.height, originalWidth: dims.width },  
     836            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) 
     837        ], Object.extend({ 
     838             beforeSetup: function(effect) { 
     839               effect.effects[0].element.setStyle({height: '0px'}); 
     840               effect.effects[0].element.show();  
     841             }, 
     842             afterFinishInternal: function(effect) { 
     843               effect.effects[0].element.undoClipping(); 
     844               effect.effects[0].element.undoPositioned(); 
     845               effect.effects[0].element.setStyle(oldStyle);  
     846             } 
     847           }, options) 
     848      ) 
     849    } 
     850  }); 
    533851} 
    534852 
    535853Effect.Shrink = function(element) { 
    536854  element = $(element); 
    537   var options = arguments[1] || {}; 
    538    
    539   var originalWidth = element.clientWidth; 
    540   var originalHeight = element.clientHeight; 
    541   element.style.overflow = 'hidden'; 
    542   Element.show(element); 
    543  
    544   var direction = options.direction || 'center'; 
    545   var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; 
    546   var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; 
    547   var opacityTransition = options.opacityTransition || Effect.Transitions.none; 
    548    
     855  var options = Object.extend({ 
     856    direction: 'center', 
     857    moveTransition: Effect.Transitions.sinoidal, 
     858    scaleTransition: Effect.Transitions.sinoidal, 
     859    opacityTransition: Effect.Transitions.none 
     860  }, arguments[1] || {}); 
     861  var oldStyle = { 
     862    top: element.style.top, 
     863    left: element.style.left, 
     864    height: element.style.height, 
     865    width: element.style.width, 
     866    opacity: element.getInlineOpacity() }; 
     867 
     868  var dims = element.getDimensions(); 
    549869  var moveX, moveY; 
    550870   
    551   switch (direction) { 
     871  switch (options.direction) { 
    552872    case 'top-left': 
    553873      moveX = moveY = 0; 
    554874      break; 
    555875    case 'top-right': 
    556       moveX = originalWidth; 
     876      moveX = dims.width; 
    557877      moveY = 0; 
    558878      break; 
    559879    case 'bottom-left': 
    560880      moveX = 0; 
    561       moveY = originalHeight; 
     881      moveY = dims.height; 
    562882      break; 
    563883    case 'bottom-right': 
    564       moveX = originalWidth; 
    565       moveY = originalHeight; 
     884      moveX = dims.width; 
     885      moveY = dims.height; 
    566886      break; 
    567887    case 'center':   
    568       moveX = originalWidth / 2; 
    569       moveY = originalHeight / 2; 
     888      moveX = dims.width / 2; 
     889      moveY = dims.height / 2; 
    570890      break; 
    571891  } 
    572892   
    573893  return new Effect.Parallel( 
    574     [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }), 
    575       new Effect.Scale(element, 0, { sync: true, transition: moveTransition }), 
    576       new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: scaleTransition }) ], 
    577     options); 
     894    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), 
     895      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), 
     896      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) 
     897    ], Object.extend({             
     898         beforeStartInternal: function(effect) { 
     899           effect.effects[0].element.makePositioned(); 
     900           effect.effects[0].element.makeClipping(); }, 
     901         afterFinishInternal: function(effect) { 
     902           effect.effects[0].element.hide(); 
     903           effect.effects[0].element.undoClipping(); 
     904           effect.effects[0].element.undoPositioned(); 
     905           effect.effects[0].element.setStyle(oldStyle); } 
     906       }, options) 
     907  ); 
    578908} 
    579909 
    580910Effect.Pulsate = function(element) { 
     911  element = $(element); 
    581912  var options    = arguments[1] || {}; 
     913  var oldOpacity = element.getInlineOpacity(); 
    582914  var transition = options.transition || Effect.Transitions.sinoidal; 
    583915  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; 
    584916  reverser.bind(transition); 
    585917  return new Effect.Opacity(element,  
    586     Object.extend(Object.extend({  duration: 3.0, 
    587        afterFinish: function(effect) { Element.show(effect.element); } 
     918    Object.extend(Object.extend({  duration: 3.0, from: 0, 
     919      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } 
    588920    }, options), {transition: reverser})); 
    589921} 
    590922 
    591923Effect.Fold = function(element) { 
    592  $(element).style.overflow = 'hidden'; 
    593  return new Effect.Scale(element, 5, Object.extend({    
    594    scaleContent: false, 
    595    scaleTo: 100, 
    596    scaleX: false, 
    597    afterFinish: function(effect) { 
    598    new Effect.Scale(element, 1, {  
    599      scaleContent: false,  
    600      scaleTo: 0, 
    601      scaleY: false, 
    602      afterFinish: function(effect) { Element.hide(effect.element) } }); 
    603  }}, arguments[1] || {})); 
    604 } 
    605  
    606 // old: new Effect.ContentZoom(element, percent) 
    607 // new: Element.setContentZoom(element, percent)  
    608  
    609 Element.setContentZoom = function(element, percent) { 
    610   var element = $(element); 
    611   element.style.fontSize = (percent/100) + "em";   
    612   if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 
    613 } 
     924  element = $(element); 
     925  var oldStyle = { 
     926    top: element.style.top, 
     927    left: element.style.left, 
     928    width: element.style.width, 
     929    height: element.style.height }; 
     930  Element.makeClipping(element); 
     931  return new Effect.Scale(element, 5, Object.extend({    
     932    scaleContent: false, 
     933    scaleX: false, 
     934    afterFinishInternal: function(effect) { 
     935    new Effect.Scale(element, 1, {  
     936      scaleContent: false,  
     937      scaleY: false, 
     938      afterFinishInternal: function(effect) { 
     939        effect.element.hide(); 
     940        effect.element.undoClipping();  
     941        effect.element.setStyle(oldStyle); 
     942      } }); 
     943  }}, arguments[1] || {})); 
     944}; 
     945 
     946['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', 
     947 'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each(  
     948  function(f) { Element.Methods[f] = Element[f]; } 
     949); 
     950 
     951Element.Methods.visualEffect = function(element, effect, options) { 
     952  s = effect.gsub(/_/, '-').camelize(); 
     953  effect_class = s.charAt(0).toUpperCase() + s.substring(1); 
     954  new Effect[effect_class](element, options); 
     955  return $(element); 
     956}; 
     957 
     958Element.addMethods(); 
  • trunk/trax/data/public/javascripts/prototype.js

    r182 r226  
    1 // $Id$ 
    2 /*  Prototype JavaScript framework, version 1.3.0 
     1/*  Prototype JavaScript framework, version 1.5.0_rc0 
    32 *  (c) 2005 Sam Stephenson <sam@conio.net> 
    43 * 
    5  *  THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff 
    6  *  against the source tree, available from the Prototype darcs repository.  
    7  * 
    84 *  Prototype is freely distributable under the terms of an MIT-style license. 
    9  * 
    105 *  For details, see the Prototype web site: http://prototype.conio.net/ 
    116 * 
     
    138 
    149var Prototype = { 
    15   Version: '1.3.0', 
    16   emptyFunction: function() {} 
     10  Version: '1.5.0_rc0', 
     11  ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)', 
     12 
     13  emptyFunction: function() {}, 
     14  K: function(x) {return x} 
    1715} 
    1816 
    1917var Class = { 
    2018  create: function() { 
    21     return function() {  
     19    return function() { 
    2220      this.initialize.apply(this, arguments); 
    2321    } 
     
    2826 
    2927Object.extend = function(destination, source) { 
    30   for (property in source) { 
     28  for (var property in source) { 
    3129    destination[property] = source[property]; 
    3230  } 
     
    3432} 
    3533 
    36 Object.prototype.extend = function(object) { 
    37   return Object.extend.apply(this, [this, object]); 
    38 } 
    39  
    40 Function.prototype.bind = function(object) { 
    41   var __method = this; 
     34Object.inspect = function(object) { 
     35  try { 
     36    if (object == undefined) return 'undefined'; 
     37    if (object == null) return 'null'; 
     38    return object.inspect ? object.inspect() : object.toString(); 
     39  } catch (e) { 
     40    if (e instanceof RangeError) return '...'; 
     41    throw e; 
     42  } 
     43} 
     44 
     45Function.prototype.bind = function() { 
     46  var __method = this, args = $A(arguments), object = args.shift(); 
    4247  return function() { 
    43     __method.apply(object, arguments); 
     48    return __method.apply(object, args.concat($A(arguments))); 
    4449  } 
    4550} 
     
    4853  var __method = this; 
    4954  return function(event) { 
    50     __method.call(object, event || window.event); 
    51   } 
    52 } 
    53  
    54 Number.prototype.toColorPart = function() { 
    55   var digits = this.toString(16); 
    56   if (this < 16) return '0' + digits; 
    57   return digits; 
    58 } 
     55    return __method.call(object, event || window.event); 
     56  } 
     57} 
     58 
     59Object.extend(Number.prototype, { 
     60  toColorPart: function() { 
     61    var digits = this.toString(16); 
     62    if (this < 16) return '0' + digits; 
     63    return digits; 
     64  }, 
     65 
     66  succ: function() { 
     67    return this + 1; 
     68  }, 
     69 
     70  times: function(iterator) { 
     71    $R(0, this, true).each(iterator); 
     72    return this; 
     73  } 
     74}); 
    5975 
    6076var Try = { 
     
    92108  onTimerEvent: function() { 
    93109    if (!this.currentlyExecuting) { 
    94       try {  
     110      try { 
    95111        this.currentlyExecuting = true; 
    96         this.callback();  
    97       } finally {  
     112        this.callback(); 
     113      } finally { 
    98114        this.currentlyExecuting = false; 
    99115      } 
     
    101117  } 
    102118} 
    103  
    104 /*--------------------------------------------------------------------------*/ 
    105  
    106 function $() { 
    107   var elements = new Array(); 
    108  
    109   for (var i = 0; i < arguments.length; i++) { 
    110     var element = arguments[i]; 
    111     if (typeof element == 'string') 
    112       element = document.getElementById(element); 
    113  
    114     if (arguments.length == 1)  
    115       return element; 
    116  
    117     elements.push(element); 
    118   } 
    119  
    120   return elements; 
    121 } 
    122  
    123 if (!Array.prototype.push) { 
    124   Array.prototype.push = function() { 
    125         var startLength = this.length; 
    126         for (var i = 0; i < arguments.length; i++) 
    127       this[startLength + i] = arguments[i]; 
    128       return this.length; 
    129   } 
    130 } 
    131  
    132 if (!Function.prototype.apply) { 
    133   // Based on code from http://www.youngpup.net/ 
    134   Function.prototype.apply = function(object, parameters) { 
    135     var parameterStrings = new Array(); 
    136     if (!object)     object = window; 
    137     if (!parameters) parameters = new Array(); 
    138      
    139     for (var i = 0; i < parameters.length; i++) 
    140       parameterStrings[i] = 'parameters[' + i + ']'; 
    141      
    142     object.__apply__ = this; 
    143     var result = eval('object.__apply__(' +  
    144       parameterStrings[i].join(', ') + ')'); 
    145     object.__apply__ = null; 
    146      
     119Object.extend(String.prototype, { 
     120  gsub: function(pattern, replacement) { 
     121    var result = '', source = this, match; 
     122    replacement = arguments.callee.prepareReplacement(replacement); 
     123 
     124    while (source.length > 0) { 
     125      if (match = source.match(pattern)) { 
     126        result += source.slice(0, match.index); 
     127        result += (replacement(match) || '').toString(); 
     128        source  = source.slice(match.index + match[0].length); 
     129      } else { 
     130        result += source, source = ''; 
     131      } 
     132    } 
    147133    return result; 
    148   } 
    149 } 
    150  
    151 String.prototype.extend({ 
     134  }, 
     135 
     136  sub: function(pattern, replacement, count) { 
     137    replacement = this.gsub.prepareReplacement(replacement); 
     138    count = count === undefined ? 1 : count; 
     139 
     140    return this.gsub(pattern, function(match) { 
     141      if (--count < 0) return match[0]; 
     142      return replacement(match); 
     143    }); 
     144  }, 
     145 
     146  scan: function(pattern, iterator) { 
     147    this.gsub(pattern, iterator); 
     148    return this; 
     149  }, 
     150 
     151  truncate: function(length, truncation) { 
     152    length = length || 30; 
     153    truncation = truncation === undefined ? '...' : truncation; 
     154    return this.length > length ? 
     155      this.slice(0, length - truncation.length) + truncation : this; 
     156  }, 
     157 
     158  strip: function() { 
     159    return this.replace(/^\s+/, '').replace(/\s+$/, ''); 
     160  }, 
     161 
    152162  stripTags: function() { 
    153163    return this.replace(/<\/?[^>]+>/gi, ''); 
     164  }, 
     165 
     166  stripScripts: function() { 
     167    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); 
     168  }, 
     169 
     170  extractScripts: function() { 
     171    var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); 
     172    var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); 
     173    return (this.match(matchAll) || []).map(function(scriptTag) { 
     174      return (scriptTag.match(matchOne) || ['', ''])[1]; 
     175    }); 
     176  }, 
     177 
     178  evalScripts: function() { 
     179    return this.extractScripts().map(function(script) { return eval(script) }); 
    154180  }, 
    155181 
     
    164190    var div = document.createElement('div'); 
    165191    div.innerHTML = this.stripTags(); 
    166     return div.childNodes[0].nodeValue; 
     192    return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; 
     193  }, 
     194 
     195  toQueryParams: function() { 
     196    var pairs = this.match(/^\??(.*)$/)[1].split('&'); 
     197    return pairs.inject({}, function(params, pairString) { 
     198      var pair = pairString.split('='); 
     199      params[pair[0]] = pair[1]; 
     200      return params; 
     201    }); 
     202  }, 
     203 
     204  toArray: function() { 
     205    return this.split(''); 
     206  }, 
     207 
     208  camelize: function() { 
     209    var oStringList = this.split('-'); 
     210    if (oStringList.length == 1) return oStringList[0]; 
     211 
     212    var camelizedString = this.indexOf('-') == 0 
     213      ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) 
     214      : oStringList[0]; 
     215 
     216    for (var i = 1, len = oStringList.length; i < len; i++) { 
     217      var s = oStringList[i]; 
     218      camelizedString += s.charAt(0).toUpperCase() + s.substring(1); 
     219    } 
     220 
     221    return camelizedString; 
     222  }, 
     223 
     224  inspect: function() { 
     225    return "'" + this.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + "'"; 
    167226  } 
    168227}); 
     228 
     229String.prototype.gsub.prepareReplacement = function(replacement) { 
     230  if (typeof replacement == 'function') return replacement; 
     231  var template = new Template(replacement); 
     232  return function(match) { return template.evaluate(match) }; 
     233} 
     234 
     235String.prototype.parseQuery = String.prototype.toQueryParams; 
     236 
     237var Template = Class.create(); 
     238Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; 
     239Template.prototype = { 
     240  initialize: function(template, pattern) { 
     241    this.template = template.toString(); 
     242    this.pattern  = pattern || Template.Pattern; 
     243  }, 
     244 
     245  evaluate: function(object) { 
     246    return this.template.gsub(this.pattern, function(match) { 
     247      var before = match[1]; 
     248      if (before == '\\') return match[2]; 
     249      return before + (object[match[3]] || '').toString(); 
     250    }); 
     251  } 
     252} 
     253 
     254var $break    = new Object(); 
     255var $continue = new Object(); 
     256 
     257var Enumerable = { 
     258  each: function(iterator) { 
     259    var index = 0; 
     260    try { 
     261      this._each(function(value) { 
     262        try { 
     263          iterator(value, index++); 
     264        } catch (e) { 
     265          if (e != $continue) throw e; 
     266        } 
     267      }); 
     268    } catch (e) { 
     269      if (e != $break) throw e; 
     270    } 
     271  }, 
     272 
     273  all: function(iterator) { 
     274    var result = true; 
     275    this.each(function(value, index) { 
     276      result = result && !!(iterator || Prototype.K)(value, index); 
     277      if (!result) throw $break; 
     278    }); 
     279    return result; 
     280  }, 
     281 
     282  any: function(iterator) { 
     283    var result = true; 
     284    this.each(function(value, index) { 
     285      if (result = !!(iterator || Prototype.K)(value, index)) 
     286        throw $break; 
     287    }); 
     288    return result; 
     289  }, 
     290 
     291  collect: function(iterator) { 
     292    var results = []; 
     293    this.each(function(value, index) { 
     294      results.push(iterator(value, index)); 
     295    }); 
     296    return results; 
     297  }, 
     298 
     299  detect: function (iterator) { 
     300    var result; 
     301    this.each(function(value, index) { 
     302      if (iterator(value, index)) { 
     303        result = value; 
     304        throw $break; 
     305      } 
     306    }); 
     307    return result; 
     308  }, 
     309 
     310  findAll: function(iterator) { 
     311    var results = []; 
     312    this.each(function(value, index) { 
     313      if (iterator(value, index)) 
     314        results.push(value); 
     315    }); 
     316    return results; 
     317  }, 
     318 
     319  grep: function(pattern, iterator) { 
     320    var results = []; 
     321    this.each(function(value, index) { 
     322      var stringValue = value.toString(); 
     323      if (stringValue.match(pattern)) 
     324        results.push((iterator || Prototype.K)(value, index)); 
     325    }) 
     326    return results; 
     327  }, 
     328 
     329  include: function(object) { 
     330    var found = false; 
     331    this.each(function(value) { 
     332      if (value == object) { 
     333        found = true; 
     334        throw $break; 
     335      } 
     336    }); 
     337    return found; 
     338  }, 
     339 
     340  inject: function(memo, iterator) { 
     341    this.each(function(value, index) { 
     342      memo = iterator(memo, value, index); 
     343    }); 
     344    return memo; 
     345  }, 
     346 
     347  invoke: function(method) { 
     348    var args = $A(arguments).slice(1); 
     349    return this.collect(function(value) { 
     350      return value[method].apply(value, args); 
     351    }); 
     352  }, 
     353 
     354  max: function(iterator) { 
     355    var result; 
     356    this.each(function(value, index) { 
     357      value = (iterator || Prototype.K)(value, index); 
     358      if (result == undefined || value >= result) 
     359        result = value; 
     360    }); 
     361    return result; 
     362  }, 
     363 
     364  min: function(iterator) { 
     365    var result; 
     366    this.each(function(value, index) { 
     367      value = (iterator || Prototype.K)(value, index); 
     368      if (result == undefined || value < result) 
     369        result = value; 
     370    }); 
     371    return result; 
     372  }, 
     373 
     374  partition: function(iterator) { 
     375    var trues = [], falses = []; 
     376    this.each(function(value, index) { 
     377      ((iterator || Prototype.K)(value, index) ? 
     378        trues : falses).push(value); 
     379    }); 
     380    return [trues, falses]; 
     381  }, 
     382 
     383  pluck: function(property) { 
     384    var results = []; 
     385    this.each(function(value, index) { 
     386      results.push(value[property]); 
     387    }); 
     388    return results; 
     389  }, 
     390 
     391  reject: function(iterator) { 
     392    var results = []; 
     393    this.each(function(value, index) { 
     394      if (!iterator(value, index)) 
     395        results.push(value); 
     396    }); 
     397    return results; 
     398  }, 
     399 
     400  sortBy: function(iterator) { 
     401    return this.collect(function(value, index) { 
     402      return {value: value, criteria: iterator(value, index)}; 
     403    }).sort(function(left, right) { 
     404      var a = left.criteria, b = right.criteria; 
     405      return a < b ? -1 : a > b ? 1 : 0; 
     406    }).pluck('value'); 
     407  }, 
     408 
     409  toArray: function() { 
     410    return this.collect(Prototype.K); 
     411  }, 
     412 
     413  zip: function() { 
     414    var iterator = Prototype.K, args = $A(arguments); 
     415    if (typeof args.last() == 'function') 
     416      iterator = args.pop(); 
     417 
     418    var collections = [this].concat(args).map($A); 
     419    return this.map(function(value, index) { 
     420      return iterator(collections.pluck(index)); 
     421    }); 
     422  }, 
     423 
     424  inspect: function() { 
     425    return '#<Enumerable:' + this.toArray().inspect() + '>'; 
     426  } 
     427} 
     428 
     429Object.extend(Enumerable, { 
     430  map:     Enumerable.collect, 
     431  find:    Enumerable.detect, 
     432  select:  Enumerable.findAll, 
     433  member:  Enumerable.include, 
     434  entries: Enumerable.toArray 
     435}); 
     436var $A = Array.from = function(iterable) { 
     437  if (!iterable) return []; 
     438  if (iterable.toArray) { 
     439    return iterable.toArray(); 
     440  } else { 
     441    var results = []; 
     442    for (var i = 0; i < iterable.length; i++) 
     443      results.push(iterable[i]); 
     444    return results; 
     445  } 
     446} 
     447 
     448Object.extend(Array.prototype, Enumerable); 
     449 
     450if (!Array.prototype._reverse) 
     451  Array.prototype._reverse = Array.prototype.reverse; 
     452 
     453Object.extend(Array.prototype, { 
     454  _each: function(iterator) { 
     455    for (var i = 0; i < this.length; i++) 
     456      iterator(this[i]); 
     457  }, 
     458 
     459  clear: function() { 
     460    this.length = 0; 
     461    return this; 
     462  }, 
     463 
     464  first: function() { 
     465    return this[0]; 
     466  }, 
     467 
     468  last: function() { 
     469    return this[this.length - 1]; 
     470  }, 
     471 
     472  compact: function() { 
     473    return this.select(function(value) { 
     474      return value != undefined || value != null; 
     475    }); 
     476  }, 
     477 
     478  flatten: function() { 
     479    return this.inject([], function(array, value) { 
     480      return array.concat(value && value.constructor == Array ? 
     481        value.flatten() : [value]); 
     482    }); 
     483  }, 
     484 
     485  without: function() { 
     486    var values = $A(arguments); 
     487    return this.select(function(value) { 
     488      return !values.include(value); 
     489    }); 
     490  }, 
     491 
     492  indexOf: function(object) { 
     493    for (var i = 0; i < this.length; i++) 
     494      if (this[i] == object) return i; 
     495    return -1; 
     496  }, 
     497 
     498  reverse: function(inline) { 
     499    return (inline !== false ? this : this.toArray())._reverse(); 
     500  }, 
     501 
     502  inspect: function() { 
     503    return '[' + this.map(Object.inspect).join(', ') + ']'; 
     504  } 
     505}); 
     506var Hash = { 
     507  _each: function(iterator) { 
     508    for (var key in this) { 
     509      var value = this[key]; 
     510      if (typeof value == 'function') continue; 
     511 
     512      var pair = [key, value]; 
     513      pair.key = key; 
     514      pair.value = value; 
     515      iterator(pair); 
     516    } 
     517  }, 
     518 
     519  keys: function() { 
     520    return this.pluck('key'); 
     521  }, 
     522 
     523  values: function() { 
     524    return this.pluck('value'); 
     525  }, 
     526 
     527  merge: function(hash) { 
     528    return $H(hash).inject($H(this), function(mergedHash, pair) { 
     529      mergedHash[pair.key] = pair.value; 
     530      return mergedHash; 
     531    }); 
     532  }, 
     533 
     534  toQueryString: function() { 
     535    return this.map(function(pair) { 
     536      return pair.map(encodeURIComponent).join('='); 
     537    }).join('&'); 
     538  }, 
     539 
     540  inspect: function() { 
     541    return '#<Hash:{' + this.map(function(pair) { 
     542      return pair.map(Object.inspect).join(': '); 
     543    }).join(', ') + '}>'; 
     544  } 
     545} 
     546 
     547function $H(object) { 
     548  var hash = Object.extend({}, object || {}); 
     549  Object.extend(hash, Enumerable); 
     550  Object.extend(hash, Hash); 
     551  return hash; 
     552} 
     553ObjectRange = Class.create(); 
     554Object.extend(ObjectRange.prototype, Enumerable); 
     555Object.extend(ObjectRange.prototype, { 
     556  initialize: function(start, end, exclusive) { 
     557    this.start = start; 
     558    this.end = end; 
     559    this.exclusive = exclusive; 
     560  }, 
     561 
     562  _each: function(iterator) { 
     563    var value = this.start; 
     564    do { 
     565      iterator(value); 
     566      value = value.succ(); 
     567    } while (this.include(value)); 
     568  }, 
     569 
     570  include: function(value) { 
     571    if (value < this.start) 
     572      return false; 
     573    if (this.exclusive) 
     574      return value < this.end; 
     575    return value <= this.end; 
     576  } 
     577}); 
     578 
     579var $R = function(start, end, exclusive) { 
     580  return new ObjectRange(start, end, exclusive); 
     581} 
    169582 
    170583var Ajax = { 
    171584  getTransport: function() { 
    172585    return Try.these( 
     586      function() {return new XMLHttpRequest()}, 
    173587      function() {return new ActiveXObject('Msxml2.XMLHTTP')}, 
    174       function() {return new ActiveXObject('Microsoft.XMLHTTP')}, 
    175       function() {return new XMLHttpRequest()} 
     588      function() {return new ActiveXObject('Microsoft.XMLHTTP')} 
    176589    ) || false; 
    177   } 
    178 } 
     590  }, 
     591 
     592  activeRequestCount: 0 
     593} 
     594 
     595Ajax.Responders = { 
     596  responders: [], 
     597 
     598  _each: function(iterator) { 
     599    this.responders._each(iterator); 
     600  }, 
     601 
     602  register: function(responderToAdd) { 
     603    if (!this.include(responderToAdd)) 
     604      this.responders.push(responderToAdd); 
     605  }, 
     606 
     607  unregister: function(responderToRemove) { 
     608    this.responders = this.responders.without(responderToRemove); 
     609  }, 
     610 
     611  dispatch: function(callback, request, transport, json) { 
     612    this.each(function(responder) { 
     613      if (responder[callback] && typeof responder[callback] == 'function') { 
     614        try { 
     615          responder[callback].apply(responder, [request, transport, json]); 
     616        } catch (e) {} 
     617      } 
     618    }); 
     619  } 
     620}; 
     621 
     622Object.extend(Ajax.Responders, Enumerable); 
     623 
     624Ajax.Responders.register({ 
     625  onCreate: function() { 
     626    Ajax.activeRequestCount++; 
     627  }, 
     628 
     629  onComplete: function() { 
     630    Ajax.activeRequestCount--; 
     631  } 
     632}); 
    179633 
    180634Ajax.Base = function() {}; 
     
    184638      method:       'post', 
    185639      asynchronous: true, 
     640      contentType:  'application/x-www-form-urlencoded', 
    186641      parameters:   '' 
    187     }.extend(options || {}); 
     642    } 
     643    Object.extend(this.options, options || {}); 
    188644  }, 
    189645 
    190646  responseIsSuccess: function() { 
    191647    return this.transport.status == undefined 
    192         || this.transport.status == 0  
     648        || this.transport.status == 0 
    193649        || (this.transport.status >= 200 && this.transport.status < 300); 
    194650  }, 
     
    200656 
    201657Ajax.Request = Class.create(); 
    202 Ajax.Request.Events =  
     658Ajax.Request.Events = 
    203659  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; 
    204660 
    205 Ajax.Request.prototype = (new Ajax.Base()).extend({ 
     661Ajax.Request.prototype = Object.extend(new Ajax.Base(), { 
    206662  initialize: function(url, options) { 
    207663    this.transport = Ajax.getTransport(); 
     
    215671 
    216672    try { 
    217       if (this.options.method == 'get') 
    218         url += '?' + parameters; 
    219  
    220       this.transport.open(this.options.method, url, 
     673      this.url = url; 
     674      if (this.options.method == 'get' && parameters.length > 0) 
     675        this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; 
     676 
     677      Ajax.Responders.dispatch('onCreate', this, this.transport); 
     678 
     679      this.transport.open(this.options.method, this.url, 
    221680        this.options.asynchronous); 
    222681 
     
    232691 
    233692    } catch (e) { 
     693      this.dispatchException(e); 
    234694    } 
    235695  }, 
    236696 
    237697  setRequestHeaders: function() { 
    238     var requestHeaders =  
     698    var requestHeaders = 
    239699      ['X-Requested-With', 'XMLHttpRequest', 
    240        'X-Prototype-Version', Prototype.Version]; 
     700       'X-Prototype-Version', Prototype.Version, 
     701       'Accept', 'text/javascript, text/html, application/xml, text/xml, */*']; 
    241702 
    242703    if (this.options.method == 'post') { 
    243       requestHeaders.push('Content-type',  
    244         'application/x-www-form-urlencoded'); 
     704      requestHeaders.push('Content-type', this.options.contentType); 
    245705 
    246706      /* Force "Connection: close" for Mozilla browsers to work around 
    247707       * a bug where XMLHttpReqeuest sends an incorrect Content-length 
    248        * header. See Mozilla Bugzilla #246651.  
     708       * header. See Mozilla Bugzilla #246651. 
    249709       */ 
    250710      if (this.transport.overrideMimeType) 
     
    265725  }, 
    266726 
     727  header: function(name) { 
     728    try { 
     729      return this.transport.getResponseHeader(name); 
     730    } catch (e) {} 
     731  }, 
     732 
     733  evalJSON: function() { 
     734    try { 
     735      return eval('(' + this.header('X-JSON') + ')'); 
     736    } catch (e) {} 
     737  }, 
     738 
     739  evalResponse: function() { 
     740    try { 
     741      return eval(this.transport.responseText); 
     742    } catch (e) { 
     743      this.dispatchException(e); 
     744    } 
     745  }, 
     746 
    267747  respondToReadyState: function(readyState) { 
    268748    var event = Ajax.Request.Events[readyState]; 
    269  
    270     if (event == 'Complete' && this.responseIsFailure()) 
    271       (this.options['on' + this.transport.status] 
    272        || this.options.onFailure 
    273        || Prototype.emptyFunction)(this.transport); 
    274  
    275     (this.options['on' + event] || Prototype.emptyFunction)(this.transport);     
     749    var transport = this.transport, json = this.evalJSON(); 
     750 
     751    if (event == 'Complete') { 
     752      try { 
     753        (this.options['on' + this.transport.status] 
     754         || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] 
     755         || Prototype.emptyFunction)(transport, json); 
     756      } catch (e) { 
     757        this.dispatchException(e); 
     758      } 
     759 
     760      if ((this.header('Content-type') || '').match(/^text\/javascript/i)) 
     761        this.evalResponse(); 
     762    } 
     763 
     764    try { 
     765      (this.options['on' + event] || Prototype.emptyFunction)(transport, json); 
     766      Ajax.Responders.dispatch('on' + event, this, transport, json); 
     767    } catch (e) { 
     768      this.dispatchException(e); 
     769    } 
     770 
     771    /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ 
     772    if (event == 'Complete') 
     773      this.transport.onreadystatechange = Prototype.emptyFunction; 
     774  }, 
     775 
     776  dispatchException: function(exception) { 
     777    (this.options.onException || Prototype.emptyFunction)(this, exception); 
     778    Ajax.Responders.dispatch('onException', this, exception); 
    276779  } 
    277780}); 
    278781 
    279782Ajax.Updater = Class.create(); 
    280 Ajax.Updater.ScriptFragment = '(?:<script.*?>)((\n|.)*?)(?:<\/script>)'; 
    281  
    282 Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({ 
     783 
     784Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { 
    283785  initialize: function(container, url, options) { 
    284786    this.containers = { 
     
    292794 
    293795    var onComplete = this.options.onComplete || Prototype.emptyFunction; 
    294     this.options.onComplete = (function() { 
     796    this.options.onComplete = (function(transport, object) { 
    295797      this.updateContent(); 
    296       onComplete(this.transport);       
     798      onComplete(transport, object); 
    297799    }).bind(this); 
    298800 
     
    303805    var receiver = this.responseIsSuccess() ? 
    304806      this.containers.success : this.containers.failure; 
    305  
    306     var match    = new RegExp(Ajax.Updater.ScriptFragment, 'img'); 
    307     var response = this.transport.responseText.replace(match, ''); 
    308     var scripts  = this.transport.responseText.match(match); 
     807    var response = this.transport.responseText; 
     808 
     809    if (!this.options.evalScripts) 
     810      response = response.stripScripts(); 
    309811 
    310812    if (receiver) { 
     
    312814        new this.options.insertion(receiver, response); 
    313815      } else { 
    314         receiver.innerHTML = response; 
     816        Element.update(receiver, response); 
    315817      } 
    316818    } 
     
    318820    if (this.responseIsSuccess()) { 
    319821      if (this.onComplete) 
    320         setTimeout((function() {this.onComplete( 
    321           this.transport)}).bind(this), 10); 
    322     } 
    323  
    324     if (this.options.evalScripts && scripts) { 
    325       match = new RegExp(Ajax.Updater.ScriptFragment, 'im'); 
    326       setTimeout((function() { 
    327         for (var i = 0; i < scripts.length; i++) 
    328           eval(scripts[i].match(match)[1]); 
    329       }).bind(this), 10); 
     822        setTimeout(this.onComplete.bind(this), 10); 
    330823    } 
    331824  } 
     
    333826 
    334827Ajax.PeriodicalUpdater = Class.create(); 
    335 Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({ 
     828Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { 
    336829  initialize: function(container, url, options) { 
    337830    this.setOptions(options); 
     
    339832 
    340833    this.frequency = (this.options.frequency || 2); 
    341     this.decay = 1; 
     834    this.decay = (this.options.decay || 1); 
    342835 
    343836    this.updater = {}; 
     
    356849    this.updater.onComplete = undefined; 
    357850    clearTimeout(this.timer); 
    358     (this.onComplete || Ajax.emptyFunction).apply(this, arguments); 
     851    (this.onComplete || Prototype.emptyFunction).apply(this, arguments); 
    359852  }, 
    360853 
    361854  updateComplete: function(request) { 
    362855    if (this.options.decay) { 
    363       this.decay = (request.responseText == this.lastText ?  
     856      this.decay = (request.responseText == this.lastText ? 
    364857        this.decay * this.options.decay : 1); 
    365858 
    366859      this.lastText = request.responseText; 
    367860    } 
    368     this.timer = setTimeout(this.onTimerEvent.bind(this),  
     861    this.timer = setTimeout(this.onTimerEvent.bind(this), 
    369862      this.decay * this.frequency * 1000); 
    370863  }, 
     
    374867  } 
    375868}); 
    376  
    377 document.getElementsByClassName = function(className) { 
    378   var children = document.getElementsByTagName('*') || document.all; 
    379   var elements = new Array(); 
    380    
    381   for (var i = 0; i < children.length; i++) { 
    382     var child = children[i]; 
    383     var classNames = child.className.split(' '); 
    384     for (var j = 0; j < classNames.length; j++) { 
    385       if (classNames[j] == className) { 
    386         elements.push(child); 
    387         break; 
    388       } 
    389     } 
    390   } 
    391    
    392   return elements; 
     869function $() { 
     870  var results = [], element; 
     871  for (var i = 0; i < arguments.length; i++) { 
     872    element = arguments[i]; 
     873    if (typeof element == 'string') 
     874      element = document.getElementById(element); 
     875    results.push(Element.extend(element)); 
     876  } 
     877  return results.length < 2 ? results[0] : results; 
     878} 
     879 
     880document.getElementsByClassName = function(className, parentElement) { 
     881  var children = ($(parentElement) || document.body).getElementsByTagName('*'); 
     882  return $A(children).inject([], function(elements, child) { 
     883    if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) 
     884      elements.push(Element.extend(child)); 
     885    return elements; 
     886  }); 
    393887} 
    394888 
    395889/*--------------------------------------------------------------------------*/ 
    396890 
    397 if (!window.Element) { 
     891if (!window.Element) 
    398892  var Element = new Object(); 
    399 } 
    400  
    401 Object.extend(Element, { 
     893 
     894Element.extend = function(element) { 
     895  if (!element) return; 
     896  if (_nativeExtensions) return element; 
     897 
     898  if (!element._extended && element.tagName && element != window) { 
     899    var methods = Element.Methods, cache = Element.extend.cache; 
     900    for (property in methods) { 
     901      var value = methods[property]; 
     902      if (typeof value == 'function') 
     903        element[property] = cache.findOrStore(value); 
     904    } 
     905  } 
     906 
     907  element._extended = true; 
     908  return element; 
     909} 
     910 
     911Element.extend.cache = { 
     912  findOrStore: function(value) { 
     913    return this[value] = this[value] || function() { 
     914      return value.apply(null, [this].concat($A(arguments))); 
     915    } 
     916  } 
     917} 
     918 
     919Element.Methods = { 
     920  visible: function(element) { 
     921    return $(element).style.display != 'none'; 
     922  }, 
     923 
    402924  toggle: function() { 
    403925    for (var i = 0; i < arguments.length; i++) { 
    404926      var element = $(arguments[i]); 
    405       element.style.display =  
    406         (element.style.display == 'none' ? '' : 'none'); 
     927      Element[Element.visible(element) ? 'hide' : 'show'](element); 
    407928    } 
    408929  }, 
     
    426947    element.parentNode.removeChild(element); 
    427948  }, 
    428     
     949 
     950  update: function(element, html) { 
     951    $(element).innerHTML = html.stripScripts(); 
     952    setTimeout(function() {html.evalScripts()}, 10); 
     953  }, 
     954 
     955  replace: function(element, html) { 
     956    element = $(element); 
     957    if (element.outerHTML) { 
     958      element.outerHTML = html.stripScripts(); 
     959    } else { 
     960      var range = element.ownerDocument.createRange(); 
     961      range.selectNodeContents(element); 
     962      element.parentNode.replaceChild( 
     963        range.createContextualFragment(html.stripScripts()), element); 
     964    } 
     965    setTimeout(function() {html.evalScripts()}, 10); 
     966  }, 
     967 
    429968  getHeight: function(element) { 
    430969    element = $(element); 
    431     return element.offsetHeight;  
     970    return element.offsetHeight; 
     971  }, 
     972 
     973  classNames: function(element) { 
     974    return new Element.ClassNames(element); 
    432975  }, 
    433976 
    434977  hasClassName: function(element, className) { 
    435     element = $(element); 
    436     if (!element) 
    437       return; 
    438     var a = element.className.split(' '); 
    439     for (var i = 0; i < a.length; i++) { 
    440       if (a[i] == className) 
    441         return true; 
    442     } 
    443     return false; 
     978    if (!(element = $(element))) return; 
     979    return Element.classNames(element).include(className); 
    444980  }, 
    445981 
    446982  addClassName: function(element, className) { 
    447     element = $(element); 
    448     Element.removeClassName(element, className); 
    449     element.className += ' ' + className; 
     983    if (!(element = $(element))) return; 
     984    return Element.classNames(element).add(className); 
    450985  }, 
    451986 
    452987  removeClassName: function(element, className) { 
    453     element = $(element); 
    454     if (!element) 
    455       return; 
    456     var newClassName = ''; 
    457     var a = element.className.split(' '); 
    458     for (var i = 0; i < a.length; i++) { 
    459       if (a[i] != className) { 
    460         if (i > 0) 
    461           newClassName += ' '; 
    462         newClassName += a[i]; 
    463       } 
    464     } 
    465     element.className = newClassName; 
    466   }, 
    467    
     988    if (!(element = $(element))) return; 
     989    return Element.classNames(element).remove(className); 
     990  }, 
     991 
    468992  // removes whitespace-only text node children 
    469993  cleanWhitespace: function(element) { 
    470     var element = $(element); 
     994    element = $(element); 
    471995    for (var i = 0; i < element.childNodes.length; i++) { 
    472996      var node = element.childNodes[i]; 
    473       if (node.nodeType == 3 && !/\S/.test(node.nodeValue))  
     997      if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) 
    474998        Element.remove(node); 
    475999    } 
    476   } 
    477 }); 
     1000  }, 
     1001 
     1002  empty: function(element) { 
     1003    return $(element).innerHTML.match(/^\s*$/); 
     1004  }, 
     1005 
     1006  childOf: function(element, ancestor) { 
     1007    element = $(element), ancestor = $(ancestor); 
     1008    while (element = element.parentNode) 
     1009      if (element == ancestor) return true; 
     1010    return false; 
     1011  }, 
     1012 
     1013  scrollTo: function(element) { 
     1014    element = $(element); 
     1015    var x = element.x ? element.x : element.offsetLeft, 
     1016        y = element.y ? element.y : element.offsetTop; 
     1017    window.scrollTo(x, y); 
     1018  }, 
     1019 
     1020  getStyle: function(element, style) { 
     1021    element = $(element); 
     1022    var value = element.style[style.camelize()]; 
     1023    if (!value) { 
     1024      if (document.defaultView && document.defaultView.getComputedStyle) { 
     1025        var css = document.defaultView.getComputedStyle(element, null); 
     1026        value = css ? css.getPropertyValue(style) : null; 
     1027      } else if (element.currentStyle) { 
     1028        value = element.currentStyle[style.camelize()]; 
     1029      } 
     1030    } 
     1031 
     1032    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) 
     1033      if (Element.getStyle(element, 'position') == 'static') value = 'auto'; 
     1034 
     1035    return value == 'auto' ? null : value; 
     1036  }, 
     1037 
     1038  setStyle: function(element, style) { 
     1039    element = $(element); 
     1040    for (var name in style) 
     1041      element.style[name.camelize()] = style[name]; 
     1042  }, 
     1043 
     1044  getDimensions: function(element) { 
     1045    element = $(element); 
     1046    if (Element.getStyle(element, 'display') != 'none') 
     1047      return {width: element.offsetWidth, height: element.offsetHeight}; 
     1048 
     1049    // All *Width and *Height properties give 0 on elements with display none, 
     1050    // so enable the element temporarily 
     1051    var els = element.style; 
     1052    var originalVisibility = els.visibility; 
     1053    var originalPosition = els.position; 
     1054    els.visibility = 'hidden'; 
     1055    els.position = 'absolute'; 
     1056    els.display = ''; 
     1057    var originalWidth = element.clientWidth; 
     1058    var originalHeight = element.clientHeight; 
     1059    els.display = 'none'; 
     1060    els.position = originalPosition; 
     1061    els.visibility = originalVisibility; 
     1062    return {width: originalWidth, height: originalHeight}; 
     1063  }, 
     1064 
     1065  makePositioned: function(element) { 
     1066    element = $(element); 
     1067    var pos = Element.getStyle(element, 'position'); 
     1068    if (pos == 'static' || !pos) { 
     1069      element._madePositioned = true; 
     1070      element.style.position = 'relative'; 
     1071      // Opera returns the offset relative to the positioning context, when an 
     1072      // element is position relative but top and left have not been defined 
     1073      if (window.opera) { 
     1074        element.style.top = 0; 
     1075        element.style.left = 0; 
     1076      } 
     1077    } 
     1078  }, 
     1079 
     1080  undoPositioned: function(element) { 
     1081    element = $(element); 
     1082    if (element._madePositioned) { 
     1083      element._madePositioned = undefined; 
     1084      element.style.position = 
     1085        element.style.top = 
     1086        element.style.left = 
     1087        element.style.bottom = 
     1088        element.style.right = ''; 
     1089    } 
     1090  }, 
     1091 
     1092  makeClipping: function(element) { 
     1093    element = $(element); 
     1094    if (element._overflow) return; 
     1095    element._overflow = element.style.overflow; 
     1096    if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') 
     1097      element.style.overflow = 'hidden'; 
     1098  }, 
     1099 
     1100  undoClipping: function(element) { 
     1101    element = $(element); 
     1102    if (element._overflow) return; 
     1103    element.style.overflow = element._overflow; 
     1104    element._overflow = undefined; 
     1105  } 
     1106} 
     1107 
     1108Object.extend(Element, Element.Methods); 
     1109 
     1110var _nativeExtensions = false; 
     1111 
     1112if(!HTMLElement && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) { 
     1113  var HTMLElement = {} 
     1114  HTMLElement.prototype = document.createElement('div').__proto__; 
     1115} 
     1116 
     1117Element.addMethods = function(methods) { 
     1118  Object.extend(Element.Methods, methods || {}); 
     1119 
     1120  if(typeof HTMLElement != 'undefined') { 
     1121    var methods = Element.Methods, cache = Element.extend.cache; 
     1122    for (property in methods) { 
     1123      var value = methods[property]; 
     1124      if (typeof value == 'function') 
     1125        HTMLElement.prototype[property] = cache.findOrStore(value); 
     1126    } 
     1127    _nativeExtensions = true; 
     1128  } 
     1129} 
     1130 
     1131Element.addMethods(); 
    4781132 
    4791133var Toggle = new Object(); 
     
    4891143  initialize: function(element, content) { 
    4901144    this.element = $(element); 
    491     this.content = content; 
    492      
     1145    this.content = content.stripScripts(); 
     1146 
    4931147    if (this.adjacency && this.element.insertAdjacentHTML) { 
    494       this.element.insertAdjacentHTML(this.adjacency, this.content); 
     1148      try { 
     1149        this.element.insertAdjacentHTML(this.adjacency, this.content); 
     1150      } catch (e) { 
     1151        var tagName = this.element.tagName.toLowerCase(); 
     1152        if (tagName == 'tbody' || tagName == 'tr') { 
     1153          this.insertContent(this.contentFromAnonymousTable()); 
     1154        } else { 
     1155          throw e; 
     1156        } 
     1157      } 
    4951158    } else { 
    4961159      this.range = this.element.ownerDocument.createRange(); 
    4971160      if (this.initializeRange) this.initializeRange(); 
    498       this.fragment = this.range.createContextualFragment(this.content); 
    499       this.insertContent(); 
    500     } 
     1161      this.insertContent([this.range.createContextualFragment(this.content)]); 
     1162    } 
     1163 
     1164    setTimeout(function() {content.evalScripts()}, 10); 
     1165  }, 
     1166 
     1167  contentFromAnonymousTable: function() { 
     1168    var div = document.createElement('div'); 
     1169    div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>'; 
     1170    return $A(div.childNodes[0].childNodes[0].childNodes); 
    5011171  } 
    5021172} 
     
    5051175 
    5061176Insertion.Before = Class.create(); 
    507 Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({ 
     1177Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { 
    5081178  initializeRange: function() { 
    5091179    this.range.setStartBefore(this.element); 
    5101180  }, 
    511    
    512   insertContent: function() { 
    513     this.element.parentNode.insertBefore(this.fragment, this.element); 
     1181 
     1182  insertContent: function(fragments) { 
     1183    fragments.each((function(fragment) { 
     1184      this.element.parentNode.insertBefore(fragment, this.element); 
     1185    }).bind(this)); 
    5141186  } 
    5151187}); 
    5161188 
    5171189Insertion.Top = Class.create(); 
    518 Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({ 
     1190Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { 
    5191191  initializeRange: function() { 
    5201192    this.range.selectNodeContents(this.element); 
    5211193    this.range.collapse(true); 
    5221194  }, 
    523    
    524   insertContent: function() {   
    525     this.element.insertBefore(this.fragment, this.element.firstChild); 
     1195 
     1196  insertContent: function(fragments) { 
     1197    fragments.reverse(false).each((function(fragment) { 
     1198      this.element.insertBefore(fragment, this.element.firstChild); 
     1199    }).bind(this)); 
    5261200  } 
    5271201}); 
    5281202 
    5291203Insertion.Bottom = Class.create(); 
    530 Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({ 
     1204Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { 
    5311205  initializeRange: function() { 
    5321206    this.range.selectNodeContents(this.element); 
    5331207    this.range.collapse(this.element); 
    5341208  }, 
    535    
    536   insertContent: function() { 
    537     this.element.appendChild(this.fragment); 
     1209 
     1210  insertContent: function(fragments) { 
     1211    fragments.each((function(fragment) { 
     1212      this.element.appendChild(fragment); 
     1213    }).bind(this)); 
    5381214  } 
    5391215}); 
    5401216 
    5411217Insertion.After = Class.create(); 
    542 Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({ 
     1218Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { 
    5431219  initializeRange: function() { 
    5441220    this.range.setStartAfter(this.element); 
    5451221  }, 
    546    
    547   insertContent: function() { 
    548     this.element.parentNode.insertBefore(this.fragment,  
    549       this.element.nextSibling); 
     1222 
     1223  insertContent: function(fragments) { 
     1224    fragments.each((function(fragment) { 
     1225      this.element.parentNode.insertBefore(fragment, 
     1226        this.element.nextSibling); 
     1227    }).bind(this)); 
    5501228  } 
    5511229}); 
    5521230 
     1231/*--------------------------------------------------------------------------*/ 
     1232 
     1233Element.ClassNames = Class.create(); 
     1234Element.ClassNames.prototype = { 
     1235  initialize: function(element) { 
     1236    this.element = $(element); 
     1237  }, 
     1238 
     1239  _each: function(iterator) { 
     1240    this.element.className.split(/\s+/).select(function(name) { 
     1241      return name.length > 0; 
     1242    })._each(iterator); 
     1243  }, 
     1244 
     1245  set: function(className) { 
     1246    this.element.className = className; 
     1247  }, 
     1248 
     1249  add: function(classNameToAdd) { 
     1250    if (this.include(classNameToAdd)) return; 
     1251    this.set(this.toArray().concat(classNameToAdd).join(' ')); 
     1252  }, 
     1253 
     1254  remove: function(classNameToRemove) { 
     1255    if (!this.include(classNameToRemove)) return; 
     1256    this.set(this.select(function(className) { 
     1257      return className != classNameToRemove; 
     1258    }).join(' ')); 
     1259  }, 
     1260 
     1261  toString: function() { 
     1262    return this.toArray().join(' '); 
     1263  } 
     1264} 
     1265 
     1266Object.extend(Element.ClassNames.prototype, Enumerable); 
     1267var Selector = Class.create(); 
     1268Selector.prototype = { 
     1269  initialize: function(expression) { 
     1270    this.params = {classNames: []}; 
     1271    this.expression = expression.toString().strip(); 
     1272    this.parseExpression(); 
     1273    this.compileMatcher(); 
     1274  }, 
     1275 
     1276  parseExpression: function() { 
     1277    function abort(message) { throw 'Parse error in selector: ' + message; } 
     1278 
     1279    if (this.expression == '')  abort('empty expression'); 
     1280 
     1281    var params = this.params, expr = this.expression, match, modifier, clause, rest; 
     1282    while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { 
     1283      params.attributes = params.attributes || []; 
     1284      params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); 
     1285      expr = match[1]; 
     1286    } 
     1287 
     1288    if (expr == '*') return this.params.wildcard = true; 
     1289 
     1290    while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { 
     1291      modifier = match[1], clause = match[2], rest = match[3]; 
     1292      switch (modifier) { 
     1293        case '#':       params.id = clause; break; 
     1294        case '.':       params.classNames.push(clause); break; 
     1295        case '': 
     1296        case undefined: params.tagName = clause.toUpperCase(); break; 
     1297        default:        abort(expr.inspect()); 
     1298      } 
     1299      expr = rest; 
     1300    } 
     1301 
     1302    if (expr.length > 0) abort(expr.inspect()); 
     1303  }, 
     1304 
     1305  buildMatchExpression: function() { 
     1306    var params = this.params, conditions = [], clause; 
     1307 
     1308    if (params.wildcard) 
     1309      conditions.push('true'); 
     1310    if (clause = params.id) 
     1311      conditions.push('element.id == ' + clause.inspect()); 
     1312    if (clause = params.tagName) 
     1313      conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); 
     1314    if ((clause = params.classNames).length > 0) 
     1315      for (var i = 0; i < clause.length; i++) 
     1316        conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')'); 
     1317    if (clause = params.attributes) { 
     1318      clause.each(function(attribute) { 
     1319        var value = 'element.getAttribute(' + attribute.name.inspect() + ')'; 
     1320        var splitValueBy = function(delimiter) { 
     1321          return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; 
     1322        } 
     1323 
     1324        switch (attribute.operator) { 
     1325          case '=':       conditions.push(value + ' == ' + attribute.value.inspect()); break; 
     1326          case '~=':      conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; 
     1327          case '|=':      conditions.push( 
     1328                            splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() 
     1329                          ); break; 
     1330          case '!=':      conditions.push(value + ' != ' + attribute.value.inspect()); break; 
     1331          case '': 
     1332          case undefined: conditions.push(value + ' != null'); break; 
     1333          default:        throw 'Unknown operator ' + attribute.operator + ' in selector'; 
     1334        } 
     1335      }); 
     1336    } 
     1337 
     1338    return conditions.join(' && '); 
     1339  }, 
     1340 
     1341  compileMatcher: function() { 
     1342    this.match = new Function('element', 'if (!element.tagName) return false; \ 
     1343      return ' + this.buildMatchExpression()); 
     1344  }, 
     1345 
     1346  findElements: function(scope) { 
     1347    var element; 
     1348 
     1349    if (element = $(this.params.id)) 
     1350      if (this.match(element)) 
     1351        if (!scope || Element.childOf(element, scope)) 
     1352          return [element]; 
     1353 
     1354    scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); 
     1355 
     1356    var results = []; 
     1357    for (var i = 0; i < scope.length; i++) 
     1358      if (this.match(element = scope[i])) 
     1359        results.push(Element.extend(element)); 
     1360 
     1361    return results; 
     1362  }, 
     1363 
     1364  toString: function() { 
     1365    return this.expression; 
     1366  } 
     1367} 
     1368 
     1369function $$() { 
     1370  return $A(arguments).map(function(expression) { 
     1371    return expression.strip().split(/\s+/).inject([null], function(results, expr) { 
     1372      var selector = new Selector(expr); 
     1373      return results.map(selector.findElements.bind(selector)).flatten(); 
     1374    }); 
     1375  }).flatten(); 
     1376} 
    5531377var Field = { 
    5541378  clear: function() { 
     
    5601384    $(element).focus(); 
    5611385  }, 
    562    
     1386 
    5631387  present: function() { 
    5641388    for (var i = 0; i < arguments.length; i++) 
     
    5661390    return true; 
    5671391  }, 
    568    
     1392 
    5691393  select: function(element) { 
    5701394    $(element).select(); 
    5711395  }, 
    572     
     1396 
    5731397  activate: function(element) { 
    574     $(element).focus(); 
    575     $(element).select(); 
     1398    element = $(element); 
     1399    element.focus(); 
     1400    if (element.select) 
     1401      element.select(); 
    5761402  } 
    5771403} 
     
    5831409    var elements = Form.getElements($(form)); 
    5841410    var queryComponents = new Array(); 
    585      
     1411 
    5861412    for (var i = 0; i < elements.length; i++) { 
    5871413      var queryComponent = Form.Element.serialize(elements[i]); 
     
    5891415        queryComponents.push(queryComponent); 
    5901416    } 
    591      
     1417 
    5921418    return queryComponents.join('&'); 
    5931419  }, 
    594    
     1420 
    5951421  getElements: function(form) { 
    596     var form = $(form); 
     1422    form = $(form); 
    5971423    var elements = new Array(); 
    5981424 
    599     for (tagName in Form.Element.Serializers) { 
     1425    for (var tagName in Form.Element.Serializers) { 
    6001426      var tagElements = form.getElementsByTagName(tagName); 
    6011427      for (var j = 0; j < tagElements.length; j++) 
     
    6041430    return elements; 
    6051431  }, 
    606    
     1432 
    6071433  getInputs: function(form, typeName, name) { 
    608     var form = $(form); 
     1434    form = $(form); 
    6091435    var inputs = form.getElementsByTagName('input'); 
    610      
     1436 
    6111437    if (!typeName && !name) 
    6121438      return inputs; 
    613        
     1439 
    6141440    var matchingInputs = new Array(); 
    6151441    for (var i = 0; i < inputs.length; i++) { 
    6161442      var input = inputs[i]; 
    6171443      if ((typeName && input.type != typeName) || 
    618           (name && input.name != name))  
     1444          (name && input.name != name)) 
    6191445        continue; 
    6201446      matchingInputs.push(input); 
     
    6411467  }, 
    6421468 
     1469  findFirstElement: function(form) { 
     1470    return Form.getElements(form).find(function(element) { 
     1471      return element.type != 'hidden' && !element.disabled && 
     1472        ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); 
     1473    }); 
     1474  }, 
     1475 
    6431476  focusFirstElement: function(form) { 
    644     var form = $(form); 
    645     var elements = Form.getElements(form); 
    646     for (var i = 0; i < elements.length; i++) { 
    647       var element = elements[i]; 
    648       if (element.type != 'hidden' && !element.disabled) { 
    649         Field.activate(element); 
    650         break; 
    651       } 
    652     } 
     1477    Field.activate(Form.findFirstElement(form)); 
    6531478  }, 
    6541479 
     
    6601485Form.Element = { 
    6611486  serialize: function(element) { 
    662     var element = $(element); 
     1487    element = $(element); 
    6631488    var method = element.tagName.toLowerCase(); 
    6641489    var parameter = Form.Element.Serializers[method](element); 
    665      
    666     if (parameter) 
    667       return encodeURIComponent(parameter[0]) + '=' +  
    668         encodeURIComponent(parameter[1]);                    
    669   }, 
    670    
     1490 
     1491    if (parameter) { 
     1492      var key = encodeURIComponent(parameter[0]); 
     1493      if (key.length == 0) return; 
     1494 
     1495      if (parameter[1].constructor != Array) 
     1496        parameter[1] = [parameter[1]]; 
     1497 
     1498      return parameter[1].map(function(value) { 
     1499        return key + '=' + encodeURIComponent(value); 
     1500      }).join('&'); 
     1501    } 
     1502  }, 
     1503 
    6711504  getValue: function(element) { 
    672     var element = $(element); 
     1505    element = $(element); 
    6731506    var method = element.tagName.toLowerCase(); 
    6741507    var parameter = Form.Element.Serializers[method](element); 
    675      
    676     if (parameter)  
     1508 
     1509    if (parameter) 
    6771510      return parameter[1]; 
    6781511  } 
     
    6871520      case 'text': 
    6881521        return Form.Element.Serializers.textarea(element); 
    689       case 'checkbox':   
     1522      case 'checkbox': 
    6901523      case 'radio': 
    6911524        return Form.Element.Serializers.inputSelector(element); 
     
    7041537 
    7051538  select: function(element) { 
    706     var value = ''; 
    707     if (element.type == 'select-one') { 
    708       var index = element.selectedIndex; 
    709       if (index >= 0) 
    710         value = element.options[index].value || element.options[index].text; 
    711     } else { 
    712       value = new Array(); 
    713       for (var i = 0; i < element.length; i++) { 
    714         var opt = element.options[i]; 
    715         if (opt.selected) 
    716           value.push(opt.value || opt.text); 
    717       } 
     1539    return Form.Element.Serializers[element.type == 'select-one' ? 
     1540      'selectOne' : 'selectMany'](element); 
     1541  }, 
     1542 
     1543  selectOne: function(element) { 
     1544    var value = '', opt, index = element.selectedIndex; 
     1545    if (index >= 0) { 
     1546      opt = element.options[index]; 
     1547      value = opt.value || opt.text; 
     1548    } 
     1549    return [element.name, value]; 
     1550  }, 
     1551 
     1552  selectMany: function(element) { 
     1553    var value = []; 
     1554    for (var i = 0; i < element.length; i++) { 
     1555      var opt = element.options[i]; 
     1556      if (opt.selected) 
     1557        value.push(opt.value || opt.text); 
    7181558    } 
    7191559    return [element.name, value]; 
     
    7331573    this.element   = $(element); 
    7341574    this.callback  = callback; 
    735      
     1575 
    7361576    this.lastValue = this.getValue(); 
    7371577    this.registerCallback(); 
    7381578  }, 
    739    
     1579 
    7401580  registerCallback: function() { 
    7411581    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); 
    7421582  }, 
    743    
     1583 
    7441584  onTimerEvent: function() { 
    7451585    var value = this.getValue(); 
     
    7521592 
    7531593Form.Element.Observer = Class.create(); 
    754 Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({ 
     1594Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { 
    7551595  getValue: function() { 
    7561596    return Form.Element.getValue(this.element); 
     
    7591599 
    7601600Form.Observer = Class.create(); 
    761 Form.Observer.prototype = (new Abstract.TimedObserver()).extend({ 
     1601Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { 
    7621602  getValue: function() { 
    7631603    return Form.serialize(this.element); 
     
    7721612    this.element  = $(element); 
    7731613    this.callback = callback; 
    774      
     1614 
    7751615    this.lastValue = this.getValue(); 
    7761616    if (this.element.tagName.toLowerCase() == 'form') 
     
    7791619      this.registerCallback(this.element); 
    7801620  }, 
    781    
     1621 
    7821622  onElementEvent: function() { 
    7831623    var value = this.getValue(); 
     
    7871627    } 
    7881628  }, 
    789    
     1629 
    7901630  registerFormCallbacks: function() { 
    7911631    var elements = Form.getElements(this.element); 
     
    7931633      this.registerCallback(elements[i]); 
    7941634  }, 
    795    
     1635 
    7961636  registerCallback: function(element) { 
    7971637    if (element.type) { 
    7981638      switch (element.type.toLowerCase()) { 
    799         case 'checkbox':   
     1639        case 'checkbox': 
    8001640        case 'radio': 
    801           element.target = this; 
    802           element.prev_onclick = element.onclick || Prototype.emptyFunction; 
    803           element.onclick = function() { 
    804             this.prev_onclick();  
    805             this.target.onElementEvent(); 
    806           } 
     1641          Event.observe(element, 'click', this.onElementEvent.bind(this)); 
    8071642          break; 
    8081643        case 'password': 
     
    8111646        case 'select-one': 
    8121647        case 'select-multiple': 
    813           element.target = this; 
    814           element.prev_onchange = element.onchange || Prototype.emptyFunction; 
    815           element.onchange = function() { 
    816             this.prev_onchange();  
    817             this.target.onElementEvent(); 
    818           } 
     1648          Event.observe(element, 'change', this.onElementEvent.bind(this)); 
    8191649          break; 
    8201650      } 
    821     }     
     1651    } 
    8221652  } 
    8231653} 
    8241654 
    8251655Form.Element.EventObserver = Class.create(); 
    826 Form.Element.EventObserver.prototype = (new Abstract.EventObserver()).extend({ 
     1656Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { 
    8271657  getValue: function() { 
    8281658    return Form.Element.getValue(this.element); 
     
    8311661 
    8321662Form.EventObserver = Class.create(); 
    833 Form.EventObserver.prototype = (new Abstract.EventObserver()).extend({ 
     1663Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { 
    8341664  getValue: function() { 
    8351665    return Form.serialize(this.element); 
    8361666  } 
    8371667}); 
    838  
    839  
    8401668if (!window.Event) { 
    8411669  var Event = new Object(); 
     
    8631691 
    8641692  pointerX: function(event) { 
    865     return event.pageX || (event.clientX +  
     1693    return event.pageX || (event.clientX + 
    8661694      (document.documentElement.scrollLeft || document.body.scrollLeft)); 
    8671695  }, 
    8681696 
    8691697  pointerY: function(event) { 
    870     return event.pageY || (event.clientY +  
     1698    return event.pageY || (event.clientY + 
    8711699      (document.documentElement.scrollTop || document.body.scrollTop)); 
    8721700  }, 
    8731701 
    8741702  stop: function(event) { 
    875     if (event.preventDefault) {  
    876       event.preventDefault();  
    877       event.stopPropagation();  
     1703    if (event.preventDefault) { 
     1704      event.preventDefault(); 
     1705      event.stopPropagation(); 
    8781706    } else { 
    8791707      event.returnValue = false; 
     1708      event.cancelBubble = true; 
    8801709    } 
    8811710  }, 
     
    8911720  }, 
    8921721 
     1722  observers: false, 
     1723 
     1724  _observeAndCache: function(element, name, observer, useCapture) { 
     1725    if (!this.observers) this.observers = []; 
     1726    if (element.addEventListener) { 
     1727      this.observers.push([element, name, observer, useCapture]); 
     1728      element.addEventListener(name, observer, useCapture); 
     1729    } else if (element.attachEvent) { 
     1730      this.observers.push([element, name, observer, useCapture]); 
     1731      element.attachEvent('on' + name, observer); 
     1732    } 
     1733  }, 
     1734 
     1735  unloadCache: function() { 
     1736    if (!Event.observers) return; 
     1737    for (var i = 0; i < Event.observers.length; i++) { 
     1738      Event.stopObserving.apply(this, Event.observers[i]); 
     1739      Event.observers[i][0] = null; 
     1740    } 
     1741    Event.observers = false; 
     1742  }, 
     1743 
    8931744  observe: function(element, name, observer, useCapture) { 
    8941745    var element = $(element); 
    8951746    useCapture = useCapture || false; 
    896      
    897     if (name == 'keypress') { 
    898       if (navigator.appVersion.indexOf('AppleWebKit') > 0) { 
    899         element.addEventListener('keydown', observer, useCapture); 
    900         return; 
    901       } 
    902       if (element.addEventListener) { 
    903         element.addEventListener('keypress', observer, useCapture); 
    904       } else if (element.attachEvent) { 
    905         element.attachEvent('onkeydown', observer); 
    906       } 
    907     } else { 
    908       if (element.addEventListener) { 
    909         element.addEventListener(name, observer, useCapture); 
    910       } else if (element.attachEvent) { 
    911         element.attachEvent('on' + name, observer); 
    912       } 
    913     } 
     1747 
     1748    if (name == 'keypress' && 
     1749        (navigator.appVersion.match(/Konqueror|Safari|KHTML/) 
     1750        || element.attachEvent)) 
     1751      name = 'keydown'; 
     1752 
     1753    this._observeAndCache(element, name, observer, useCapture); 
    9141754  }, 
    9151755 
     
    9171757    var element = $(element); 
    9181758    useCapture = useCapture || false; 
    919      
    920     if (name == 'keypress') { 
    921       if (navigator.appVersion.indexOf('AppleWebKit') > 0) { 
    922         element.removeEventListener('keydown', observer, useCapture); 
    923         return; 
    924       } 
    925       if (element.removeEventListener) { 
    926         element.removeEventListener('keypress', observer, useCapture); 
    927       } else if (element.detachEvent) { 
    928         element.detachEvent('onkeydown', observer); 
    929       } 
    930     } else { 
    931       if (element.removeEventListener) { 
    932         element.removeEventListener(name, observer, useCapture); 
    933       } else if (element.detachEvent) { 
    934         element.detachEvent('on' + name, observer); 
    935       } 
     1759 
     1760    if (name == 'keypress' && 
     1761        (navigator.appVersion.match(/Konqueror|Safari|KHTML/) 
     1762        || element.detachEvent)) 
     1763      name = 'keydown'; 
     1764 
     1765    if (element.removeEventListener) { 
     1766      element.removeEventListener(name, observer, useCapture); 
     1767    } else if (element.detachEvent) { 
     1768      element.detachEvent('on' + name, observer); 
    9361769    } 
    9371770  } 
    9381771}); 
    9391772 
     1773/* prevent memory leaks in IE */ 
     1774if (navigator.appVersion.match(/\bMSIE\b/)) 
     1775  Event.observe(window, 'unload', Event.unloadCache, false); 
    9401776var Position = { 
    941  
    9421777  // set to true if needed, warning: firefox performance problems 
    9431778  // NOT neeeded for page scrolling, only if draggable contained in 
    9441779  // scrollable elements 
    945   includeScrollOffsets: false,  
     1780  includeScrollOffsets: false, 
    9461781 
    9471782  // must be called before calling withinIncludingScrolloffset, every time the 
    9481783  // page is scrolled 
    9491784  prepare: function() { 
    950     this.deltaX =  window.pageXOffset  
    951                 || document.documentElement.scrollLeft  
    952                 || document.body.scrollLeft  
     1785    this.deltaX =  window.pageXOffset 
     1786                || document.documentElement.scrollLeft 
     1787                || document.body.scrollLeft 
    9531788                || 0; 
    954     this.deltaY =  window.pageYOffset  
    955                 || document.documentElement.scrollTop  
    956                 || document.body.scrollTop  
     1789    this.deltaY =  window.pageYOffset 
     1790                || document.documentElement.scrollTop 
     1791                || document.body.scrollTop 
    9571792                || 0; 
    9581793  }, 
     
    9621797    do { 
    9631798      valueT += element.scrollTop  || 0; 
    964       valueL += element.scrollLeft || 0;  
     1799      valueL += element.scrollLeft || 0; 
    9651800      element = element.parentNode; 
    9661801    } while (element); 
     
    9781813  }, 
    9791814 
     1815  positionedOffset: function(element) { 
     1816    var valueT = 0, valueL = 0; 
     1817    do { 
     1818      valueT += element.offsetTop  || 0; 
     1819      valueL += element.offsetLeft || 0; 
     1820      element = element.offsetParent; 
     1821      if (element) { 
     1822        p = Element.getStyle(element, 'position'); 
     1823        if (p == 'relative' || p == 'absolute') break; 
     1824      } 
     1825    } while (element); 
     1826    return [valueL, valueT]; 
     1827  }, 
     1828 
     1829  offsetParent: function(element) { 
     1830    if (element.offsetParent) return element.offsetParent; 
     1831    if (element == document.body) return element; 
     1832 
     1833    while ((element = element.parentNode) && element != document.body) 
     1834      if (Element.getStyle(element, 'position') != 'static') 
     1835        return element; 
     1836 
     1837    return document.body; 
     1838  }, 
     1839 
    9801840  // caches x/y coordinate pair to use with overlap 
    9811841  within: function(element, x, y) { 
     
    9881848    return (y >= this.offset[1] && 
    9891849            y <  this.offset[1] + element.offsetHeight && 
    990             x >= this.offset[0] &&  
     1850            x >= this.offset[0] && 
    9911851            x <  this.offset[0] + element.offsetWidth); 
    9921852  }, 
     
    10011861    return (this.ycomp >= this.offset[1] && 
    10021862            this.ycomp <  this.offset[1] + element.offsetHeight && 
    1003             this.xcomp >= this.offset[0] &&  
     1863            this.xcomp >= this.offset[0] && 
    10041864            this.xcomp <  this.offset[0] + element.offsetWidth); 
    10051865  }, 
    10061866 
    10071867  // within must be called directly before 
    1008   overlap: function(mode, element) {   
    1009     if (!mode) return 0;   
    1010     if (mode == 'vertical')  
    1011       return ((this.offset[1] + element.offsetHeight) - this.ycomp) /  
     1868  overlap: function(mode, element) { 
     1869    if (!mode) return 0; 
     1870    if (mode == 'vertical') 
     1871      return ((this.offset[1] + element.offsetHeight) - this.ycomp) / 
    10121872        element.offsetHeight; 
    10131873    if (mode == 'horizontal') 
    1014       return ((this.offset[0] + element.offsetWidth) - this.xcomp) /  
     1874      return ((this.offset[0] + element.offsetWidth) - this.xcomp) / 
    10151875        element.offsetWidth; 
    10161876  }, 
     
    10251885    target.style.width  = source.offsetWidth + 'px'; 
    10261886    target.style.height = source.offsetHeight + 'px'; 
    1027   } 
    1028 } 
     1887  }, 
     1888 
     1889  page: function(forElement) { 
     1890    var valueT = 0, valueL = 0; 
     1891 
     1892    var element = forElement; 
     1893    do { 
     1894      valueT += element.offsetTop  || 0; 
     1895      valueL += element.offsetLeft || 0; 
     1896 
     1897      // Safari fix 
     1898      if (element.offsetParent==document.body) 
     1899        if (Element.getStyle(element,'position')=='absolute') break; 
     1900 
     1901    } while (element = element.offsetParent); 
     1902 
     1903    element = forElement; 
     1904    do { 
     1905      valueT -= element.scrollTop  || 0; 
     1906      valueL -= element.scrollLeft || 0; 
     1907    } while (element = element.parentNode); 
     1908 
     1909    return [valueL, valueT]; 
     1910  }, 
     1911 
     1912  clone: function(source, target) { 
     1913    var options = Object.extend({ 
     1914      setLeft:    true, 
     1915      setTop:     true, 
     1916      setWidth:   true, 
     1917      setHeight:  true, 
     1918      offsetTop:  0, 
     1919      offsetLeft: 0 
     1920    }, arguments[2] || {}) 
     1921 
     1922    // find page position of source 
     1923    source = $(source); 
     1924    var p = Position.page(source); 
     1925 
     1926    // find coordinate system to use 
     1927    target = $(target); 
     1928    var delta = [0, 0]; 
     1929    var parent = null; 
     1930    // delta [0,0] will do fine with position: fixed elements, 
     1931    // position:absolute needs offsetParent deltas 
     1932    if (Element.getStyle(target,'position') == 'absolute') { 
     1933      parent = Position.offsetParent(target); 
     1934      delta = Position.page(parent); 
     1935    } 
     1936 
     1937    // correct by body offsets (fixes Safari) 
     1938    if (parent == document.body) { 
     1939      delta[0] -= document.body.offsetLeft; 
     1940      delta[1] -= document.body.offsetTop; 
     1941    } 
     1942 
     1943    // set position 
     1944    if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px'; 
     1945    if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px'; 
     1946    if(options.setWidth)  target.style.width = source.offsetWidth + 'px'; 
     1947    if(options.setHeight) target.style.height = source.offsetHeight + 'px'; 
     1948  }, 
     1949 
     1950  absolutize: function(element) { 
     1951    element = $(element); 
     1952    if (element.style.position == 'absolute') return; 
     1953    Position.prepare(); 
     1954 
     1955    var offsets = Position.positionedOffset(element); 
     1956    var top     = offsets[1]; 
     1957    var left    = offsets[0]; 
     1958    var width   = element.clientWidth; 
     1959    var height  = element.clientHeight; 
     1960 
     1961    element._originalLeft   = left - parseFloat(element.style.left  || 0); 
     1962    element._originalTop    = top  - parseFloat(element.style.top || 0); 
     1963    element._originalWidth  = element.style.width; 
     1964    element._originalHeight = element.style.height; 
     1965 
     1966    element.style.position = 'absolute'; 
     1967    element.style.top    = top + 'px';; 
     1968    element.style.left   = left + 'px';; 
     1969    element.style.width  = width + 'px';; 
     1970    element.style.height = height + 'px';; 
     1971  }, 
     1972 
     1973  relativize: function(element) { 
     1974    element = $(element); 
     1975    if (element.style.position == 'relative') return; 
     1976    Position.prepare(); 
     1977 
     1978    element.style.position = 'relative'; 
     1979    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0); 
     1980    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); 
     1981 
     1982    element.style.top    = top + 'px'; 
     1983    element.style.left   = left + 'px'; 
     1984    element.style.height = element._originalHeight; 
     1985    element.style.width  = element._originalWidth; 
     1986  } 
     1987} 
     1988 
     1989// Safari returns margins on body which is incorrect if the child is absolutely 
     1990// positioned.  For performance reasons, redefine Position.cumulativeOffset for 
     1991// KHTML/WebKit only. 
     1992if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { 
     1993  Position.cumulativeOffset = function(element) { 
     1994    var valueT = 0, valueL = 0; 
     1995    do { 
     1996      valueT += element.offsetTop  || 0; 
     1997      valueL += element.offsetLeft || 0; 
     1998      if (element.offsetParent == document.body) 
     1999        if (Element.getStyle(element, 'position') == 'absolute') break; 
     2000 
     2001      element = element.offsetParent; 
     2002    } while (element); 
     2003 
     2004    return [valueL, valueT]; 
     2005  } 
     2006}