[ Index ] |
|
Code source de PRADO 3.0.6 |
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
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Sun Feb 25 21:07:04 2007 | par Balluche grâce à PHPXref 0.7 |