[ Index ]
 

Code source de PRADO 3.0.6

Accédez au Source d'autres logiciels libresSoutenez Angelica Josefina !

title

Body

[fermer]

/framework/Web/Javascripts/js/debug/ -> ajax.js (source)

   1  var Ajax = {
   2    getTransport: function() {
   3      return Try.these(
   4        function() {return new XMLHttpRequest()},
   5        function() {return new ActiveXObject('Msxml2.XMLHTTP')},
   6        function() {return new ActiveXObject('Microsoft.XMLHTTP')}
   7      ) || false;
   8    },
   9    
  10    activeRequestCount: 0
  11  }
  12  
  13  Ajax.Responders = {
  14    responders: [],
  15    
  16    _each: function(iterator) {
  17      this.responders._each(iterator);
  18    },
  19  
  20    register: function(responderToAdd) {
  21      if (!this.include(responderToAdd))
  22        this.responders.push(responderToAdd);
  23    },
  24    
  25    unregister: function(responderToRemove) {
  26      this.responders = this.responders.without(responderToRemove);
  27    },
  28    
  29    dispatch: function(callback, request, transport, json) {
  30      this.each(function(responder) {
  31        if (responder[callback] && typeof responder[callback] == 'function') {
  32          try {
  33            responder[callback].apply(responder, [request, transport, json]);
  34          } catch (e) {}
  35        }
  36      });
  37    }
  38  };
  39  
  40  Object.extend(Ajax.Responders, Enumerable);
  41  
  42  Ajax.Responders.register({
  43    onCreate: function() {
  44      Ajax.activeRequestCount++;
  45    },
  46    
  47    onComplete: function() {
  48      Ajax.activeRequestCount--;
  49    }
  50  });
  51  
  52  Ajax.Base = function() {};
  53  Ajax.Base.prototype = {
  54    setOptions: function(options) {
  55      this.options = {
  56        method:       'post',
  57        asynchronous: true,
  58        contentType:  'application/x-www-form-urlencoded',
  59        parameters:   ''
  60      }
  61      Object.extend(this.options, options || {});
  62    },
  63  
  64    responseIsSuccess: function() {
  65      return this.transport.status == undefined
  66          || this.transport.status == 0 
  67          || (this.transport.status >= 200 && this.transport.status < 300);
  68    },
  69  
  70    responseIsFailure: function() {
  71      return !this.responseIsSuccess();
  72    }
  73  }
  74  
  75  Ajax.Request = Class.create();
  76  Ajax.Request.Events = 
  77    ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
  78  
  79  Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
  80    initialize: function(url, options) {
  81      this.transport = Ajax.getTransport();
  82      this.setOptions(options);
  83      this.request(url);
  84    },
  85  
  86    request: function(url) {
  87      var parameters = this.options.parameters || '';
  88      if (parameters.length > 0) parameters += '&_=';
  89  
  90      try {
  91        this.url = url;
  92        if (this.options.method == 'get' && parameters.length > 0)
  93          this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
  94        
  95        Ajax.Responders.dispatch('onCreate', this, this.transport);
  96        
  97        this.transport.open(this.options.method, this.url, 
  98          this.options.asynchronous);
  99  
 100        if (this.options.asynchronous) {
 101          this.transport.onreadystatechange = this.onStateChange.bind(this);
 102          setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
 103        }
 104  
 105        this.setRequestHeaders();
 106  
 107        var body = this.options.postBody ? this.options.postBody : parameters;
 108        this.transport.send(this.options.method == 'post' ? body : null);
 109  
 110      } catch (e) {
 111        this.dispatchException(e);
 112      }
 113    },
 114  
 115    setRequestHeaders: function() {
 116      var requestHeaders = 
 117        ['X-Requested-With', 'XMLHttpRequest',
 118         'X-Prototype-Version', Prototype.Version,
 119         'Accept', 'text/javascript, text/html, application/xml, text/xml, */*'];
 120  
 121      if (this.options.method == 'post') {
 122        requestHeaders.push('Content-type', this.options.contentType);
 123  
 124        /* Force "Connection: close" for Mozilla browsers to work around
 125         * a bug where XMLHttpReqeuest sends an incorrect Content-length
 126         * header. See Mozilla Bugzilla #246651. 
 127         */
 128        if (this.transport.overrideMimeType)
 129          requestHeaders.push('Connection', 'close');
 130      }
 131  
 132      if (this.options.requestHeaders)
 133        requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
 134  
 135      for (var i = 0; i < requestHeaders.length; i += 2)
 136        this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
 137    },
 138  
 139    onStateChange: function() {
 140      var readyState = this.transport.readyState;
 141      if (readyState != 1)
 142        this.respondToReadyState(this.transport.readyState);
 143    },
 144    
 145    header: function(name) {
 146      try {
 147        return this.transport.getResponseHeader(name);
 148      } catch (e) {}
 149    },
 150    
 151    evalJSON: function() {
 152      try {
 153        return eval('(' + this.header('X-JSON') + ')');
 154      } catch (e) {}
 155    },
 156    
 157    evalResponse: function() {
 158      try {
 159        return eval(this.transport.responseText);
 160      } catch (e) {
 161        this.dispatchException(e);
 162      }
 163    },
 164  
 165    respondToReadyState: function(readyState) {
 166      var event = Ajax.Request.Events[readyState];
 167      var transport = this.transport, json = this.evalJSON();
 168  
 169      if (event == 'Complete') {
 170        try {
 171          (this.options['on' + this.transport.status]
 172           || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
 173           || Prototype.emptyFunction)(transport, json);
 174        } catch (e) {
 175          this.dispatchException(e);
 176        }
 177        
 178        if ((this.header('Content-type') || '').match(/^text\/javascript/i))
 179          this.evalResponse();
 180      }
 181      
 182      try {
 183        (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
 184        Ajax.Responders.dispatch('on' + event, this, transport, json);
 185      } catch (e) {
 186        this.dispatchException(e);
 187      }
 188      
 189      /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
 190      if (event == 'Complete')
 191        this.transport.onreadystatechange = Prototype.emptyFunction;
 192    },
 193    
 194    dispatchException: function(exception) {
 195      (this.options.onException || Prototype.emptyFunction)(this, exception);
 196      Ajax.Responders.dispatch('onException', this, exception);
 197    }
 198  });
 199  
 200  Ajax.Updater = Class.create();
 201  
 202  Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
 203    initialize: function(container, url, options) {
 204      this.containers = {
 205        success: container.success ? $(container.success) : $(container),
 206        failure: container.failure ? $(container.failure) :
 207          (container.success ? null : $(container))
 208      }
 209  
 210      this.transport = Ajax.getTransport();
 211      this.setOptions(options);
 212  
 213      var onComplete = this.options.onComplete || Prototype.emptyFunction;
 214      this.options.onComplete = (function(transport, object) {
 215        this.updateContent();
 216        onComplete(transport, object);
 217      }).bind(this);
 218  
 219      this.request(url);
 220    },
 221  
 222    updateContent: function() {
 223      var receiver = this.responseIsSuccess() ?
 224        this.containers.success : this.containers.failure;
 225      var response = this.transport.responseText;
 226      
 227      if (!this.options.evalScripts)
 228        response = response.stripScripts();
 229  
 230      if (receiver) {
 231        if (this.options.insertion) {
 232          new this.options.insertion(receiver, response);
 233        } else {
 234          Element.update(receiver, response);
 235        }
 236      }
 237  
 238      if (this.responseIsSuccess()) {
 239        if (this.onComplete)
 240          setTimeout(this.onComplete.bind(this), 10);
 241      }
 242    }
 243  });
 244  
 245  Ajax.PeriodicalUpdater = Class.create();
 246  Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
 247    initialize: function(container, url, options) {
 248      this.setOptions(options);
 249      this.onComplete = this.options.onComplete;
 250  
 251      this.frequency = (this.options.frequency || 2);
 252      this.decay = (this.options.decay || 1);
 253      
 254      this.updater = {};
 255      this.container = container;
 256      this.url = url;
 257  
 258      this.start();
 259    },
 260  
 261    start: function() {
 262      this.options.onComplete = this.updateComplete.bind(this);
 263      this.onTimerEvent();
 264    },
 265  
 266    stop: function() {
 267      this.updater.onComplete = undefined;
 268      clearTimeout(this.timer);
 269      (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
 270    },
 271  
 272    updateComplete: function(request) {
 273      if (this.options.decay) {
 274        this.decay = (request.responseText == this.lastText ? 
 275          this.decay * this.options.decay : 1);
 276  
 277        this.lastText = request.responseText;
 278      }
 279      this.timer = setTimeout(this.onTimerEvent.bind(this), 
 280        this.decay * this.frequency * 1000);
 281    },
 282  
 283    onTimerEvent: function() {
 284      this.updater = new Ajax.Updater(this.container, this.url, this.options);
 285    }
 286  });
 287  
 288  
 289  // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
 290  //           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
 291  //           (c) 2005 Jon Tirsen (http://www.tirsen.com)
 292  // Contributors:
 293  //  Richard Livsey
 294  //  Rahul Bhargava
 295  //  Rob Wills
 296  // 
 297  // See scriptaculous.js for full license.
 298  
 299  // Autocompleter.Base handles all the autocompletion functionality 
 300  // that's independent of the data source for autocompletion. This
 301  // includes drawing the autocompletion menu, observing keyboard
 302  // and mouse events, and similar.
 303  //
 304  // Specific autocompleters need to provide, at the very least, 
 305  // a getUpdatedChoices function that will be invoked every time
 306  // the text inside the monitored textbox changes. This method 
 307  // should get the text for which to provide autocompletion by
 308  // invoking this.getToken(), NOT by directly accessing
 309  // this.element.value. This is to allow incremental tokenized
 310  // autocompletion. Specific auto-completion logic (AJAX, etc)
 311  // belongs in getUpdatedChoices.
 312  //
 313  // Tokenized incremental autocompletion is enabled automatically
 314  // when an autocompleter is instantiated with the 'tokens' option
 315  // in the options parameter, e.g.:
 316  // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
 317  // will incrementally autocomplete with a comma as the token.
 318  // Additionally, ',' in the above example can be replaced with
 319  // a token array, e.g. { tokens: [',', '\n'] } which
 320  // enables autocompletion on multiple tokens. This is most 
 321  // useful when one of the tokens is \n (a newline), as it 
 322  // allows smart autocompletion after linebreaks.
 323  
 324  if(typeof Effect == 'undefined')
 325    throw("controls.js requires including script.aculo.us' effects.js library");
 326  
 327  var Autocompleter = {}
 328  Autocompleter.Base = function() {};
 329  Autocompleter.Base.prototype = {
 330    baseInitialize: function(element, update, options) {
 331      this.element     = $(element); 
 332      this.update      = $(update);  
 333      this.hasFocus    = false; 
 334      this.changed     = false; 
 335      this.active      = false; 
 336      this.index       = 0;     
 337      this.entryCount  = 0;
 338  
 339      if (this.setOptions)
 340        this.setOptions(options);
 341      else
 342        this.options = options || {};
 343  
 344      this.options.paramName    = this.options.paramName || this.element.name;
 345      this.options.tokens       = this.options.tokens || [];
 346      this.options.frequency    = this.options.frequency || 0.4;
 347      this.options.minChars     = this.options.minChars || 1;
 348      this.options.onShow       = this.options.onShow || 
 349      function(element, update){ 
 350        if(!update.style.position || update.style.position=='absolute') {
 351          update.style.position = 'absolute';
 352          Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
 353        }
 354        Effect.Appear(update,{duration:0.15});
 355      };
 356      this.options.onHide = this.options.onHide || 
 357      function(element, update){ new Effect.Fade(update,{duration:0.15}) };
 358  
 359      if (typeof(this.options.tokens) == 'string') 
 360        this.options.tokens = new Array(this.options.tokens);
 361  
 362      this.observer = null;
 363      
 364      this.element.setAttribute('autocomplete','off');
 365  
 366      Element.hide(this.update);
 367  
 368      Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
 369      Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
 370    },
 371  
 372    show: function() {
 373      if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
 374      if(!this.iefix && 
 375        (navigator.appVersion.indexOf('MSIE')>0) &&
 376        (navigator.userAgent.indexOf('Opera')<0) &&
 377        (Element.getStyle(this.update, 'position')=='absolute')) {
 378        new Insertion.After(this.update, 
 379         '<iframe id="' + this.update.id + '_iefix" '+
 380         'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
 381         'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
 382        this.iefix = $(this.update.id+'_iefix');
 383      }
 384      if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
 385    },
 386    
 387    fixIEOverlapping: function() {
 388      Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
 389      this.iefix.style.zIndex = 1;
 390      this.update.style.zIndex = 2;
 391      Element.show(this.iefix);
 392    },
 393  
 394    hide: function() {
 395      this.stopIndicator();
 396      if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
 397      if(this.iefix) Element.hide(this.iefix);
 398    },
 399  
 400    startIndicator: function() {
 401      if(this.options.indicator) Element.show(this.options.indicator);
 402    },
 403  
 404    stopIndicator: function() {
 405      if(this.options.indicator) Element.hide(this.options.indicator);
 406    },
 407  
 408    onKeyPress: function(event) {
 409      if(this.active)
 410        switch(event.keyCode) {
 411         case Event.KEY_TAB:
 412         case Event.KEY_RETURN:
 413           this.selectEntry();
 414           Event.stop(event);
 415         case Event.KEY_ESC:
 416           this.hide();
 417           this.active = false;
 418           Event.stop(event);
 419           return;
 420         case Event.KEY_LEFT:
 421         case Event.KEY_RIGHT:
 422           return;
 423         case Event.KEY_UP:
 424           this.markPrevious();
 425           this.render();
 426           if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
 427           return;
 428         case Event.KEY_DOWN:
 429           this.markNext();
 430           this.render();
 431           if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
 432           return;
 433        }
 434       else 
 435         if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
 436           (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
 437  
 438      this.changed = true;
 439      this.hasFocus = true;
 440  
 441      if(this.observer) clearTimeout(this.observer);
 442        this.observer = 
 443          setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
 444    },
 445  
 446    activate: function() {
 447      this.changed = false;
 448      this.hasFocus = true;
 449      this.getUpdatedChoices();
 450    },
 451  
 452    onHover: function(event) {
 453      var element = Event.findElement(event, 'LI');
 454      if(this.index != element.autocompleteIndex) 
 455      {
 456          this.index = element.autocompleteIndex;
 457          this.render();
 458      }
 459      Event.stop(event);
 460    },
 461    
 462    onClick: function(event) {
 463      var element = Event.findElement(event, 'LI');
 464      this.index = element.autocompleteIndex;
 465      this.selectEntry();
 466      this.hide();
 467    },
 468    
 469    onBlur: function(event) {
 470      // needed to make click events working
 471      setTimeout(this.hide.bind(this), 250);
 472      this.hasFocus = false;
 473      this.active = false;     
 474    }, 
 475    
 476    render: function() {
 477      if(this.entryCount > 0) {
 478        for (var i = 0; i < this.entryCount; i++)
 479          this.index==i ? 
 480            Element.addClassName(this.getEntry(i),"selected") : 
 481            Element.removeClassName(this.getEntry(i),"selected");
 482          
 483        if(this.hasFocus) { 
 484          this.show();
 485          this.active = true;
 486        }
 487      } else {
 488        this.active = false;
 489        this.hide();
 490      }
 491    },
 492    
 493    markPrevious: function() {
 494      if(this.index > 0) this.index--
 495        else this.index = this.entryCount-1;
 496      this.getEntry(this.index).scrollIntoView(true);
 497    },
 498    
 499    markNext: function() {
 500      if(this.index < this.entryCount-1) this.index++
 501        else this.index = 0;
 502      this.getEntry(this.index).scrollIntoView(false);
 503    },
 504    
 505    getEntry: function(index) {
 506      return this.update.firstChild.childNodes[index];
 507    },
 508    
 509    getCurrentEntry: function() {
 510      return this.getEntry(this.index);
 511    },
 512    
 513    selectEntry: function() {
 514      this.active = false;
 515      this.updateElement(this.getCurrentEntry());
 516    },
 517  
 518    updateElement: function(selectedElement) {
 519      if (this.options.updateElement) {
 520        this.options.updateElement(selectedElement);
 521        return;
 522      }
 523      var value = '';
 524      if (this.options.select) {
 525        var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
 526        if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
 527      } else
 528        value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
 529      
 530      var lastTokenPos = this.findLastToken();
 531      if (lastTokenPos != -1) {
 532        var newValue = this.element.value.substr(0, lastTokenPos + 1);
 533        var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
 534        if (whitespace)
 535          newValue += whitespace[0];
 536        this.element.value = newValue + value;
 537      } else {
 538        this.element.value = value;
 539      }
 540      this.element.focus();
 541      
 542      if (this.options.afterUpdateElement)
 543        this.options.afterUpdateElement(this.element, selectedElement);
 544    },
 545  
 546    updateChoices: function(choices) {
 547      if(!this.changed && this.hasFocus) {
 548        this.update.innerHTML = choices;
 549        Element.cleanWhitespace(this.update);
 550        Element.cleanWhitespace(this.update.firstChild);
 551  
 552        if(this.update.firstChild && this.update.firstChild.childNodes) {
 553          this.entryCount = 
 554            this.update.firstChild.childNodes.length;
 555          for (var i = 0; i < this.entryCount; i++) {
 556            var entry = this.getEntry(i);
 557            entry.autocompleteIndex = i;
 558            this.addObservers(entry);
 559          }
 560        } else { 
 561          this.entryCount = 0;
 562        }
 563  
 564        this.stopIndicator();
 565  
 566        this.index = 0;
 567        this.render();
 568      }
 569    },
 570  
 571    addObservers: function(element) {
 572      Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
 573      Event.observe(element, "click", this.onClick.bindAsEventListener(this));
 574    },
 575  
 576    onObserverEvent: function() {
 577      this.changed = false;   
 578      if(this.getToken().length>=this.options.minChars) {
 579        this.startIndicator();
 580        this.getUpdatedChoices();
 581      } else {
 582        this.active = false;
 583        this.hide();
 584      }
 585    },
 586  
 587    getToken: function() {
 588      var tokenPos = this.findLastToken();
 589      if (tokenPos != -1)
 590        var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
 591      else
 592        var ret = this.element.value;
 593  
 594      return /\n/.test(ret) ? '' : ret;
 595    },
 596  
 597    findLastToken: function() {
 598      var lastTokenPos = -1;
 599  
 600      for (var i=0; i<this.options.tokens.length; i++) {
 601        var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
 602        if (thisTokenPos > lastTokenPos)
 603          lastTokenPos = thisTokenPos;
 604      }
 605      return lastTokenPos;
 606    }
 607  }
 608  
 609  Ajax.Autocompleter = Class.create();
 610  Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
 611    initialize: function(element, update, url, options) {
 612      this.baseInitialize(element, update, options);
 613      this.options.asynchronous  = true;
 614      this.options.onComplete    = this.onComplete.bind(this);
 615      this.options.defaultParams = this.options.parameters || null;
 616      this.url                   = url;
 617    },
 618  
 619    getUpdatedChoices: function() {
 620      entry = encodeURIComponent(this.options.paramName) + '=' + 
 621        encodeURIComponent(this.getToken());
 622  
 623      this.options.parameters = this.options.callback ?
 624        this.options.callback(this.element, entry) : entry;
 625  
 626      if(this.options.defaultParams) 
 627        this.options.parameters += '&' + this.options.defaultParams;
 628  
 629      new Ajax.Request(this.url, this.options);
 630    },
 631  
 632    onComplete: function(request) {
 633      this.updateChoices(request.responseText);
 634    }
 635  
 636  });
 637  
 638  // The local array autocompleter. Used when you'd prefer to
 639  // inject an array of autocompletion options into the page, rather
 640  // than sending out Ajax queries, which can be quite slow sometimes.
 641  //
 642  // The constructor takes four parameters. The first two are, as usual,
 643  // the id of the monitored textbox, and id of the autocompletion menu.
 644  // The third is the array you want to autocomplete from, and the fourth
 645  // is the options block.
 646  //
 647  // Extra local autocompletion options:
 648  // - choices - How many autocompletion choices to offer
 649  //
 650  // - partialSearch - If false, the autocompleter will match entered
 651  //                    text only at the beginning of strings in the 
 652  //                    autocomplete array. Defaults to true, which will
 653  //                    match text at the beginning of any *word* in the
 654  //                    strings in the autocomplete array. If you want to
 655  //                    search anywhere in the string, additionally set
 656  //                    the option fullSearch to true (default: off).
 657  //
 658  // - fullSsearch - Search anywhere in autocomplete array strings.
 659  //
 660  // - partialChars - How many characters to enter before triggering
 661  //                   a partial match (unlike minChars, which defines
 662  //                   how many characters are required to do any match
 663  //                   at all). Defaults to 2.
 664  //
 665  // - ignoreCase - Whether to ignore case when autocompleting.
 666  //                 Defaults to true.
 667  //
 668  // It's possible to pass in a custom function as the 'selector' 
 669  // option, if you prefer to write your own autocompletion logic.
 670  // In that case, the other options above will not apply unless
 671  // you support them.
 672  
 673  Autocompleter.Local = Class.create();
 674  Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
 675    initialize: function(element, update, array, options) {
 676      this.baseInitialize(element, update, options);
 677      this.options.array = array;
 678    },
 679  
 680    getUpdatedChoices: function() {
 681      this.updateChoices(this.options.selector(this));
 682    },
 683  
 684    setOptions: function(options) {
 685      this.options = Object.extend({
 686        choices: 10,
 687        partialSearch: true,
 688        partialChars: 2,
 689        ignoreCase: true,
 690        fullSearch: false,
 691        selector: function(instance) {
 692          var ret       = []; // Beginning matches
 693          var partial   = []; // Inside matches
 694          var entry     = instance.getToken();
 695          var count     = 0;
 696  
 697          for (var i = 0; i < instance.options.array.length &&  
 698            ret.length < instance.options.choices ; i++) { 
 699  
 700            var elem = instance.options.array[i];
 701            var foundPos = instance.options.ignoreCase ? 
 702              elem.toLowerCase().indexOf(entry.toLowerCase()) : 
 703              elem.indexOf(entry);
 704  
 705            while (foundPos != -1) {
 706              if (foundPos == 0 && elem.length != entry.length) { 
 707                ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
 708                  elem.substr(entry.length) + "</li>");
 709                break;
 710              } else if (entry.length >= instance.options.partialChars && 
 711                instance.options.partialSearch && foundPos != -1) {
 712                if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
 713                  partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
 714                    elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
 715                    foundPos + entry.length) + "</li>");
 716                  break;
 717                }
 718              }
 719  
 720              foundPos = instance.options.ignoreCase ? 
 721                elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
 722                elem.indexOf(entry, foundPos + 1);
 723  
 724            }
 725          }
 726          if (partial.length)
 727            ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
 728          return "<ul>" + ret.join('') + "</ul>";
 729        }
 730      }, options || {});
 731    }
 732  });
 733  
 734  // AJAX in-place editor
 735  //
 736  // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
 737  
 738  // Use this if you notice weird scrolling problems on some browsers,
 739  // the DOM might be a bit confused when this gets called so do this
 740  // waits 1 ms (with setTimeout) until it does the activation
 741  Field.scrollFreeActivate = function(field) {
 742    setTimeout(function() {
 743      Field.activate(field);
 744    }, 1);
 745  }
 746  
 747  Ajax.InPlaceEditor = Class.create();
 748  Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
 749  Ajax.InPlaceEditor.prototype = {
 750    initialize: function(element, url, options) {
 751      this.url = url;
 752      this.element = $(element);
 753  
 754      this.options = Object.extend({
 755        okButton: true,
 756        okText: "ok",
 757        cancelLink: true,
 758        cancelText: "cancel",
 759        savingText: "Saving...",
 760        clickToEditText: "Click to edit",
 761        okText: "ok",
 762        rows: 1,
 763        onComplete: function(transport, element) {
 764          new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
 765        },
 766        onFailure: function(transport) {
 767          alert("Error communicating with the server: " + transport.responseText.stripTags());
 768        },
 769        callback: function(form) {
 770          return Form.serialize(form);
 771        },
 772        handleLineBreaks: true,
 773        loadingText: 'Loading...',
 774        savingClassName: 'inplaceeditor-saving',
 775        loadingClassName: 'inplaceeditor-loading',
 776        formClassName: 'inplaceeditor-form',
 777        highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
 778        highlightendcolor: "#FFFFFF",
 779        externalControl: null,
 780        submitOnBlur: false,
 781        ajaxOptions: {},
 782        evalScripts: false
 783      }, options || {});
 784  
 785      if(!this.options.formId && this.element.id) {
 786        this.options.formId = this.element.id + "-inplaceeditor";
 787        if ($(this.options.formId)) {
 788          // there's already a form with that name, don't specify an id
 789          this.options.formId = null;
 790        }
 791      }
 792      
 793      if (this.options.externalControl) {
 794        this.options.externalControl = $(this.options.externalControl);
 795      }
 796      
 797      this.originalBackground = Element.getStyle(this.element, 'background-color');
 798      if (!this.originalBackground) {
 799        this.originalBackground = "transparent";
 800      }
 801      
 802      this.element.title = this.options.clickToEditText;
 803      
 804      this.onclickListener = this.enterEditMode.bindAsEventListener(this);
 805      this.mouseoverListener = this.enterHover.bindAsEventListener(this);
 806      this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
 807      Event.observe(this.element, 'click', this.onclickListener);
 808      Event.observe(this.element, 'mouseover', this.mouseoverListener);
 809      Event.observe(this.element, 'mouseout', this.mouseoutListener);
 810      if (this.options.externalControl) {
 811        Event.observe(this.options.externalControl, 'click', this.onclickListener);
 812        Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
 813        Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
 814      }
 815    },
 816    enterEditMode: function(evt) {
 817      if (this.saving) return;
 818      if (this.editing) return;
 819      this.editing = true;
 820      this.onEnterEditMode();
 821      if (this.options.externalControl) {
 822        Element.hide(this.options.externalControl);
 823      }
 824      Element.hide(this.element);
 825      this.createForm();
 826      this.element.parentNode.insertBefore(this.form, this.element);
 827      if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
 828      // stop the event to avoid a page refresh in Safari
 829      if (evt) {
 830        Event.stop(evt);
 831      }
 832      return false;
 833    },
 834    createForm: function() {
 835      this.form = document.createElement("form");
 836      this.form.id = this.options.formId;
 837      Element.addClassName(this.form, this.options.formClassName)
 838      this.form.onsubmit = this.onSubmit.bind(this);
 839  
 840      this.createEditField();
 841  
 842      if (this.options.textarea) {
 843        var br = document.createElement("br");
 844        this.form.appendChild(br);
 845      }
 846  
 847      if (this.options.okButton) {
 848        okButton = document.createElement("input");
 849        okButton.type = "submit";
 850        okButton.value = this.options.okText;
 851        okButton.className = 'editor_ok_button';
 852        this.form.appendChild(okButton);
 853      }
 854  
 855      if (this.options.cancelLink) {
 856        cancelLink = document.createElement("a");
 857        cancelLink.href = "#";
 858        cancelLink.appendChild(document.createTextNode(this.options.cancelText));
 859        cancelLink.onclick = this.onclickCancel.bind(this);
 860        cancelLink.className = 'editor_cancel';      
 861        this.form.appendChild(cancelLink);
 862      }
 863    },
 864    hasHTMLLineBreaks: function(string) {
 865      if (!this.options.handleLineBreaks) return false;
 866      return string.match(/<br/i) || string.match(/<p>/i);
 867    },
 868    convertHTMLLineBreaks: function(string) {
 869      return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
 870    },
 871    createEditField: function() {
 872      var text;
 873      if(this.options.loadTextURL) {
 874        text = this.options.loadingText;
 875      } else {
 876        text = this.getText();
 877      }
 878  
 879      var obj = this;
 880      
 881      if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
 882        this.options.textarea = false;
 883        var textField = document.createElement("input");
 884        textField.obj = this;
 885        textField.type = "text";
 886        textField.name = "value";
 887        textField.value = text;
 888        textField.style.backgroundColor = this.options.highlightcolor;
 889        textField.className = 'editor_field';
 890        var size = this.options.size || this.options.cols || 0;
 891        if (size != 0) textField.size = size;
 892        if (this.options.submitOnBlur)
 893          textField.onblur = this.onSubmit.bind(this);
 894        this.editField = textField;
 895      } else {
 896        this.options.textarea = true;
 897        var textArea = document.createElement("textarea");
 898        textArea.obj = this;
 899        textArea.name = "value";
 900        textArea.value = this.convertHTMLLineBreaks(text);
 901        textArea.rows = this.options.rows;
 902        textArea.cols = this.options.cols || 40;
 903        textArea.className = 'editor_field';      
 904        if (this.options.submitOnBlur)
 905          textArea.onblur = this.onSubmit.bind(this);
 906        this.editField = textArea;
 907      }
 908      
 909      if(this.options.loadTextURL) {
 910        this.loadExternalText();
 911      }
 912      this.form.appendChild(this.editField);
 913    },
 914    getText: function() {
 915      return this.element.innerHTML;
 916    },
 917    loadExternalText: function() {
 918      Element.addClassName(this.form, this.options.loadingClassName);
 919      this.editField.disabled = true;
 920      new Ajax.Request(
 921        this.options.loadTextURL,
 922        Object.extend({
 923          asynchronous: true,
 924          onComplete: this.onLoadedExternalText.bind(this)
 925        }, this.options.ajaxOptions)
 926      );
 927    },
 928    onLoadedExternalText: function(transport) {
 929      Element.removeClassName(this.form, this.options.loadingClassName);
 930      this.editField.disabled = false;
 931      this.editField.value = transport.responseText.stripTags();
 932      Field.scrollFreeActivate(this.editField);
 933    },
 934    onclickCancel: function() {
 935      this.onComplete();
 936      this.leaveEditMode();
 937      return false;
 938    },
 939    onFailure: function(transport) {
 940      this.options.onFailure(transport);
 941      if (this.oldInnerHTML) {
 942        this.element.innerHTML = this.oldInnerHTML;
 943        this.oldInnerHTML = null;
 944      }
 945      return false;
 946    },
 947    onSubmit: function() {
 948      // onLoading resets these so we need to save them away for the Ajax call
 949      var form = this.form;
 950      var value = this.editField.value;
 951      
 952      // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
 953      // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
 954      // to be displayed indefinitely
 955      this.onLoading();
 956      
 957      if (this.options.evalScripts) {
 958        new Ajax.Request(
 959          this.url, Object.extend({
 960            parameters: this.options.callback(form, value),
 961            onComplete: this.onComplete.bind(this),
 962            onFailure: this.onFailure.bind(this),
 963            asynchronous:true, 
 964            evalScripts:true
 965          }, this.options.ajaxOptions));
 966      } else  {
 967        new Ajax.Updater(
 968          { success: this.element,
 969            // don't update on failure (this could be an option)
 970            failure: null }, 
 971          this.url, Object.extend({
 972            parameters: this.options.callback(form, value),
 973            onComplete: this.onComplete.bind(this),
 974            onFailure: this.onFailure.bind(this)
 975          }, this.options.ajaxOptions));
 976      }
 977      // stop the event to avoid a page refresh in Safari
 978      if (arguments.length > 1) {
 979        Event.stop(arguments[0]);
 980      }
 981      return false;
 982    },
 983    onLoading: function() {
 984      this.saving = true;
 985      this.removeForm();
 986      this.leaveHover();
 987      this.showSaving();
 988    },
 989    showSaving: function() {
 990      this.oldInnerHTML = this.element.innerHTML;
 991      this.element.innerHTML = this.options.savingText;
 992      Element.addClassName(this.element, this.options.savingClassName);
 993      this.element.style.backgroundColor = this.originalBackground;
 994      Element.show(this.element);
 995    },
 996    removeForm: function() {
 997      if(this.form) {
 998        if (this.form.parentNode) Element.remove(this.form);
 999        this.form = null;
1000      }
1001    },
1002    enterHover: function() {
1003      if (this.saving) return;
1004      this.element.style.backgroundColor = this.options.highlightcolor;
1005      if (this.effect) {
1006        this.effect.cancel();
1007      }
1008      Element.addClassName(this.element, this.options.hoverClassName)
1009    },
1010    leaveHover: function() {
1011      if (this.options.backgroundColor) {
1012        this.element.style.backgroundColor = this.oldBackground;
1013      }
1014      Element.removeClassName(this.element, this.options.hoverClassName)
1015      if (this.saving) return;
1016      this.effect = new Effect.Highlight(this.element, {
1017        startcolor: this.options.highlightcolor,
1018        endcolor: this.options.highlightendcolor,
1019        restorecolor: this.originalBackground
1020      });
1021    },
1022    leaveEditMode: function() {
1023      Element.removeClassName(this.element, this.options.savingClassName);
1024      this.removeForm();
1025      this.leaveHover();
1026      this.element.style.backgroundColor = this.originalBackground;
1027      Element.show(this.element);
1028      if (this.options.externalControl) {
1029        Element.show(this.options.externalControl);
1030      }
1031      this.editing = false;
1032      this.saving = false;
1033      this.oldInnerHTML = null;
1034      this.onLeaveEditMode();
1035    },
1036    onComplete: function(transport) {
1037      this.leaveEditMode();
1038      this.options.onComplete.bind(this)(transport, this.element);
1039    },
1040    onEnterEditMode: function() {},
1041    onLeaveEditMode: function() {},
1042    dispose: function() {
1043      if (this.oldInnerHTML) {
1044        this.element.innerHTML = this.oldInnerHTML;
1045      }
1046      this.leaveEditMode();
1047      Event.stopObserving(this.element, 'click', this.onclickListener);
1048      Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
1049      Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
1050      if (this.options.externalControl) {
1051        Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
1052        Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
1053        Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
1054      }
1055    }
1056  };
1057  
1058  Ajax.InPlaceCollectionEditor = Class.create();
1059  Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
1060  Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
1061    createEditField: function() {
1062      if (!this.cached_selectTag) {
1063        var selectTag = document.createElement("select");
1064        var collection = this.options.collection || [];
1065        var optionTag;
1066        collection.each(function(e,i) {
1067          optionTag = document.createElement("option");
1068          optionTag.value = (e instanceof Array) ? e[0] : e;
1069          if(this.options.value==optionTag.value) optionTag.selected = true;
1070          optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
1071          selectTag.appendChild(optionTag);
1072        }.bind(this));
1073        this.cached_selectTag = selectTag;
1074      }
1075  
1076      this.editField = this.cached_selectTag;
1077      if(this.options.loadTextURL) this.loadExternalText();
1078      this.form.appendChild(this.editField);
1079      this.options.callback = function(form, value) {
1080        return "value=" + encodeURIComponent(value);
1081      }
1082    }
1083  });
1084  
1085  // Delayed observer, like Form.Element.Observer, 
1086  // but waits for delay after last key input
1087  // Ideal for live-search fields
1088  
1089  Form.Element.DelayedObserver = Class.create();
1090  Form.Element.DelayedObserver.prototype = {
1091    initialize: function(element, delay, callback) {
1092      this.delay     = delay || 0.5;
1093      this.element   = $(element);
1094      this.callback  = callback;
1095      this.timer     = null;
1096      this.lastValue = $F(this.element); 
1097      Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
1098    },
1099    delayedListener: function(event) {
1100      if(this.lastValue == $F(this.element)) return;
1101      if(this.timer) clearTimeout(this.timer);
1102      this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
1103      this.lastValue = $F(this.element);
1104    },
1105    onTimerEvent: function() {
1106      this.timer = null;
1107      this.callback(this.element, $F(this.element));
1108    }
1109  };
1110  
1111  
1112  // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
1113  //           (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
1114  // 
1115  // See scriptaculous.js for full license.
1116  
1117  /*--------------------------------------------------------------------------*/
1118  
1119  if(typeof Effect == 'undefined')
1120    throw("dragdrop.js requires including script.aculo.us' effects.js library");
1121  
1122  var Droppables = {
1123    drops: [],
1124  
1125    remove: function(element) {
1126      this.drops = this.drops.reject(function(d) { return d.element==$(element) });
1127    },
1128  
1129    add: function(element) {
1130      element = $(element);
1131      var options = Object.extend({
1132        greedy:     true,
1133        hoverclass: null,
1134        tree:       false
1135      }, arguments[1] || {});
1136  
1137      // cache containers
1138      if(options.containment) {
1139        options._containers = [];
1140        var containment = options.containment;
1141        if((typeof containment == 'object') && 
1142          (containment.constructor == Array)) {
1143          containment.each( function(c) { options._containers.push($(c)) });
1144        } else {
1145          options._containers.push($(containment));
1146        }
1147      }
1148      
1149      if(options.accept) options.accept = [options.accept].flatten();
1150  
1151      Element.makePositioned(element); // fix IE
1152      options.element = element;
1153  
1154      this.drops.push(options);
1155    },
1156    
1157    findDeepestChild: function(drops) {
1158      deepest = drops[0];
1159        
1160      for (i = 1; i < drops.length; ++i)
1161        if (Element.isParent(drops[i].element, deepest.element))
1162          deepest = drops[i];
1163      
1164      return deepest;
1165    },
1166  
1167    isContained: function(element, drop) {
1168      var containmentNode;
1169      if(drop.tree) {
1170        containmentNode = element.treeNode; 
1171      } else {
1172        containmentNode = element.parentNode;
1173      }
1174      return drop._containers.detect(function(c) { return containmentNode == c });
1175    },
1176    
1177    isAffected: function(point, element, drop) {
1178      return (
1179        (drop.element!=element) &&
1180        ((!drop._containers) ||
1181          this.isContained(element, drop)) &&
1182        ((!drop.accept) ||
1183          (Element.classNames(element).detect( 
1184            function(v) { return drop.accept.include(v) } ) )) &&
1185        Position.within(drop.element, point[0], point[1]) );
1186    },
1187  
1188    deactivate: function(drop) {
1189      if(drop.hoverclass)
1190        Element.removeClassName(drop.element, drop.hoverclass);
1191      this.last_active = null;
1192    },
1193  
1194    activate: function(drop) {
1195      if(drop.hoverclass)
1196        Element.addClassName(drop.element, drop.hoverclass);
1197      this.last_active = drop;
1198    },
1199  
1200    show: function(point, element) {
1201      if(!this.drops.length) return;
1202      var affected = [];
1203      
1204      if(this.last_active) this.deactivate(this.last_active);
1205      this.drops.each( function(drop) {
1206        if(Droppables.isAffected(point, element, drop))
1207          affected.push(drop);
1208      });
1209          
1210      if(affected.length>0) {
1211        drop = Droppables.findDeepestChild(affected);
1212        Position.within(drop.element, point[0], point[1]);
1213        if(drop.onHover)
1214          drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
1215        
1216        Droppables.activate(drop);
1217      }
1218    },
1219  
1220    fire: function(event, element) {
1221      if(!this.last_active) return;
1222      Position.prepare();
1223  
1224      if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
1225        if (this.last_active.onDrop) 
1226          this.last_active.onDrop(element, this.last_active.element, event);
1227    },
1228  
1229    reset: function() {
1230      if(this.last_active)
1231        this.deactivate(this.last_active);
1232    }
1233  }
1234  
1235  var Draggables = {
1236    drags: [],
1237    observers: [],
1238    
1239    register: function(draggable) {
1240      if(this.drags.length == 0) {
1241        this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
1242        this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
1243        this.eventKeypress  = this.keyPress.bindAsEventListener(this);
1244        
1245        Event.observe(document, "mouseup", this.eventMouseUp);
1246        Event.observe(document, "mousemove", this.eventMouseMove);
1247        Event.observe(document, "keypress", this.eventKeypress);
1248      }
1249      this.drags.push(draggable);
1250    },
1251    
1252    unregister: function(draggable) {
1253      this.drags = this.drags.reject(function(d) { return d==draggable });
1254      if(this.drags.length == 0) {
1255        Event.stopObserving(document, "mouseup", this.eventMouseUp);
1256        Event.stopObserving(document, "mousemove", this.eventMouseMove);
1257        Event.stopObserving(document, "keypress", this.eventKeypress);
1258      }
1259    },
1260    
1261    activate: function(draggable) {
1262      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
1263      this.activeDraggable = draggable;
1264    },
1265    
1266    deactivate: function() {
1267      this.activeDraggable = null;
1268    },
1269    
1270    updateDrag: function(event) {
1271      if(!this.activeDraggable) return;
1272      var pointer = [Event.pointerX(event), Event.pointerY(event)];
1273      // Mozilla-based browsers fire successive mousemove events with
1274      // the same coordinates, prevent needless redrawing (moz bug?)
1275      if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
1276      this._lastPointer = pointer;
1277      this.activeDraggable.updateDrag(event, pointer);
1278    },
1279    
1280    endDrag: function(event) {
1281      if(!this.activeDraggable) return;
1282      this._lastPointer = null;
1283      this.activeDraggable.endDrag(event);
1284      this.activeDraggable = null;
1285    },
1286    
1287    keyPress: function(event) {
1288      if(this.activeDraggable)
1289        this.activeDraggable.keyPress(event);
1290    },
1291    
1292    addObserver: function(observer) {
1293      this.observers.push(observer);
1294      this._cacheObserverCallbacks();
1295    },
1296    
1297    removeObserver: function(element) {  // element instead of observer fixes mem leaks
1298      this.observers = this.observers.reject( function(o) { return o.element==element });
1299      this._cacheObserverCallbacks();
1300    },
1301    
1302    notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
1303      if(this[eventName+'Count'] > 0)
1304        this.observers.each( function(o) {
1305          if(o[eventName]) o[eventName](eventName, draggable, event);
1306        });
1307    },
1308    
1309    _cacheObserverCallbacks: function() {
1310      ['onStart','onEnd','onDrag'].each( function(eventName) {
1311        Draggables[eventName+'Count'] = Draggables.observers.select(
1312          function(o) { return o[eventName]; }
1313        ).length;
1314      });
1315    }
1316  }
1317  
1318  /*--------------------------------------------------------------------------*/
1319  
1320  var Draggable = Class.create();
1321  Draggable._revertCache = {};
1322  Draggable._dragging    = {};
1323  
1324  Draggable.prototype = {
1325    initialize: function(element) {
1326      var options = Object.extend({
1327        handle: false,
1328        starteffect: function(element) {
1329          element._opacity = Element.getOpacity(element);
1330          Draggable._dragging[element] = true;
1331          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
1332        },
1333        reverteffect: function(element, top_offset, left_offset) {
1334          var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
1335          Draggable._revertCache[element] =
1336            new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
1337              queue: {scope:'_draggable', position:'end'}
1338            });
1339        },
1340        endeffect: function(element) {
1341          var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0;
1342          new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 
1343            queue: {scope:'_draggable', position:'end'},
1344            afterFinish: function(){ Draggable._dragging[element] = false }
1345          }); 
1346        },
1347        zindex: 1000,
1348        revert: false,
1349        scroll: false,
1350        scrollSensitivity: 20,
1351        scrollSpeed: 15,
1352        snap: false   // false, or xy or [x,y] or function(x,y){ return [x,y] }
1353      }, arguments[1] || {});
1354  
1355      this.element = $(element);
1356      
1357      if(options.handle && (typeof options.handle == 'string')) {
1358        var h = Element.childrenWithClassName(this.element, options.handle, true);
1359        if(h.length>0) this.handle = h[0];
1360      }
1361      if(!this.handle) this.handle = $(options.handle);
1362      if(!this.handle) this.handle = this.element;
1363      
1364      if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML)
1365        options.scroll = $(options.scroll);
1366  
1367      Element.makePositioned(this.element); // fix IE    
1368  
1369      this.delta    = this.currentDelta();
1370      this.options  = options;
1371      this.dragging = false;   
1372  
1373      this.eventMouseDown = this.initDrag.bindAsEventListener(this);
1374      Event.observe(this.handle, "mousedown", this.eventMouseDown);
1375      
1376      Draggables.register(this);
1377    },
1378    
1379    destroy: function() {
1380      Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
1381      Draggables.unregister(this);
1382    },
1383    
1384    currentDelta: function() {
1385      return([
1386        parseInt(Element.getStyle(this.element,'left') || '0'),
1387        parseInt(Element.getStyle(this.element,'top') || '0')]);
1388    },
1389    
1390    initDrag: function(event) {
1391      if(typeof Draggable._dragging[this.element] != undefined &&
1392        Draggable._dragging[this.element]) return;
1393      if(Event.isLeftClick(event)) {    
1394        // abort on form elements, fixes a Firefox issue
1395        var src = Event.element(event);
1396        if(src.tagName && (
1397          src.tagName=='INPUT' ||
1398          src.tagName=='SELECT' ||
1399          src.tagName=='OPTION' ||
1400          src.tagName=='BUTTON' ||
1401          src.tagName=='TEXTAREA')) return;
1402          
1403        if(Draggable._revertCache[this.element]) {
1404          Draggable._revertCache[this.element].cancel();
1405          Draggable._revertCache[this.element] = null;
1406        }
1407        
1408        var pointer = [Event.pointerX(event), Event.pointerY(event)];
1409        var pos     = Position.cumulativeOffset(this.element);
1410        this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
1411        
1412        Draggables.activate(this);
1413        Event.stop(event);
1414      }
1415    },
1416    
1417    startDrag: function(event) {
1418      this.dragging = true;
1419      
1420      if(this.options.zindex) {
1421        this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
1422        this.element.style.zIndex = this.options.zindex;
1423      }
1424      
1425      if(this.options.ghosting) {
1426        this._clone = this.element.cloneNode(true);
1427        Position.absolutize(this.element);
1428        this.element.parentNode.insertBefore(this._clone, this.element);
1429      }
1430      
1431      if(this.options.scroll) {
1432        if (this.options.scroll == window) {
1433          var where = this._getWindowScroll(this.options.scroll);
1434          this.originalScrollLeft = where.left;
1435          this.originalScrollTop = where.top;
1436        } else {
1437          this.originalScrollLeft = this.options.scroll.scrollLeft;
1438          this.originalScrollTop = this.options.scroll.scrollTop;
1439        }
1440      }
1441      
1442      Draggables.notify('onStart', this, event);
1443      if(this.options.starteffect) this.options.starteffect(this.element);
1444    },
1445    
1446    updateDrag: function(event, pointer) {
1447      if(!this.dragging) this.startDrag(event);
1448      Position.prepare();
1449      Droppables.show(pointer, this.element);
1450      Draggables.notify('onDrag', this, event);
1451      this.draw(pointer);
1452      if(this.options.change) this.options.change(this);
1453      
1454      if(this.options.scroll) {
1455        this.stopScrolling();
1456        
1457        var p;
1458        if (this.options.scroll == window) {
1459          with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
1460        } else {
1461          p = Position.page(this.options.scroll);
1462          p[0] += this.options.scroll.scrollLeft;
1463          p[1] += this.options.scroll.scrollTop;
1464          p.push(p[0]+this.options.scroll.offsetWidth);
1465          p.push(p[1]+this.options.scroll.offsetHeight);
1466        }
1467        var speed = [0,0];
1468        if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
1469        if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
1470        if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
1471        if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
1472        this.startScrolling(speed);
1473      }
1474      
1475      // fix AppleWebKit rendering
1476      if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
1477      
1478      Event.stop(event);
1479    },
1480    
1481    finishDrag: function(event, success) {
1482      this.dragging = false;
1483  
1484      if(this.options.ghosting) {
1485        Position.relativize(this.element);
1486        Element.remove(this._clone);
1487        this._clone = null;
1488      }
1489  
1490      if(success) Droppables.fire(event, this.element);
1491      Draggables.notify('onEnd', this, event);
1492  
1493      var revert = this.options.revert;
1494      if(revert && typeof revert == 'function') revert = revert(this.element);
1495      
1496      var d = this.currentDelta();
1497      if(revert && this.options.reverteffect) {
1498        this.options.reverteffect(this.element, 
1499          d[1]-this.delta[1], d[0]-this.delta[0]);
1500      } else {
1501        this.delta = d;
1502      }
1503  
1504      if(this.options.zindex)
1505        this.element.style.zIndex = this.originalZ;
1506  
1507      if(this.options.endeffect) 
1508        this.options.endeffect(this.element);
1509  
1510      Draggables.deactivate(this);
1511      Droppables.reset();
1512    },
1513    
1514    keyPress: function(event) {
1515      if(event.keyCode!=Event.KEY_ESC) return;
1516      this.finishDrag(event, false);
1517      Event.stop(event);
1518    },
1519    
1520    endDrag: function(event) {
1521      if(!this.dragging) return;
1522      this.stopScrolling();
1523      this.finishDrag(event, true);
1524      Event.stop(event);
1525    },
1526    
1527    draw: function(point) {
1528      var pos = Position.cumulativeOffset(this.element);
1529      var d = this.currentDelta();
1530      pos[0] -= d[0]; pos[1] -= d[1];
1531      
1532      if(this.options.scroll && (this.options.scroll != window)) {
1533        pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
1534        pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
1535      }
1536      
1537      var p = [0,1].map(function(i){ 
1538        return (point[i]-pos[i]-this.offset[i]) 
1539      }.bind(this));
1540      
1541      if(this.options.snap) {
1542        if(typeof this.options.snap == 'function') {
1543          p = this.options.snap(p[0],p[1],this);
1544        } else {
1545        if(this.options.snap instanceof Array) {
1546          p = p.map( function(v, i) {
1547            return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
1548        } else {
1549          p = p.map( function(v) {
1550            return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
1551        }
1552      }}
1553      
1554      var style = this.element.style;
1555      if((!this.options.constraint) || (this.options.constraint=='horizontal'))
1556        style.left = p[0] + "px";
1557      if((!this.options.constraint) || (this.options.constraint=='vertical'))
1558        style.top  = p[1] + "px";
1559      if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
1560    },
1561    
1562    stopScrolling: function() {
1563      if(this.scrollInterval) {
1564        clearInterval(this.scrollInterval);
1565        this.scrollInterval = null;
1566        Draggables._lastScrollPointer = null;
1567      }
1568    },
1569    
1570    startScrolling: function(speed) {
1571      if(!(speed[0] || speed[1])) return;
1572      this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
1573      this.lastScrolled = new Date();
1574      this.scrollInterval = setInterval(this.scroll.bind(this), 10);
1575    },
1576    
1577    scroll: function() {
1578      var current = new Date();
1579      var delta = current - this.lastScrolled;
1580      this.lastScrolled = current;
1581      if(this.options.scroll == window) {
1582        with (this._getWindowScroll(this.options.scroll)) {
1583          if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
1584            var d = delta / 1000;
1585            this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
1586          }
1587        }
1588      } else {
1589        this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
1590        this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
1591      }
1592      
1593      Position.prepare();
1594      Droppables.show(Draggables._lastPointer, this.element);
1595      Draggables.notify('onDrag', this);
1596      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
1597      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
1598      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
1599      if (Draggables._lastScrollPointer[0] < 0)
1600        Draggables._lastScrollPointer[0] = 0;
1601      if (Draggables._lastScrollPointer[1] < 0)
1602        Draggables._lastScrollPointer[1] = 0;
1603      this.draw(Draggables._lastScrollPointer);
1604      
1605      if(this.options.change) this.options.change(this);
1606    },
1607    
1608    _getWindowScroll: function(w) {
1609      var T, L, W, H;
1610      with (w.document) {
1611        if (w.document.documentElement && documentElement.scrollTop) {
1612          T = documentElement.scrollTop;
1613          L = documentElement.scrollLeft;
1614        } else if (w.document.body) {
1615          T = body.scrollTop;
1616          L = body.scrollLeft;
1617        }
1618        if (w.innerWidth) {
1619          W = w.innerWidth;
1620          H = w.innerHeight;
1621        } else if (w.document.documentElement && documentElement.clientWidth) {
1622          W = documentElement.clientWidth;
1623          H = documentElement.clientHeight;
1624        } else {
1625          W = body.offsetWidth;
1626          H = body.offsetHeight
1627        }
1628      }
1629      return { top: T, left: L, width: W, height: H };
1630    }
1631  }
1632  
1633  /*--------------------------------------------------------------------------*/
1634  
1635  var SortableObserver = Class.create();
1636  SortableObserver.prototype = {
1637    initialize: function(element, observer) {
1638      this.element   = $(element);
1639      this.observer  = observer;
1640      this.lastValue = Sortable.serialize(this.element);
1641    },
1642    
1643    onStart: function() {
1644      this.lastValue = Sortable.serialize(this.element);
1645    },
1646    
1647    onEnd: function() {
1648      Sortable.unmark();
1649      if(this.lastValue != Sortable.serialize(this.element))
1650        this.observer(this.element)
1651    }
1652  }
1653  
1654  var Sortable = {
1655    sortables: {},
1656    
1657    _findRootElement: function(element) {
1658      while (element.tagName != "BODY") {  
1659        if(element.id && Sortable.sortables[element.id]) return element;
1660        element = element.parentNode;
1661      }
1662    },
1663  
1664    options: function(element) {
1665      element = Sortable._findRootElement($(element));
1666      if(!element) return;
1667      return Sortable.sortables[element.id];
1668    },
1669    
1670    destroy: function(element){
1671      var s = Sortable.options(element);
1672      
1673      if(s) {
1674        Draggables.removeObserver(s.element);
1675        s.droppables.each(function(d){ Droppables.remove(d) });
1676        s.draggables.invoke('destroy');
1677        
1678        delete Sortable.sortables[s.element.id];
1679      }
1680    },
1681  
1682    create: function(element) {
1683      element = $(element);
1684      var options = Object.extend({ 
1685        element:     element,
1686        tag:         'li',       // assumes li children, override with tag: 'tagname'
1687        dropOnEmpty: false,
1688        tree:        false,
1689        treeTag:     'ul',
1690        overlap:     'vertical', // one of 'vertical', 'horizontal'
1691        constraint:  'vertical', // one of 'vertical', 'horizontal', false
1692        containment: element,    // also takes array of elements (or id's); or false
1693        handle:      false,      // or a CSS class
1694        only:        false,
1695        hoverclass:  null,
1696        ghosting:    false,
1697        scroll:      false,
1698        scrollSensitivity: 20,
1699        scrollSpeed: 15,
1700        format:      /^[^_]*_(.*)$/,
1701        onChange:    Prototype.emptyFunction,
1702        onUpdate:    Prototype.emptyFunction
1703      }, arguments[1] || {});
1704  
1705      // clear any old sortable with same element
1706      this.destroy(element);
1707  
1708      // build options for the draggables
1709      var options_for_draggable = {
1710        revert:      true,
1711        scroll:      options.scroll,
1712        scrollSpeed: options.scrollSpeed,
1713        scrollSensitivity: options.scrollSensitivity,
1714        ghosting:    options.ghosting,
1715        constraint:  options.constraint,
1716        handle:      options.handle };
1717  
1718      if(options.starteffect)
1719        options_for_draggable.starteffect = options.starteffect;
1720  
1721      if(options.reverteffect)
1722        options_for_draggable.reverteffect = options.reverteffect;
1723      else
1724        if(options.ghosting) options_for_draggable.reverteffect = function(element) {
1725          element.style.top  = 0;
1726          element.style.left = 0;
1727        };
1728  
1729      if(options.endeffect)
1730        options_for_draggable.endeffect = options.endeffect;
1731  
1732      if(options.zindex)
1733        options_for_draggable.zindex = options.zindex;
1734  
1735      // build options for the droppables  
1736      var options_for_droppable = {
1737        overlap:     options.overlap,
1738        containment: options.containment,
1739        tree:        options.tree,
1740        hoverclass:  options.hoverclass,
1741        onHover:     Sortable.onHover
1742        //greedy:      !options.dropOnEmpty
1743      }
1744      
1745      var options_for_tree = {
1746        onHover:      Sortable.onEmptyHover,
1747        overlap:      options.overlap,
1748        containment:  options.containment,
1749        hoverclass:   options.hoverclass
1750      }
1751  
1752      // fix for gecko engine
1753      Element.cleanWhitespace(element); 
1754  
1755      options.draggables = [];
1756      options.droppables = [];
1757  
1758      // drop on empty handling
1759      if(options.dropOnEmpty || options.tree) {
1760        Droppables.add(element, options_for_tree);
1761        options.droppables.push(element);
1762      }
1763  
1764      (this.findElements(element, options) || []).each( function(e) {
1765        // handles are per-draggable
1766        var handle = options.handle ? 
1767          Element.childrenWithClassName(e, options.handle)[0] : e;    
1768        options.draggables.push(
1769          new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
1770        Droppables.add(e, options_for_droppable);
1771        if(options.tree) e.treeNode = element;
1772        options.droppables.push(e);      
1773      });
1774      
1775      if(options.tree) {
1776        (Sortable.findTreeElements(element, options) || []).each( function(e) {
1777          Droppables.add(e, options_for_tree);
1778          e.treeNode = element;
1779          options.droppables.push(e);
1780        });
1781      }
1782  
1783      // keep reference
1784      this.sortables[element.id] = options;
1785  
1786      // for onupdate
1787      Draggables.addObserver(new SortableObserver(element, options.onUpdate));
1788  
1789    },
1790  
1791    // return all suitable-for-sortable elements in a guaranteed order
1792    findElements: function(element, options) {
1793      return Element.findChildren(
1794        element, options.only, options.tree ? true : false, options.tag);
1795    },
1796    
1797    findTreeElements: function(element, options) {
1798      return Element.findChildren(
1799        element, options.only, options.tree ? true : false, options.treeTag);
1800    },
1801  
1802    onHover: function(element, dropon, overlap) {
1803      if(Element.isParent(dropon, element)) return;
1804  
1805      if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
1806        return;
1807      } else if(overlap>0.5) {
1808        Sortable.mark(dropon, 'before');
1809        if(dropon.previousSibling != element) {
1810          var oldParentNode = element.parentNode;
1811          element.style.visibility = "hidden"; // fix gecko rendering
1812          dropon.parentNode.insertBefore(element, dropon);
1813          if(dropon.parentNode!=oldParentNode) 
1814            Sortable.options(oldParentNode).onChange(element);
1815          Sortable.options(dropon.parentNode).onChange(element);
1816        }
1817      } else {
1818        Sortable.mark(dropon, 'after');
1819        var nextElement = dropon.nextSibling || null;
1820        if(nextElement != element) {
1821          var oldParentNode = element.parentNode;
1822          element.style.visibility = "hidden"; // fix gecko rendering
1823          dropon.parentNode.insertBefore(element, nextElement);
1824          if(dropon.parentNode!=oldParentNode) 
1825            Sortable.options(oldParentNode).onChange(element);
1826          Sortable.options(dropon.parentNode).onChange(element);
1827        }
1828      }
1829    },
1830    
1831    onEmptyHover: function(element, dropon, overlap) {
1832      var oldParentNode = element.parentNode;
1833      var droponOptions = Sortable.options(dropon);
1834          
1835      if(!Element.isParent(dropon, element)) {
1836        var index;
1837        
1838        var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
1839        var child = null;
1840              
1841        if(children) {
1842          var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
1843          
1844          for (index = 0; index < children.length; index += 1) {
1845            if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
1846              offset -= Element.offsetSize (children[index], droponOptions.overlap);
1847            } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
1848              child = index + 1 < children.length ? children[index + 1] : null;
1849              break;
1850            } else {
1851              child = children[index];
1852              break;
1853            }
1854          }
1855        }
1856        
1857        dropon.insertBefore(element, child);
1858        
1859        Sortable.options(oldParentNode).onChange(element);
1860        droponOptions.onChange(element);
1861      }
1862    },
1863  
1864    unmark: function() {
1865      if(Sortable._marker) Element.hide(Sortable._marker);
1866    },
1867  
1868    mark: function(dropon, position) {
1869      // mark on ghosting only
1870      var sortable = Sortable.options(dropon.parentNode);
1871      if(sortable && !sortable.ghosting) return; 
1872  
1873      if(!Sortable._marker) {
1874        Sortable._marker = $('dropmarker') || document.createElement('DIV');
1875        Element.hide(Sortable._marker);
1876        Element.addClassName(Sortable._marker, 'dropmarker');
1877        Sortable._marker.style.position = 'absolute';
1878        document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
1879      }    
1880      var offsets = Position.cumulativeOffset(dropon);
1881      Sortable._marker.style.left = offsets[0] + 'px';
1882      Sortable._marker.style.top = offsets[1] + 'px';
1883      
1884      if(position=='after')
1885        if(sortable.overlap == 'horizontal') 
1886          Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
1887        else
1888          Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
1889      
1890      Element.show(Sortable._marker);
1891    },
1892    
1893    _tree: function(element, options, parent) {
1894      var children = Sortable.findElements(element, options) || [];
1895    
1896      for (var i = 0; i < children.length; ++i) {
1897        var match = children[i].id.match(options.format);
1898  
1899        if (!match) continue;
1900        
1901        var child = {
1902          id: encodeURIComponent(match ? match[1] : null),
1903          element: element,
1904          parent: parent,
1905          children: new Array,
1906          position: parent.children.length,
1907          container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase())
1908        }
1909        
1910        /* Get the element containing the children and recurse over it */
1911        if (child.container)
1912          this._tree(child.container, options, child)
1913        
1914        parent.children.push (child);
1915      }
1916  
1917      return parent; 
1918    },
1919  
1920    /* Finds the first element of the given tag type within a parent element.
1921      Used for finding the first LI[ST] within a L[IST]I[TEM].*/
1922    _findChildrenElement: function (element, containerTag) {
1923      if (element && element.hasChildNodes)
1924        for (var i = 0; i < element.childNodes.length; ++i)
1925          if (element.childNodes[i].tagName == containerTag)
1926            return element.childNodes[i];
1927    
1928      return null;
1929    },
1930  
1931    tree: function(element) {
1932      element = $(element);
1933      var sortableOptions = this.options(element);
1934      var options = Object.extend({
1935        tag: sortableOptions.tag,
1936        treeTag: sortableOptions.treeTag,
1937        only: sortableOptions.only,
1938        name: element.id,
1939        format: sortableOptions.format
1940      }, arguments[1] || {});
1941      
1942      var root = {
1943        id: null,
1944        parent: null,
1945        children: new Array,
1946        container: element,
1947        position: 0
1948      }
1949      
1950      return Sortable._tree (element, options, root);
1951    },
1952  
1953    /* Construct a [i] index for a particular node */
1954    _constructIndex: function(node) {
1955      var index = '';
1956      do {
1957        if (node.id) index = '[' + node.position + ']' + index;
1958      } while ((node = node.parent) != null);
1959      return index;
1960    },
1961  
1962    sequence: function(element) {
1963      element = $(element);
1964      var options = Object.extend(this.options(element), arguments[1] || {});
1965      
1966      return $(this.findElements(element, options) || []).map( function(item) {
1967        return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
1968      });
1969    },
1970  
1971    setSequence: function(element, new_sequence) {
1972      element = $(element);
1973      var options = Object.extend(this.options(element), arguments[2] || {});
1974      
1975      var nodeMap = {};
1976      this.findElements(element, options).each( function(n) {
1977          if (n.id.match(options.format))
1978              nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
1979          n.parentNode.removeChild(n);
1980      });
1981     
1982      new_sequence.each(function(ident) {
1983        var n = nodeMap[ident];
1984        if (n) {
1985          n[1].appendChild(n[0]);
1986          delete nodeMap[ident];
1987        }
1988      });
1989    },
1990    
1991    serialize: function(element) {
1992      element = $(element);
1993      var options = Object.extend(Sortable.options(element), arguments[1] || {});
1994      var name = encodeURIComponent(
1995        (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
1996      
1997      if (options.tree) {
1998        return Sortable.tree(element, arguments[1]).children.map( function (item) {
1999          return [name + Sortable._constructIndex(item) + "[id]=" + 
2000                  encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
2001        }).flatten().join('&');
2002      } else {
2003        return Sortable.sequence(element, arguments[1]).map( function(item) {
2004          return name + "[]=" + encodeURIComponent(item);
2005        }).join('&');
2006      }
2007    }
2008  }
2009  
2010  /* Returns true if child is contained within element */
2011  Element.isParent = function(child, element) {
2012    if (!child.parentNode || child == element) return false;
2013  
2014    if (child.parentNode == element) return true;
2015  
2016    return Element.isParent(child.parentNode, element);
2017  }
2018  
2019  Element.findChildren = function(element, only, recursive, tagName) {    
2020    if(!element.hasChildNodes()) return null;
2021    tagName = tagName.toUpperCase();
2022    if(only) only = [only].flatten();
2023    var elements = [];
2024    $A(element.childNodes).each( function(e) {
2025      if(e.tagName && e.tagName.toUpperCase()==tagName &&
2026        (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
2027          elements.push(e);
2028      if(recursive) {
2029        var grandchildren = Element.findChildren(e, only, recursive, tagName);
2030        if(grandchildren) elements.push(grandchildren);
2031      }
2032    });
2033  
2034    return (elements.length>0 ? elements.flatten() : []);
2035  }
2036  
2037  Element.offsetSize = function (element, type) {
2038    if (type == 'vertical' || type == 'height')
2039      return element.offsetHeight;
2040    else
2041      return element.offsetWidth;
2042  }
2043  
2044  // Copyright (c) 2005 Marty Haught, Thomas Fuchs 
2045  //
2046  // See http://script.aculo.us for more info
2047  // 
2048  // Permission is hereby granted, free of charge, to any person obtaining
2049  // a copy of this software and associated documentation files (the
2050  // "Software"), to deal in the Software without restriction, including
2051  // without limitation the rights to use, copy, modify, merge, publish,
2052  // distribute, sublicense, and/or sell copies of the Software, and to
2053  // permit persons to whom the Software is furnished to do so, subject to
2054  // the following conditions:
2055  // 
2056  // The above copyright notice and this permission notice shall be
2057  // included in all copies or substantial portions of the Software.
2058  //
2059  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
2060  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2061  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
2062  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
2063  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
2064  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2065  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2066  
2067  if(!Control) var Control = {};
2068  Control.Slider = Class.create();
2069  
2070  // options:
2071  //  axis: 'vertical', or 'horizontal' (default)
2072  //
2073  // callbacks:
2074  //  onChange(value)
2075  //  onSlide(value)
2076  Control.Slider.prototype = {
2077    initialize: function(handle, track, options) {
2078      var slider = this;
2079      
2080      if(handle instanceof Array) {
2081        this.handles = handle.collect( function(e) { return $(e) });
2082      } else {
2083        this.handles = [$(handle)];
2084      }
2085      
2086      this.track   = $(track);
2087      this.options = options || {};
2088  
2089      this.axis      = this.options.axis || 'horizontal';
2090      this.increment = this.options.increment || 1;
2091      this.step      = parseInt(this.options.step || '1');
2092      this.range     = this.options.range || $R(0,1);
2093      
2094      this.value     = 0; // assure backwards compat
2095      this.values    = this.handles.map( function() { return 0 });
2096      this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
2097      this.options.startSpan = $(this.options.startSpan || null);
2098      this.options.endSpan   = $(this.options.endSpan || null);
2099  
2100      this.restricted = this.options.restricted || false;
2101  
2102      this.maximum   = this.options.maximum || this.range.end;
2103      this.minimum   = this.options.minimum || this.range.start;
2104  
2105      // Will be used to align the handle onto the track, if necessary
2106      this.alignX = parseInt(this.options.alignX || '0');
2107      this.alignY = parseInt(this.options.alignY || '0');
2108      
2109      this.trackLength = this.maximumOffset() - this.minimumOffset();
2110  
2111      this.handleLength = this.isVertical() ? 
2112        (this.handles[0].offsetHeight != 0 ? 
2113          this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) : 
2114        (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth : 
2115          this.handles[0].style.width.replace(/px$/,""));
2116  
2117      this.active   = false;
2118      this.dragging = false;
2119      this.disabled = false;
2120  
2121      if(this.options.disabled) this.setDisabled();
2122  
2123      // Allowed values array
2124      this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
2125      if(this.allowedValues) {
2126        this.minimum = this.allowedValues.min();
2127        this.maximum = this.allowedValues.max();
2128      }
2129  
2130      this.eventMouseDown = this.startDrag.bindAsEventListener(this);
2131      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
2132      this.eventMouseMove = this.update.bindAsEventListener(this);
2133  
2134      // Initialize handles in reverse (make sure first handle is active)
2135      this.handles.each( function(h,i) {
2136        i = slider.handles.length-1-i;
2137        slider.setValue(parseFloat(
2138          (slider.options.sliderValue instanceof Array ? 
2139            slider.options.sliderValue[i] : slider.options.sliderValue) || 
2140           slider.range.start), i);
2141        Element.makePositioned(h); // fix IE
2142        Event.observe(h, "mousedown", slider.eventMouseDown);
2143      });
2144      
2145      Event.observe(this.track, "mousedown", this.eventMouseDown);
2146      Event.observe(document, "mouseup", this.eventMouseUp);
2147      Event.observe(document, "mousemove", this.eventMouseMove);
2148      
2149      this.initialized = true;
2150    },
2151    dispose: function() {
2152      var slider = this;    
2153      Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
2154      Event.stopObserving(document, "mouseup", this.eventMouseUp);
2155      Event.stopObserving(document, "mousemove", this.eventMouseMove);
2156      this.handles.each( function(h) {
2157        Event.stopObserving(h, "mousedown", slider.eventMouseDown);
2158      });
2159    },
2160    setDisabled: function(){
2161      this.disabled = true;
2162    },
2163    setEnabled: function(){
2164      this.disabled = false;
2165    },  
2166    getNearestValue: function(value){
2167      if(this.allowedValues){
2168        if(value >= this.allowedValues.max()) return(this.allowedValues.max());
2169        if(value <= this.allowedValues.min()) return(this.allowedValues.min());
2170        
2171        var offset = Math.abs(this.allowedValues[0] - value);
2172        var newValue = this.allowedValues[0];
2173        this.allowedValues.each( function(v) {
2174          var currentOffset = Math.abs(v - value);
2175          if(currentOffset <= offset){
2176            newValue = v;
2177            offset = currentOffset;
2178          } 
2179        });
2180        return newValue;
2181      }
2182      if(value > this.range.end) return this.range.end;
2183      if(value < this.range.start) return this.range.start;
2184      return value;
2185    },
2186    setValue: function(sliderValue, handleIdx){
2187      if(!this.active) {
2188        this.activeHandleIdx = handleIdx || 0;
2189        this.activeHandle    = this.handles[this.activeHandleIdx];
2190        this.updateStyles();
2191      }
2192      handleIdx = handleIdx || this.activeHandleIdx || 0;
2193      if(this.initialized && this.restricted) {
2194        if((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
2195          sliderValue = this.values[handleIdx-1];
2196        if((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
2197          sliderValue = this.values[handleIdx+1];
2198      }
2199      sliderValue = this.getNearestValue(sliderValue);
2200      this.values[handleIdx] = sliderValue;
2201      this.value = this.values[0]; // assure backwards compat
2202      
2203      this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = 
2204        this.translateToPx(sliderValue);
2205      
2206      this.drawSpans();
2207      if(!this.dragging || !this.event) this.updateFinished();
2208    },
2209    setValueBy: function(delta, handleIdx) {
2210      this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, 
2211        handleIdx || this.activeHandleIdx || 0);
2212    },
2213    translateToPx: function(value) {
2214      return Math.round(
2215        ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * 
2216        (value - this.range.start)) + "px";
2217    },
2218    translateToValue: function(offset) {
2219      return ((offset/(this.trackLength-this.handleLength) * 
2220        (this.range.end-this.range.start)) + this.range.start);
2221    },
2222    getRange: function(range) {
2223      var v = this.values.sortBy(Prototype.K); 
2224      range = range || 0;
2225      return $R(v[range],v[range+1]);
2226    },
2227    minimumOffset: function(){
2228      return(this.isVertical() ? this.alignY : this.alignX);
2229    },
2230    maximumOffset: function(){
2231      return(this.isVertical() ? 
2232        (this.track.offsetHeight != 0 ? this.track.offsetHeight :
2233          this.track.style.height.replace(/px$/,"")) - this.alignY : 
2234        (this.track.offsetWidth != 0 ? this.track.offsetWidth : 
2235          this.track.style.width.replace(/px$/,"")) - this.alignY);
2236    },  
2237    isVertical:  function(){
2238      return (this.axis == 'vertical');
2239    },
2240    drawSpans: function() {
2241      var slider = this;
2242      if(this.spans)
2243        $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
2244      if(this.options.startSpan)
2245        this.setSpan(this.options.startSpan,
2246          $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
2247      if(this.options.endSpan)
2248        this.setSpan(this.options.endSpan, 
2249          $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
2250    },
2251    setSpan: function(span, range) {
2252      if(this.isVertical()) {
2253        span.style.top = this.translateToPx(range.start);
2254        span.style.height = this.translateToPx(range.end - range.start + this.range.start);
2255      } else {
2256        span.style.left = this.translateToPx(range.start);
2257        span.style.width = this.translateToPx(range.end - range.start + this.range.start);
2258      }
2259    },
2260    updateStyles: function() {
2261      this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
2262      Element.addClassName(this.activeHandle, 'selected');
2263    },
2264    startDrag: function(event) {
2265      if(Event.isLeftClick(event)) {
2266        if(!this.disabled){
2267          this.active = true;
2268          
2269          var handle = Event.element(event);
2270          var pointer  = [Event.pointerX(event), Event.pointerY(event)];
2271          var track = handle;
2272          if(track==this.track) {
2273            var offsets  = Position.cumulativeOffset(this.track); 
2274            this.event = event;
2275            this.setValue(this.translateToValue( 
2276             (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
2277            ));
2278            var offsets  = Position.cumulativeOffset(this.activeHandle);
2279            this.offsetX = (pointer[0] - offsets[0]);
2280            this.offsetY = (pointer[1] - offsets[1]);
2281          } else {
2282            // find the handle (prevents issues with Safari)
2283            while((this.handles.indexOf(handle) == -1) && handle.parentNode) 
2284              handle = handle.parentNode;
2285          
2286            this.activeHandle    = handle;
2287            this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
2288            this.updateStyles();
2289          
2290            var offsets  = Position.cumulativeOffset(this.activeHandle);
2291            this.offsetX = (pointer[0] - offsets[0]);
2292            this.offsetY = (pointer[1] - offsets[1]);
2293          }
2294        }
2295        Event.stop(event);
2296      }
2297    },
2298    update: function(event) {
2299     if(this.active) {
2300        if(!this.dragging) this.dragging = true;
2301        this.draw(event);
2302        // fix AppleWebKit rendering
2303        if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
2304        Event.stop(event);
2305     }
2306    },
2307    draw: function(event) {
2308      var pointer = [Event.pointerX(event), Event.pointerY(event)];
2309      var offsets = Position.cumulativeOffset(this.track);
2310      pointer[0] -= this.offsetX + offsets[0];
2311      pointer[1] -= this.offsetY + offsets[1];
2312      this.event = event;
2313      this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
2314      if(this.initialized && this.options.onSlide)
2315        this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
2316    },
2317    endDrag: function(event) {
2318      if(this.active && this.dragging) {
2319        this.finishDrag(event, true);
2320        Event.stop(event);
2321      }
2322      this.active = false;
2323      this.dragging = false;
2324    },  
2325    finishDrag: function(event, success) {
2326      this.active = false;
2327      this.dragging = false;
2328      this.updateFinished();
2329    },
2330    updateFinished: function() {
2331      if(this.initialized && this.options.onChange) 
2332        this.options.onChange(this.values.length>1 ? this.values : this.value, this);
2333      this.event = null;
2334    }
2335  }
2336  


Généré le : Sun Feb 25 21:07:04 2007 par Balluche grâce à PHPXref 0.7