Changeset 226 for trunk/trax/data
- Timestamp:
- 06/21/06 11:53:01 (6 years ago)
- Location:
- trunk/trax/data/public/javascripts
- Files:
-
- 4 added
- 4 modified
-
application.js (added)
-
builder.js (added)
-
controls.js (modified) (20 diffs)
-
dragdrop.js (modified) (7 diffs)
-
effects.js (modified) (13 diffs)
-
prototype.js (modified) (50 diffs)
-
scriptaculous.js (added)
-
slider.js (added)
Legend:
- Unmodified
- Added
- Removed
-
trunk/trax/data/public/javascripts/controls.js
r182 r226 1 // $Id$2 1 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 3 2 // (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) 4 // 5 // Permission is hereby granted, free of charge, to any person obtaining 6 // a copy of this software and associated documentation files (the 7 // "Software"), to deal in the Software without restriction, including 8 // without limitation the rights to use, copy, modify, merge, publish, 9 // distribute, sublicense, and/or sell copies of the Software, and to 10 // permit persons to whom the Software is furnished to do so, subject to 11 // the following conditions: 12 // 13 // The above copyright notice and this permission notice shall be 14 // included in all copies or substantial portions of the Software. 15 // 16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 24 Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { 25 var children = $(element).childNodes; 26 var text = ""; 27 var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i"); 28 29 for (var i = 0; i < children.length; i++) { 30 if(children[i].nodeType==3) { 31 text+=children[i].nodeValue; 32 } else { 33 if((!children[i].className.match(classtest)) && children[i].hasChildNodes()) 34 text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass); 35 } 36 } 37 38 return text; 39 } 3 // (c) 2005 Jon Tirsen (http://www.tirsen.com) 4 // Contributors: 5 // Richard Livsey 6 // Rahul Bhargava 7 // Rob Wills 8 // 9 // See scriptaculous.js for full license. 40 10 41 11 // Autocompleter.Base handles all the autocompletion functionality … … 48 18 // the text inside the monitored textbox changes. This method 49 19 // should get the text for which to provide autocompletion by 50 // invoking this.get Entry(), NOT by directly accessing20 // invoking this.getToken(), NOT by directly accessing 51 21 // this.element.value. This is to allow incremental tokenized 52 22 // autocompletion. Specific auto-completion logic (AJAX, etc) … … 59 29 // will incrementally autocomplete with a comma as the token. 60 30 // Additionally, ',' in the above example can be replaced with 61 // a token array, e.g. { tokens: new Array (',', '\n')} which31 // a token array, e.g. { tokens: [',', '\n'] } which 62 32 // enables autocompletion on multiple tokens. This is most 63 33 // useful when one of the tokens is \n (a newline), as it … … 67 37 Autocompleter.Base = function() {}; 68 38 Autocompleter.Base.prototype = { 69 base _initialize: function(element, update, options) {39 baseInitialize: function(element, update, options) { 70 40 this.element = $(element); 71 41 this.update = $(update); 72 this.has _focus= false;42 this.hasFocus = false; 73 43 this.changed = false; 74 44 this.active = false; 75 45 this.index = 0; 76 this.entry _count= 0;46 this.entryCount = 0; 77 47 78 48 if (this.setOptions) … … 80 50 else 81 51 this.options = options || {}; 82 83 this.options.tokens = this.options.tokens || new Array(); 52 53 this.options.paramName = this.options.paramName || this.element.name; 54 this.options.tokens = this.options.tokens || []; 84 55 this.options.frequency = this.options.frequency || 0.4; 85 this.options.min _chars = this.options.min_chars || 1;56 this.options.minChars = this.options.minChars || 1; 86 57 this.options.onShow = this.options.onShow || 87 58 function(element, update){ 88 59 if(!update.style.position || update.style.position=='absolute') { 89 60 update.style.position = 'absolute'; 90 var offsets = Position.cumulativeOffset(element); 91 update.style.left = offsets[0] + 'px'; 92 update.style.top = (offsets[1] + element.offsetHeight) + 'px'; 93 update.style.width = element.offsetWidth + 'px'; 61 Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight}); 94 62 } 95 newEffect.Appear(update,{duration:0.15});63 Effect.Appear(update,{duration:0.15}); 96 64 }; 97 65 this.options.onHide = this.options.onHide || 98 66 function(element, update){ new Effect.Fade(update,{duration:0.15}) }; 99 100 if(this.options.indicator)101 this.indicator = $(this.options.indicator);102 67 103 68 if (typeof(this.options.tokens) == 'string') 104 69 this.options.tokens = new Array(this.options.tokens); 105 70 106 71 this.observer = null; 107 72 73 this.element.setAttribute('autocomplete','off'); 74 108 75 Element.hide(this.update); 109 76 110 77 Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); 111 78 Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); … … 113 80 114 81 show: function() { 115 if(this.update.style.display=='none') this.options.onShow(this.element, this.update); 116 if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') { 82 if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); 83 if(!this.iefix && 84 (navigator.appVersion.indexOf('MSIE')>0) && 85 (navigator.userAgent.indexOf('Opera')<0) && 86 (Element.getStyle(this.update, 'position')=='absolute')) { 117 87 new Insertion.After(this.update, 118 88 '<iframe id="' + this.update.id + '_iefix" '+ 119 'style="display:none; filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +89 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + 120 90 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); 121 91 this.iefix = $(this.update.id+'_iefix'); 122 92 } 123 if(this.iefix) { 124 Position.clone(this.update, this.iefix); 125 this.iefix.style.zIndex = 1; 126 this.update.style.zIndex = 2; 127 Element.show(this.iefix); 128 } 93 if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); 129 94 }, 130 95 96 fixIEOverlapping: function() { 97 Position.clone(this.update, this.iefix); 98 this.iefix.style.zIndex = 1; 99 this.update.style.zIndex = 2; 100 Element.show(this.iefix); 101 }, 102 131 103 hide: function() { 132 if(this.update.style.display=='') this.options.onHide(this.element, this.update); 104 this.stopIndicator(); 105 if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); 133 106 if(this.iefix) Element.hide(this.iefix); 134 107 }, 135 108 136 109 startIndicator: function() { 137 if(this. indicator) Element.show(this.indicator);138 }, 139 110 if(this.options.indicator) Element.show(this.options.indicator); 111 }, 112 140 113 stopIndicator: function() { 141 if(this. indicator) Element.hide(this.indicator);114 if(this.options.indicator) Element.hide(this.options.indicator); 142 115 }, 143 116 … … 147 120 case Event.KEY_TAB: 148 121 case Event.KEY_RETURN: 149 this.select _entry();122 this.selectEntry(); 150 123 Event.stop(event); 151 124 case Event.KEY_ESC: 152 125 this.hide(); 153 126 this.active = false; 127 Event.stop(event); 154 128 return; 155 129 case Event.KEY_LEFT: … … 157 131 return; 158 132 case Event.KEY_UP: 159 this.mark _previous();133 this.markPrevious(); 160 134 this.render(); 161 135 if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); 162 136 return; 163 137 case Event.KEY_DOWN: 164 this.mark _next();138 this.markNext(); 165 139 this.render(); 166 140 if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); … … 168 142 } 169 143 else 170 if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)171 return;172 144 if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 145 (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; 146 173 147 this.changed = true; 174 this.has _focus = true;175 148 this.hasFocus = true; 149 176 150 if(this.observer) clearTimeout(this.observer); 177 151 this.observer = 178 152 setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); 179 153 }, 180 154 155 activate: function() { 156 this.changed = false; 157 this.hasFocus = true; 158 this.getUpdatedChoices(); 159 }, 160 181 161 onHover: function(event) { 182 162 var element = Event.findElement(event, 'LI'); … … 192 172 var element = Event.findElement(event, 'LI'); 193 173 this.index = element.autocompleteIndex; 194 this.select _entry();195 Event.stop(event);174 this.selectEntry(); 175 this.hide(); 196 176 }, 197 177 … … 199 179 // needed to make click events working 200 180 setTimeout(this.hide.bind(this), 250); 201 this.has _focus = false;181 this.hasFocus = false; 202 182 this.active = false; 203 183 }, 204 184 205 185 render: function() { 206 if(this.entry _count > 0) {207 for (var i = 0; i < this.entry _count; i++)186 if(this.entryCount > 0) { 187 for (var i = 0; i < this.entryCount; i++) 208 188 this.index==i ? 209 Element.addClassName(this.get _entry(i),"selected") :210 Element.removeClassName(this.get _entry(i),"selected");189 Element.addClassName(this.getEntry(i),"selected") : 190 Element.removeClassName(this.getEntry(i),"selected"); 211 191 212 if(this.has_focus) { 213 if(this.get_current_entry().scrollIntoView) 214 this.get_current_entry().scrollIntoView(false); 215 192 if(this.hasFocus) { 216 193 this.show(); 217 194 this.active = true; 218 195 } 219 } else this.hide(); 196 } else { 197 this.active = false; 198 this.hide(); 199 } 220 200 }, 221 201 222 mark _previous: function() {202 markPrevious: function() { 223 203 if(this.index > 0) this.index-- 224 else this.index = this.entry _count-1;204 else this.index = this.entryCount-1; 225 205 }, 226 206 227 mark _next: function() {228 if(this.index < this.entry _count-1) this.index++207 markNext: function() { 208 if(this.index < this.entryCount-1) this.index++ 229 209 else this.index = 0; 230 210 }, 231 211 232 get _entry: function(index) {212 getEntry: function(index) { 233 213 return this.update.firstChild.childNodes[index]; 234 214 }, 235 215 236 get _current_entry: function() {237 return this.get _entry(this.index);216 getCurrentEntry: function() { 217 return this.getEntry(this.index); 238 218 }, 239 219 240 select _entry: function() {220 selectEntry: function() { 241 221 this.active = false; 242 value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML(); 243 this.updateElement(value); 244 this.element.focus(); 245 }, 246 247 updateElement: function(value) { 248 var last_token_pos = this.findLastToken(); 249 if (last_token_pos != -1) { 250 var new_value = this.element.value.substr(0, last_token_pos + 1); 251 var whitespace = this.element.value.substr(last_token_pos + 1).match(/^\s+/); 222 this.updateElement(this.getCurrentEntry()); 223 }, 224 225 updateElement: function(selectedElement) { 226 if (this.options.updateElement) { 227 this.options.updateElement(selectedElement); 228 return; 229 } 230 var value = ''; 231 if (this.options.select) { 232 var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; 233 if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); 234 } else 235 value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); 236 237 var lastTokenPos = this.findLastToken(); 238 if (lastTokenPos != -1) { 239 var newValue = this.element.value.substr(0, lastTokenPos + 1); 240 var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); 252 241 if (whitespace) 253 new _value += whitespace[0];254 this.element.value = new _value + value;242 newValue += whitespace[0]; 243 this.element.value = newValue + value; 255 244 } else { 256 245 this.element.value = value; 257 } 258 }, 259 246 } 247 this.element.focus(); 248 249 if (this.options.afterUpdateElement) 250 this.options.afterUpdateElement(this.element, selectedElement); 251 }, 252 260 253 updateChoices: function(choices) { 261 if(!this.changed && this.has _focus) {254 if(!this.changed && this.hasFocus) { 262 255 this.update.innerHTML = choices; 263 256 Element.cleanWhitespace(this.update); … … 265 258 266 259 if(this.update.firstChild && this.update.firstChild.childNodes) { 267 this.entry _count =260 this.entryCount = 268 261 this.update.firstChild.childNodes.length; 269 for (var i = 0; i < this.entry _count; i++) {270 entry = this.get_entry(i);262 for (var i = 0; i < this.entryCount; i++) { 263 var entry = this.getEntry(i); 271 264 entry.autocompleteIndex = i; 272 265 this.addObservers(entry); 273 266 } 274 267 } else { 275 this.entry _count = 0;268 this.entryCount = 0; 276 269 } 277 270 278 271 this.stopIndicator(); 279 272 280 273 this.index = 0; 281 274 this.render(); … … 290 283 onObserverEvent: function() { 291 284 this.changed = false; 292 if(this.get Entry().length>=this.options.min_chars) {285 if(this.getToken().length>=this.options.minChars) { 293 286 this.startIndicator(); 294 287 this.getUpdatedChoices(); … … 299 292 }, 300 293 301 get Entry: function() {302 var token _pos = this.findLastToken();303 if (token _pos != -1)304 var ret = this.element.value.substr(token _pos + 1).replace(/^\s+/,'').replace(/\s+$/,'');294 getToken: function() { 295 var tokenPos = this.findLastToken(); 296 if (tokenPos != -1) 297 var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); 305 298 else 306 299 var ret = this.element.value; 307 300 308 301 return /\n/.test(ret) ? '' : ret; 309 302 }, 310 303 311 304 findLastToken: function() { 312 var last _token_pos = -1;305 var lastTokenPos = -1; 313 306 314 307 for (var i=0; i<this.options.tokens.length; i++) { 315 var this _token_pos = this.element.value.lastIndexOf(this.options.tokens[i]);316 if (this _token_pos > last_token_pos)317 last _token_pos = this_token_pos;318 } 319 return last _token_pos;308 var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]); 309 if (thisTokenPos > lastTokenPos) 310 lastTokenPos = thisTokenPos; 311 } 312 return lastTokenPos; 320 313 } 321 314 } … … 324 317 Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { 325 318 initialize: function(element, update, url, options) { 326 this.base_initialize(element, update, options);319 this.baseInitialize(element, update, options); 327 320 this.options.asynchronous = true; 328 this.options.onComplete = this.onComplete.bind(this) 329 this.options.method = 'post'; 321 this.options.onComplete = this.onComplete.bind(this); 330 322 this.options.defaultParams = this.options.parameters || null; 331 323 this.url = url; 332 324 }, 333 325 334 326 getUpdatedChoices: function() { 335 entry = encodeURIComponent(this. element.name) + '=' +336 encodeURIComponent(this.get Entry());337 327 entry = encodeURIComponent(this.options.paramName) + '=' + 328 encodeURIComponent(this.getToken()); 329 338 330 this.options.parameters = this.options.callback ? 339 331 this.options.callback(this.element, entry) : entry; 340 332 341 333 if(this.options.defaultParams) 342 334 this.options.parameters += '&' + this.options.defaultParams; 343 335 344 336 new Ajax.Request(this.url, this.options); 345 337 }, 346 338 347 339 onComplete: function(request) { 348 340 this.updateChoices(request.responseText); … … 363 355 // - choices - How many autocompletion choices to offer 364 356 // 365 // - partial _search - If false, the autocompleter will match entered357 // - partialSearch - If false, the autocompleter will match entered 366 358 // text only at the beginning of strings in the 367 359 // autocomplete array. Defaults to true, which will … … 369 361 // strings in the autocomplete array. If you want to 370 362 // search anywhere in the string, additionally set 371 // the option full _search to true (default: off).372 // 373 // - full _search - Search anywhere in autocomplete array strings.374 // 375 // - partial _chars - How many characters to enter before triggering376 // a partial match (unlike min _chars, which defines363 // the option fullSearch to true (default: off). 364 // 365 // - fullSsearch - Search anywhere in autocomplete array strings. 366 // 367 // - partialChars - How many characters to enter before triggering 368 // a partial match (unlike minChars, which defines 377 369 // how many characters are required to do any match 378 370 // at all). Defaults to 2. 379 371 // 380 // - ignore _case - Whether to ignore case when autocompleting.372 // - ignoreCase - Whether to ignore case when autocompleting. 381 373 // Defaults to true. 382 374 // … … 389 381 Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { 390 382 initialize: function(element, update, array, options) { 391 this.base _initialize(element, update, options);383 this.baseInitialize(element, update, options); 392 384 this.options.array = array; 393 385 }, … … 400 392 this.options = Object.extend({ 401 393 choices: 10, 402 partial _search: true,403 partial _chars: 2,404 ignore _case: true,405 full _search: false,394 partialSearch: true, 395 partialChars: 2, 396 ignoreCase: true, 397 fullSearch: false, 406 398 selector: function(instance) { 407 var ret = new Array(); // Beginning matches408 var partial = new Array(); // Inside matches409 var entry = instance.get Entry();399 var ret = []; // Beginning matches 400 var partial = []; // Inside matches 401 var entry = instance.getToken(); 410 402 var count = 0; 411 403 412 404 for (var i = 0; i < instance.options.array.length && 413 ret.length < instance.options.choices ; i++) { 405 ret.length < instance.options.choices ; i++) { 406 414 407 var elem = instance.options.array[i]; 415 var found _pos = instance.options.ignore_case ?408 var foundPos = instance.options.ignoreCase ? 416 409 elem.toLowerCase().indexOf(entry.toLowerCase()) : 417 410 elem.indexOf(entry); 418 411 419 while (found _pos != -1) {420 if (found _pos == 0 && elem.length != entry.length) {412 while (foundPos != -1) { 413 if (foundPos == 0 && elem.length != entry.length) { 421 414 ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 422 415 elem.substr(entry.length) + "</li>"); 423 416 break; 424 } else if (entry.length >= instance.options.partial _chars &&425 instance.options.partial _search && found_pos != -1) {426 if (instance.options.full _search || /\s/.test(elem.substr(found_pos-1,1))) {427 partial.push("<li>" + elem.substr(0, found _pos) + "<strong>" +428 elem.substr(found _pos, entry.length) + "</strong>" + elem.substr(429 found _pos + entry.length) + "</li>");417 } else if (entry.length >= instance.options.partialChars && 418 instance.options.partialSearch && foundPos != -1) { 419 if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { 420 partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" + 421 elem.substr(foundPos, entry.length) + "</strong>" + elem.substr( 422 foundPos + entry.length) + "</li>"); 430 423 break; 431 424 } 432 425 } 433 426 434 found _pos = instance.options.ignore_case ?435 elem.toLowerCase().indexOf(entry.toLowerCase(), found _pos + 1) :436 elem.indexOf(entry, found _pos + 1);427 foundPos = instance.options.ignoreCase ? 428 elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 429 elem.indexOf(entry, foundPos + 1); 437 430 438 431 } … … 445 438 } 446 439 }); 440 441 // AJAX in-place editor 442 // 443 // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor 444 445 // Use this if you notice weird scrolling problems on some browsers, 446 // the DOM might be a bit confused when this gets called so do this 447 // waits 1 ms (with setTimeout) until it does the activation 448 Field.scrollFreeActivate = function(field) { 449 setTimeout(function() { 450 Field.activate(field); 451 }, 1); 452 } 453 454 Ajax.InPlaceEditor = Class.create(); 455 Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; 456 Ajax.InPlaceEditor.prototype = { 457 initialize: function(element, url, options) { 458 this.url = url; 459 this.element = $(element); 460 461 this.options = Object.extend({ 462 okButton: true, 463 okText: "ok", 464 cancelLink: true, 465 cancelText: "cancel", 466 savingText: "Saving...", 467 clickToEditText: "Click to edit", 468 okText: "ok", 469 rows: 1, 470 onComplete: function(transport, element) { 471 new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); 472 }, 473 onFailure: function(transport) { 474 alert("Error communicating with the server: " + transport.responseText.stripTags()); 475 }, 476 callback: function(form) { 477 return Form.serialize(form); 478 }, 479 handleLineBreaks: true, 480 loadingText: 'Loading...', 481 savingClassName: 'inplaceeditor-saving', 482 loadingClassName: 'inplaceeditor-loading', 483 formClassName: 'inplaceeditor-form', 484 highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, 485 highlightendcolor: "#FFFFFF", 486 externalControl: null, 487 submitOnBlur: false, 488 ajaxOptions: {}, 489 evalScripts: false 490 }, options || {}); 491 492 if(!this.options.formId && this.element.id) { 493 this.options.formId = this.element.id + "-inplaceeditor"; 494 if ($(this.options.formId)) { 495 // there's already a form with that name, don't specify an id 496 this.options.formId = null; 497 } 498 } 499 500 if (this.options.externalControl) { 501 this.options.externalControl = $(this.options.externalControl); 502 } 503 504 this.originalBackground = Element.getStyle(this.element, 'background-color'); 505 if (!this.originalBackground) { 506 this.originalBackground = "transparent"; 507 } 508 509 this.element.title = this.options.clickToEditText; 510 511 this.onclickListener = this.enterEditMode.bindAsEventListener(this); 512 this.mouseoverListener = this.enterHover.bindAsEventListener(this); 513 this.mouseoutListener = this.leaveHover.bindAsEventListener(this); 514 Event.observe(this.element, 'click', this.onclickListener); 515 Event.observe(this.element, 'mouseover', this.mouseoverListener); 516 Event.observe(this.element, 'mouseout', this.mouseoutListener); 517 if (this.options.externalControl) { 518 Event.observe(this.options.externalControl, 'click', this.onclickListener); 519 Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); 520 Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); 521 } 522 }, 523 enterEditMode: function(evt) { 524 if (this.saving) return; 525 if (this.editing) return; 526 this.editing = true; 527 this.onEnterEditMode(); 528 if (this.options.externalControl) { 529 Element.hide(this.options.externalControl); 530 } 531 Element.hide(this.element); 532 this.createForm(); 533 this.element.parentNode.insertBefore(this.form, this.element); 534 Field.scrollFreeActivate(this.editField); 535 // stop the event to avoid a page refresh in Safari 536 if (evt) { 537 Event.stop(evt); 538 } 539 return false; 540 }, 541 createForm: function() { 542 this.form = document.createElement("form"); 543 this.form.id = this.options.formId; 544 Element.addClassName(this.form, this.options.formClassName) 545 this.form.onsubmit = this.onSubmit.bind(this); 546 547 this.createEditField(); 548 549 if (this.options.textarea) { 550 var br = document.createElement("br"); 551 this.form.appendChild(br); 552 } 553 554 if (this.options.okButton) { 555 okButton = document.createElement("input"); 556 okButton.type = "submit"; 557 okButton.value = this.options.okText; 558 okButton.className = 'editor_ok_button'; 559 this.form.appendChild(okButton); 560 } 561 562 if (this.options.cancelLink) { 563 cancelLink = document.createElement("a"); 564 cancelLink.href = "#"; 565 cancelLink.appendChild(document.createTextNode(this.options.cancelText)); 566 cancelLink.onclick = this.onclickCancel.bind(this); 567 cancelLink.className = 'editor_cancel'; 568 this.form.appendChild(cancelLink); 569 } 570 }, 571 hasHTMLLineBreaks: function(string) { 572 if (!this.options.handleLineBreaks) return false; 573 return string.match(/<br/i) || string.match(/<p>/i); 574 }, 575 convertHTMLLineBreaks: function(string) { 576 return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, ""); 577 }, 578 createEditField: function() { 579 var text; 580 if(this.options.loadTextURL) { 581 text = this.options.loadingText; 582 } else { 583 text = this.getText(); 584 } 585 586 var obj = this; 587 588 if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { 589 this.options.textarea = false; 590 var textField = document.createElement("input"); 591 textField.obj = this; 592 textField.type = "text"; 593 textField.name = "value"; 594 textField.value = text; 595 textField.style.backgroundColor = this.options.highlightcolor; 596 textField.className = 'editor_field'; 597 var size = this.options.size || this.options.cols || 0; 598 if (size != 0) textField.size = size; 599 if (this.options.submitOnBlur) 600 textField.onblur = this.onSubmit.bind(this); 601 this.editField = textField; 602 } else { 603 this.options.textarea = true; 604 var textArea = document.createElement("textarea"); 605 textArea.obj = this; 606 textArea.name = "value"; 607 textArea.value = this.convertHTMLLineBreaks(text); 608 textArea.rows = this.options.rows; 609 textArea.cols = this.options.cols || 40; 610 textArea.className = 'editor_field'; 611 if (this.options.submitOnBlur) 612 textArea.onblur = this.onSubmit.bind(this); 613 this.editField = textArea; 614 } 615 616 if(this.options.loadTextURL) { 617 this.loadExternalText(); 618 } 619 this.form.appendChild(this.editField); 620 }, 621 getText: function() { 622 return this.element.innerHTML; 623 }, 624 loadExternalText: function() { 625 Element.addClassName(this.form, this.options.loadingClassName); 626 this.editField.disabled = true; 627 new Ajax.Request( 628 this.options.loadTextURL, 629 Object.extend({ 630 asynchronous: true, 631 onComplete: this.onLoadedExternalText.bind(this) 632 }, this.options.ajaxOptions) 633 ); 634 }, 635 onLoadedExternalText: function(transport) { 636 Element.removeClassName(this.form, this.options.loadingClassName); 637 this.editField.disabled = false; 638 this.editField.value = transport.responseText.stripTags(); 639 }, 640 onclickCancel: function() { 641 this.onComplete(); 642 this.leaveEditMode(); 643 return false; 644 }, 645 onFailure: function(transport) { 646 this.options.onFailure(transport); 647 if (this.oldInnerHTML) { 648 this.element.innerHTML = this.oldInnerHTML; 649 this.oldInnerHTML = null; 650 } 651 return false; 652 }, 653 onSubmit: function() { 654 // onLoading resets these so we need to save them away for the Ajax call 655 var form = this.form; 656 var value = this.editField.value; 657 658 // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... 659 // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... 660 // to be displayed indefinitely 661 this.onLoading(); 662 663 if (this.options.evalScripts) { 664 new Ajax.Request( 665 this.url, Object.extend({ 666 parameters: this.options.callback(form, value), 667 onComplete: this.onComplete.bind(this), 668 onFailure: this.onFailure.bind(this), 669 asynchronous:true, 670 evalScripts:true 671 }, this.options.ajaxOptions)); 672 } else { 673 new Ajax.Updater( 674 { success: this.element, 675 // don't update on failure (this could be an option) 676 failure: null }, 677 this.url, Object.extend({ 678 parameters: this.options.callback(form, value), 679 onComplete: this.onComplete.bind(this), 680 onFailure: this.onFailure.bind(this) 681 }, this.options.ajaxOptions)); 682 } 683 // stop the event to avoid a page refresh in Safari 684 if (arguments.length > 1) { 685 Event.stop(arguments[0]); 686 } 687 return false; 688 }, 689 onLoading: function() { 690 this.saving = true; 691 this.removeForm(); 692 this.leaveHover(); 693 this.showSaving(); 694 }, 695 showSaving: function() { 696 this.oldInnerHTML = this.element.innerHTML; 697 this.element.innerHTML = this.options.savingText; 698 Element.addClassName(this.element, this.options.savingClassName); 699 this.element.style.backgroundColor = this.originalBackground; 700 Element.show(this.element); 701 }, 702 removeForm: function() { 703 if(this.form) { 704 if (this.form.parentNode) Element.remove(this.form); 705 this.form = null; 706 } 707 }, 708 enterHover: function() { 709 if (this.saving) return; 710 this.element.style.backgroundColor = this.options.highlightcolor; 711 if (this.effect) { 712 this.effect.cancel(); 713 } 714 Element.addClassName(this.element, this.options.hoverClassName) 715 }, 716 leaveHover: function() { 717 if (this.options.backgroundColor) { 718 this.element.style.backgroundColor = this.oldBackground; 719 } 720 Element.removeClassName(this.element, this.options.hoverClassName) 721 if (this.saving) return; 722 this.effect = new Effect.Highlight(this.element, { 723 startcolor: this.options.highlightcolor, 724 endcolor: this.options.highlightendcolor, 725 restorecolor: this.originalBackground 726 }); 727 }, 728 leaveEditMode: function() { 729 Element.removeClassName(this.element, this.options.savingClassName); 730 this.removeForm(); 731 this.leaveHover(); 732 this.element.style.backgroundColor = this.originalBackground; 733 Element.show(this.element); 734 if (this.options.externalControl) { 735 Element.show(this.options.externalControl); 736 } 737 this.editing = false; 738 this.saving = false; 739 this.oldInnerHTML = null; 740 this.onLeaveEditMode(); 741 }, 742 onComplete: function(transport) { 743 this.leaveEditMode(); 744 this.options.onComplete.bind(this)(transport, this.element); 745 }, 746 onEnterEditMode: function() {}, 747 onLeaveEditMode: function() {}, 748 dispose: function() { 749 if (this.oldInnerHTML) { 750 this.element.innerHTML = this.oldInnerHTML; 751 } 752 this.leaveEditMode(); 753 Event.stopObserving(this.element, 'click', this.onclickListener); 754 Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); 755 Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); 756 if (this.options.externalControl) { 757 Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); 758 Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); 759 Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); 760 } 761 } 762 }; 763 764 Ajax.InPlaceCollectionEditor = Class.create(); 765 Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); 766 Object.extend(Ajax.InPlaceCollectionEditor.prototype, { 767 createEditField: function() { 768 if (!this.cached_selectTag) { 769 var selectTag = document.createElement("select"); 770 var collection = this.options.collection || []; 771 var optionTag; 772 collection.each(function(e,i) { 773 optionTag = document.createElement("option"); 774 optionTag.value = (e instanceof Array) ? e[0] : e; 775 if(this.options.value==optionTag.value) optionTag.selected = true; 776 optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); 777 selectTag.appendChild(optionTag); 778 }.bind(this)); 779 this.cached_selectTag = selectTag; 780 } 781 782 this.editField = this.cached_selectTag; 783 if(this.options.loadTextURL) this.loadExternalText(); 784 this.form.appendChild(this.editField); 785 this.options.callback = function(form, value) { 786 return "value=" + encodeURIComponent(value); 787 } 788 } 789 }); 790 791 // Delayed observer, like Form.Element.Observer, 792 // but waits for delay after last key input 793 // Ideal for live-search fields 794 795 Form.Element.DelayedObserver = Class.create(); 796 Form.Element.DelayedObserver.prototype = { 797 initialize: function(element, delay, callback) { 798 this.delay = delay || 0.5; 799 this.element = $(element); 800 this.callback = callback; 801 this.timer = null; 802 this.lastValue = $F(this.element); 803 Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); 804 }, 805 delayedListener: function(event) { 806 if(this.lastValue == $F(this.element)) return; 807 if(this.timer) clearTimeout(this.timer); 808 this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); 809 this.lastValue = $F(this.element); 810 }, 811 onTimerEvent: function() { 812 this.timer = null; 813 this.callback(this.element, $F(this.element)); 814 } 815 }; -
trunk/trax/data/public/javascripts/dragdrop.js
r182 r226 1 // $Id$2 1 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 3 // 4 // Element.Class part Copyright (c) 2005 by Rick Olson 5 // 6 // Permission is hereby granted, free of charge, to any person obtaining 7 // a copy of this software and associated documentation files (the 8 // "Software"), to deal in the Software without restriction, including 9 // without limitation the rights to use, copy, modify, merge, publish, 10 // distribute, sublicense, and/or sell copies of the Software, and to 11 // permit persons to whom the Software is furnished to do so, subject to 12 // the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be 15 // included in all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Element.Class = { 26 // Element.toggleClass(element, className) toggles the class being on/off 27 // Element.toggleClass(element, className1, className2) toggles between both classes, 28 // defaulting to className1 if neither exist 29 toggle: function(element, className) { 30 if(Element.Class.has(element, className)) { 31 Element.Class.remove(element, className); 32 if(arguments.length == 3) Element.Class.add(element, arguments[2]); 33 } else { 34 Element.Class.add(element, className); 35 if(arguments.length == 3) Element.Class.remove(element, arguments[2]); 36 } 37 }, 38 39 // gets space-delimited classnames of an element as an array 40 get: function(element) { 41 element = $(element); 42 return element.className.split(' '); 43 }, 44 45 // functions adapted from original functions by Gavin Kistner 46 remove: function(element) { 47 element = $(element); 48 var regEx; 49 for(var i = 1; i < arguments.length; i++) { 50 regEx = new RegExp("^" + arguments[i] + "\\b\\s*|\\s*\\b" + arguments[i] + "\\b", 'g'); 51 element.className = element.className.replace(regEx, '') 52 } 53 }, 54 55 add: function(element) { 56 element = $(element); 57 for(var i = 1; i < arguments.length; i++) { 58 Element.Class.remove(element, arguments[i]); 59 element.className += (element.className.length > 0 ? ' ' : '') + arguments[i]; 60 } 61 }, 62 63 // returns true if all given classes exist in said element 64 has: function(element) { 65 element = $(element); 66 if(!element || !element.className) return false; 67 var regEx; 68 for(var i = 1; i < arguments.length; i++) { 69 regEx = new RegExp("\\b" + arguments[i] + "\\b"); 70 if(!regEx.test(element.className)) return false; 71 } 72 return true; 73 }, 74 75 // expects arrays of strings and/or strings as optional paramters 76 // Element.Class.has_any(element, ['classA','classB','classC'], 'classD') 77 has_any: function(element) { 78 element = $(element); 79 if(!element || !element.className) return false; 80 var regEx; 81 for(var i = 1; i < arguments.length; i++) { 82 if((typeof arguments[i] == 'object') && 83 (arguments[i].constructor == Array)) { 84 for(var j = 0; j < arguments[i].length; j++) { 85 regEx = new RegExp("\\b" + arguments[i][j] + "\\b"); 86 if(regEx.test(element.className)) return true; 87 } 88 } else { 89 regEx = new RegExp("\\b" + arguments[i] + "\\b"); 90 if(regEx.test(element.className)) return true; 91 } 92 } 93 return false; 94 }, 95 96 childrenWith: function(element, className) { 97 var children = $(element).getElementsByTagName('*'); 98 var elements = new Array(); 99 100 for (var i = 0; i < children.length; i++) { 101 if (Element.Class.has(children[i], className)) { 102 elements.push(children[i]); 103 break; 104 } 105 } 106 107 return elements; 108 } 109 } 2 // (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) 3 // 4 // See scriptaculous.js for full license. 110 5 111 6 /*--------------------------------------------------------------------------*/ 112 7 113 8 var Droppables = { 114 drops: false,115 9 drops: [], 10 116 11 remove: function(element) { 117 for(var i = 0; i < this.drops.length; i++) 118 if(this.drops[i].element == element) 119 this.drops.splice(i,1); 120 }, 121 12 this.drops = this.drops.reject(function(d) { return d.element==$(element) }); 13 }, 14 122 15 add: function(element) { 123 varelement = $(element);16 element = $(element); 124 17 var options = Object.extend({ 125 18 greedy: true, 126 hoverclass: null 19 hoverclass: null, 20 tree: false 127 21 }, arguments[1] || {}); 128 22 129 23 // cache containers 130 24 if(options.containment) { 131 options._containers = new Array();25 options._containers = []; 132 26 var containment = options.containment; 133 27 if((typeof containment == 'object') && 134 28 (containment.constructor == Array)) { 135 for(var i=0; i<containment.length; i++) 136 options._containers.push($(containment[i])); 29 containment.each( function(c) { options._containers.push($(c)) }); 137 30 } else { 138 31 options._containers.push($(containment)); 139 32 } 140 options._containers_length =141 options._containers.length-1;142 }143 33 } 34 35 if(options.accept) options.accept = [options.accept].flatten(); 36 144 37 Element.makePositioned(element); // fix IE 145 146 38 options.element = element; 147 148 // activate the droppable 149 if(!this.drops) this.drops = []; 39 150 40 this.drops.push(options); 151 41 }, 152 42 153 is_contained: function(element, drop) { 154 var containers = drop._containers; 155 var parentNode = element.parentNode; 156 var i = drop._containers_length; 157 do { if(parentNode==containers[i]) return true; } while (i--); 158 return false; 159 }, 160 161 is_affected: function(pX, pY, element, drop) { 43 findDeepestChild: function(drops) { 44 deepest = drops[0]; 45 46 for (i = 1; i < drops.length; ++i) 47 if (Element.isParent(drops[i].element, deepest.element)) 48 deepest = drops[i]; 49 50 return deepest; 51 }, 52 53 isContained: function(element, drop) { 54 var containmentNode; 55 if(drop.tree) { 56 containmentNode = element.treeNode; 57 } else { 58 containmentNode = element.parentNode; 59 } 60 return drop._containers.detect(function(c) { return containmentNode == c }); 61 }, 62 63 isAffected: function(point, element, drop) { 162 64 return ( 163 65 (drop.element!=element) && 164 66 ((!drop._containers) || 165 this.is _contained(element, drop)) &&67 this.isContained(element, drop)) && 166 68 ((!drop.accept) || 167 (Element.Class.has_any(element, drop.accept))) && 168 Position.within(drop.element, pX, pY) ); 169 }, 170 69 (Element.classNames(element).detect( 70 function(v) { return drop.accept.include(v) } ) )) && 71 Position.within(drop.element, point[0], point[1]) ); 72 }, 73 171 74 deactivate: function(drop) { 172 Element.Class.remove(drop.element, drop.hoverclass); 75 if(drop.hoverclass) 76 Element.removeClassName(drop.element, drop.hoverclass); 173 77 this.last_active = null; 174 78 }, 175 79 176 80 activate: function(drop) { 81 if(drop.hoverclass) 82 Element.addClassName(drop.element, drop.hoverclass); 83 this.last_active = drop; 84 }, 85 86 show: function(point, element) { 87 if(!this.drops.length) return; 88 var affected = []; 89 177 90 if(this.last_active) this.deactivate(this.last_active); 178 if(drop.hoverclass) { 179 Element.Class.add(drop.element, drop.hoverclass); 180 this.last_active = drop; 181 } 182 }, 183 184 show: function(event, element) { 185 if(!this.drops) return; 186 var pX = Event.pointerX(event); 187 var pY = Event.pointerY(event); 188 Position.prepare(); 189 190 var i = this.drops.length-1; do { 191 var drop = this.drops[i]; 192 if(this.is_affected(pX, pY, element, drop)) { 193 if(drop.onHover) 194 drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); 195 if(drop.greedy) { 196 this.activate(drop); 197 return; 198 } 199 } 200 } while (i--); 201 }, 202 91 this.drops.each( function(drop) { 92 if(Droppables.isAffected(point, element, drop)) 93 affected.push(drop); 94 }); 95 96 if(affected.length>0) { 97 drop = Droppables.findDeepestChild(affected); 98 Position.within(drop.element, point[0], point[1]); 99 if(drop.onHover) 100 drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); 101 102 Droppables.activate(drop); 103 } 104 }, 105 203 106 fire: function(event, element) { 204 107 if(!this.last_active) return; 205 108 Position.prepare(); 206 207 if (this.is _affected(Event.pointerX(event), Event.pointerY(event), element, this.last_active))109 110 if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) 208 111 if (this.last_active.onDrop) 209 this.last_active.onDrop(element, this.last_active); 210 211 }, 212 112 this.last_active.onDrop(element, this.last_active.element, event); 113 }, 114 213 115 reset: function() { 214 116 if(this.last_active) … … 217 119 } 218 120 219 Draggables = { 220 observers: new Array(), 121 var Draggables = { 122 drags: [], 123 observers: [], 124 125 register: function(draggable) { 126 if(this.drags.length == 0) { 127 this.eventMouseUp = this.endDrag.bindAsEventListener(this); 128 this.eventMouseMove = this.updateDrag.bindAsEventListener(this); 129 this.eventKeypress = this.keyPress.bindAsEventListener(this); 130 131 Event.observe(document, "mouseup", this.eventMouseUp); 132 Event.observe(document, "mousemove", this.eventMouseMove); 133 Event.observe(document, "keypress", this.eventKeypress); 134 } 135 this.drags.push(draggable); 136 }, 137 138 unregister: function(draggable) { 139 this.drags = this.drags.reject(function(d) { return d==draggable }); 140 if(this.drags.length == 0) { 141 Event.stopObserving(document, "mouseup", this.eventMouseUp); 142 Event.stopObserving(document, "mousemove", this.eventMouseMove); 143 Event.stopObserving(document, "keypress", this.eventKeypress); 144 } 145 }, 146 147 activate: function(draggable) { 148 window.focus(); // allows keypress events if window isn't currently focused, fails for Safari 149 this.activeDraggable = draggable; 150 }, 151 152 deactivate: function() { 153 this.activeDraggable = null; 154 }, 155 156 updateDrag: function(event) { 157 if(!this.activeDraggable) return; 158 var pointer = [Event.pointerX(event), Event.pointerY(event)]; 159 // Mozilla-based browsers fire successive mousemove events with 160 // the same coordinates, prevent needless redrawing (moz bug?) 161 if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; 162 this._lastPointer = pointer; 163 this.activeDraggable.updateDrag(event, pointer); 164 }, 165 166 endDrag: function(event) { 167 if(!this.activeDraggable) return; 168 this._lastPointer = null; 169 this.activeDraggable.endDrag(event); 170 this.activeDraggable = null; 171 }, 172 173 keyPress: function(event) { 174 if(this.activeDraggable) 175 this.activeDraggable.keyPress(event); 176 }, 177 221 178 addObserver: function(observer) { 222 this.observers.push(observer); 223 }, 224 removeObserver: function(element) { // element instead of obsever fixes mem leaks 225 for(var i = 0; i < this.observers.length; i++) 226 if(this.observers[i].element && (this.observers[i].element == element)) 227 this.observers.splice(i,1); 228 }, 229 notify: function(eventName, draggable) { // 'onStart', 'onEnd' 230 for(var i = 0; i < this.observers.length; i++) 231 this.observers[i][eventName](draggable); 179 this.observers.push(observer); 180 this._cacheObserverCallbacks(); 181 }, 182 183 removeObserver: function(element) { // element instead of observer fixes mem leaks 184 this.observers = this.observers.reject( function(o) { return o.element==element }); 185 this._cacheObserverCallbacks(); 186 }, 187 188 notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' 189 if(this[eventName+'Count'] > 0) 190 this.observers.each( function(o) { 191 if(o[eventName]) o[eventName](eventName, draggable, event); 192 }); 193 }, 194 195 _cacheObserverCallbacks: function() { 196 ['onStart','onEnd','onDrag'].each( function(eventName) { 197 Draggables[eventName+'Count'] = Draggables.observers.select( 198 function(o) { return o[eventName]; } 199 ).length; 200 }); 232 201 } 233 202 } … … 235 204 /*--------------------------------------------------------------------------*/ 236 205 237 Draggable = Class.create();206 var Draggable = Class.create(); 238 207 Draggable.prototype = { 239 208 initialize: function(element) { 240 209 var options = Object.extend({ 241 210 handle: false, 242 starteffect: function(element) { 243 new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); 211 starteffect: function(element) { 212 element._opacity = Element.getOpacity(element); 213 new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 244 214 }, 245 215 reverteffect: function(element, top_offset, left_offset) { 246 new Effect.MoveBy(element, -top_offset, -left_offset, {duration:0.4}); 216 var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; 217 element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur}); 247 218 }, 248 endeffect: function(element) { 249 new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); 219 endeffect: function(element) { 220 var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0 221 new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity}); 250 222 }, 251 223 zindex: 1000, 252 revert: false 224 revert: false, 225 scroll: false, 226 scrollSensitivity: 20, 227 scrollSpeed: 15, 228 snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] } 253 229 }, arguments[1] || {}); 254 255 this.element = $(element); 256 this.handle = options.handle ? $(options.handle) : this.element; 257 258 Element.makePositioned(this.element); // fix IE 259 260 this.offsetX = 0; 261 this.offsetY = 0; 262 this.originalLeft = this.currentLeft(); 263 this.originalTop = this.currentTop(); 264 this.originalX = this.element.offsetLeft; 265 this.originalY = this.element.offsetTop; 266 this.originalZ = parseInt(this.element.style.zIndex || "0"); 267 268 this.options = options; 269 270 this.active = false; 271 this.dragging = false; 272 273 this.eventMouseDown = this.startDrag.bindAsEventListener(this); 274 this.eventMouseUp = this.endDrag.bindAsEventListener(this); 275 this.eventMouseMove = this.update.bindAsEventListener(this); 276 this.eventKeypress = this.keyPress.bindAsEventListener(this); 277 230 231 this.element = $(element); 232 233 if(options.handle && (typeof options.handle == 'string')) { 234 var h = Element.childrenWithClassName(this.element, options.handle, true); 235 if(h.length>0) this.handle = h[0]; 236 } 237 if(!this.handle) this.handle = $(options.handle); 238 if(!this.handle) this.handle = this.element; 239 240 if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) 241 options.scroll = $(options.scroll); 242 243 Element.makePositioned(this.element); // fix IE 244 245 this.delta = this.currentDelta(); 246 this.options = options; 247 this.dragging = false; 248 249 this.eventMouseDown = this.initDrag.bindAsEventListener(this); 278 250 Event.observe(this.handle, "mousedown", this.eventMouseDown); 279 Event.observe(document, "mouseup", this.eventMouseUp);280 Event.observe(document, "mousemove", this.eventMouseMove);281 Event.observe(document, "keypress", this.eventKeypress);282 },251 252 Draggables.register(this); 253 }, 254 283 255 destroy: function() { 284 256 Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); 285 Event.stopObserving(document, "mouseup", this.eventMouseUp); 286 Event.stopObserving(document, "mousemove", this.eventMouseMove); 287 Event.stopObserving(document, "keypress", this.eventKeypress); 288 }, 289 currentLeft: function() { 290 return parseInt(this.element.style.left || '0'); 291 }, 292 currentTop: function() { 293 return parseInt(this.element.style.top || '0') 294 }, 257 Draggables.unregister(this); 258 }, 259 260 currentDelta: function() { 261 return([ 262 parseInt(Element.getStyle(this.element,'left') || '0'), 263 parseInt(Element.getStyle(this.element,'top') || '0')]); 264 }, 265 266 initDrag: function(event) { 267 if(Event.isLeftClick(event)) { 268 // abort on form elements, fixes a Firefox issue 269 var src = Event.element(event); 270 if(src.tagName && ( 271 src.tagName=='INPUT' || 272 src.tagName=='SELECT' || 273 src.tagName=='OPTION' || 274 src.tagName=='BUTTON' || 275 src.tagName=='TEXTAREA')) return; 276 277 if(this.element._revert) { 278 this.element._revert.cancel(); 279 this.element._revert = null; 280 } 281 282 var pointer = [Event.pointerX(event), Event.pointerY(event)]; 283 var pos = Position.cumulativeOffset(this.element); 284 this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); 285 286 Draggables.activate(this); 287 Event.stop(event); 288 } 289 }, 290 295 291 startDrag: function(event) { 296 if(Event.isLeftClick(event)) { 297 this.active = true; 298 299 var style = this.element.style; 300 this.originalY = this.element.offsetTop - this.currentTop() - this.originalTop; 301 this.originalX = this.element.offsetLeft - this.currentLeft() - this.originalLeft; 302 this.offsetY = event.clientY - this.originalY - this.originalTop; 303 this.offsetX = event.clientX - this.originalX - this.originalLeft; 304 305 Event.stop(event); 306 } 307 }, 292 this.dragging = true; 293 294 if(this.options.zindex) { 295 this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); 296 this.element.style.zIndex = this.options.zindex; 297 } 298 299 if(this.options.ghosting) { 300 this._clone = this.element.cloneNode(true); 301 Position.absolutize(this.element); 302 this.element.parentNode.insertBefore(this._clone, this.element); 303 } 304 305 if(this.options.scroll) { 306 if (this.options.scroll == window) { 307 var where = this._getWindowScroll(this.options.scroll); 308 this.originalScrollLeft = where.left; 309 this.originalScrollTop = where.top; 310 } else { 311 this.originalScrollLeft = this.options.scroll.scrollLeft; 312 this.originalScrollTop = this.options.scroll.scrollTop; 313 } 314 } 315 316 Draggables.notify('onStart', this, event); 317 if(this.options.starteffect) this.options.starteffect(this.element); 318 }, 319 320 updateDrag: function(event, pointer) { 321 if(!this.dragging) this.startDrag(event); 322 Position.prepare(); 323 Droppables.show(pointer, this.element); 324 Draggables.notify('onDrag', this, event); 325 this.draw(pointer); 326 if(this.options.change) this.options.change(this); 327 328 if(this.options.scroll) { 329 this.stopScrolling(); 330 331 var p; 332 if (this.options.scroll == window) { 333 with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } 334 } else { 335 p = Position.page(this.options.scroll); 336 p[0] += this.options.scroll.scrollLeft; 337 p[1] += this.options.scroll.scrollTop; 338 p.push(p[0]+this.options.scroll.offsetWidth); 339 p.push(p[1]+this.options.scroll.offsetHeight); 340 } 341 var speed = [0,0]; 342 if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); 343 if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); 344 if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); 345 if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); 346 this.startScrolling(speed); 347 } 348 349 // fix AppleWebKit rendering 350 if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 351 352 Event.stop(event); 353 }, 354 308 355 finishDrag: function(event, success) { 309 this.active = false;310 356 this.dragging = false; 311 357 358 if(this.options.ghosting) { 359 Position.relativize(this.element); 360 Element.remove(this._clone); 361 this._clone = null; 362 } 363 312 364 if(success) Droppables.fire(event, this.element); 313 Draggables.notify('onEnd', this );314 365 Draggables.notify('onEnd', this, event); 366 315 367 var revert = this.options.revert; 316 368 if(revert && typeof revert == 'function') revert = revert(this.element); 317 369 370 var d = this.currentDelta(); 318 371 if(revert && this.options.reverteffect) { 319 372 this.options.reverteffect(this.element, 320 this.currentTop()-this.originalTop, 321 this.currentLeft()-this.originalLeft); 373 d[1]-this.delta[1], d[0]-this.delta[0]); 322 374 } else { 323 this. originalLeft = this.currentLeft();324 this.originalTop = this.currentTop();325 } 326 327 this.element.style.zIndex = this.originalZ;328 375 this.delta = d; 376 } 377 378 if(this.options.zindex) 379 this.element.style.zIndex = this.originalZ; 380 329 381 if(this.options.endeffect) 330 382 this.options.endeffect(this.element); 331 383 384 Draggables.deactivate(this); 332 385 Droppables.reset(); 333 386 }, 387 334 388 keyPress: function(event) { 335 if(this.active) { 336 if(event.keyCode==Event.KEY_ESC) { 337 this.finishDrag(event, false); 338 Event.stop(event); 339 } 340 } 341 }, 389 if(event.keyCode!=Event.KEY_ESC) return; 390 this.finishDrag(event, false); 391 Event.stop(event); 392 }, 393 342 394 endDrag: function(event) { 343 if(this.active && this.dragging) { 344 this.finishDrag(event, true); 345 Event.stop(event); 346 } 347 this.active = false; 348 this.dragging = false; 349 }, 350 draw: function(event) { 395 if(!this.dragging) return; 396 this.stopScrolling(); 397 this.finishDrag(event, true); 398 Event.stop(event); 399 }, 400 401 draw: function(point) { 402 var pos = Position.cumulativeOffset(this.element); 403 var d = this.currentDelta(); 404 pos[0] -= d[0]; pos[1] -= d[1]; 405 406 if(this.options.scroll && (this.options.scroll != window)) { 407 pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; 408 pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; 409 } 410 411 var p = [0,1].map(function(i){ 412 return (point[i]-pos[i]-this.offset[i]) 413 }.bind(this)); 414 415 if(this.options.snap) { 416 if(typeof this.options.snap == 'function') { 417 p = this.options.snap(p[0],p[1],this); 418 } else { 419 if(this.options.snap instanceof Array) { 420 p = p.map( function(v, i) { 421 return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) 422 } else { 423 p = p.map( function(v) { 424 return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) 425 } 426 }} 427 351 428 var style = this.element.style; 352 this.originalX = this.element.offsetLeft - this.currentLeft() - this.originalLeft;353 this.originalY = this.element.offsetTop - this.currentTop() - this.originalTop;354 429 if((!this.options.constraint) || (this.options.constraint=='horizontal')) 355 style.left = ((event.clientX - this.originalX) - this.offsetX)+ "px";430 style.left = p[0] + "px"; 356 431 if((!this.options.constraint) || (this.options.constraint=='vertical')) 357 style.top = ((event.clientY - this.originalY) - this.offsetY)+ "px";432 style.top = p[1] + "px"; 358 433 if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering 359 434 }, 360 update: function(event) { 361 if(this.active) { 362 if(!this.dragging) { 363 var style = this.element.style; 364 this.dragging = true; 365 if(style.position=="") style.position = "relative"; 366 style.zIndex = this.options.zindex; 367 Draggables.notify('onStart', this); 368 if(this.options.starteffect) this.options.starteffect(this.element); 369 } 370 371 Droppables.show(event, this.element); 372 this.draw(event); 373 if(this.options.change) this.options.change(this); 374 375 // fix AppleWebKit rendering 376 if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 377 378 Event.stop(event); 379 } 435 436 stopScrolling: function() { 437 if(this.scrollInterval) { 438 clearInterval(this.scrollInterval); 439 this.scrollInterval = null; 440 Draggables._lastScrollPointer = null; 441 } 442 }, 443 444 startScrolling: function(speed) { 445 this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; 446 this.lastScrolled = new Date(); 447 this.scrollInterval = setInterval(this.scroll.bind(this), 10); 448 }, 449 450 scroll: function() { 451 var current = new Date(); 452 var delta = current - this.lastScrolled; 453 this.lastScrolled = current; 454 if(this.options.scroll == window) { 455 with (this._getWindowScroll(this.options.scroll)) { 456 if (this.scrollSpeed[0] || this.scrollSpeed[1]) { 457 var d = delta / 1000; 458 this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); 459 } 460 } 461 } else { 462 this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; 463 this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; 464 } 465 466 Position.prepare(); 467 Droppables.show(Draggables._lastPointer, this.element); 468 Draggables.notify('onDrag', this); 469 Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); 470 Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; 471 Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; 472 if (Draggables._lastScrollPointer[0] < 0) 473 Draggables._lastScrollPointer[0] = 0; 474 if (Draggables._lastScrollPointer[1] < 0) 475 Draggables._lastScrollPointer[1] = 0; 476 this.draw(Draggables._lastScrollPointer); 477 478 if(this.options.change) this.options.change(this); 479 }, 480 481 _getWindowScroll: function(w) { 482 var T, L, W, H; 483 with (w.document) { 484 if (w.document.documentElement && documentElement.scrollTop) { 485 T = documentElement.scrollTop; 486 L = documentElement.scrollLeft; 487 } else if (w.document.body) { 488 T = body.scrollTop; 489 L = body.scrollLeft; 490 } 491 if (w.innerWidth) { 492 W = w.innerWidth; 493 H = w.innerHeight; 494 } else if (w.document.documentElement && documentElement.clientWidth) { 495 W = documentElement.clientWidth; 496 H = documentElement.clientHeight; 497 } else { 498 W = body.offsetWidth; 499 H = body.offsetHeight 500 } 501 } 502 return { top: T, left: L, width: W, height: H }; 380 503 } 381 504 } … … 383 506 /*--------------------------------------------------------------------------*/ 384 507 385 SortableObserver = Class.create();508 var SortableObserver = Class.create(); 386 509 SortableObserver.prototype = { 387 510 initialize: function(element, observer) { … … 390 513 this.lastValue = Sortable.serialize(this.element); 391 514 }, 515 392 516 onStart: function() { 393 517 this.lastValue = Sortable.serialize(this.element); 394 518 }, 395 onEnd: function() { 519 520 onEnd: function() { 521 Sortable.unmark(); 396 522 if(this.lastValue != Sortable.serialize(this.element)) 397 523 this.observer(this.element) … … 399 525 } 400 526 401 Sortable = { 402 sortables: new Array(), 403 options: function(element){ 404 var element = $(element); 405 for(var i=0;i<this.sortables.length;i++) 406 if(this.sortables[i].element == element) 407 return this.sortables[i]; 408 return null; 409 }, 527 var Sortable = { 528 sortables: {}, 529 530 _findRootElement: function(element) { 531 while (element.tagName != "BODY") { 532 if(element.id && Sortable.sortables[element.id]) return element; 533 element = element.parentNode; 534 } 535 }, 536 537 options: function(element) { 538 element = Sortable._findRootElement($(element)); 539 if(!element) return; 540 return Sortable.sortables[element.id]; 541 }, 542 410 543 destroy: function(element){ 411 var element = $(element); 412 for(var i=0;i<this.sortables.length;i++) { 413 if(this.sortables[i].element == element) { 414 var s = this.sortables[i]; 415 Draggables.removeObserver(s.element); 416 for(var j=0;j<s.droppables.length;j++) 417 Droppables.remove(s.droppables[j]); 418 for(var j=0;j<s.draggables.length;j++) 419 s.draggables[j].destroy(); 420 this.sortables.splice(i,1); 421 } 422 } 423 }, 544 var s = Sortable.options(element); 545 546 if(s) { 547 Draggables.removeObserver(s.element); 548 s.droppables.each(function(d){ Droppables.remove(d) }); 549 s.draggables.invoke('destroy'); 550 551 delete Sortable.sortables[s.element.id]; 552 } 553 }, 554 424 555 create: function(element) { 425 varelement = $(element);556 element = $(element); 426 557 var options = Object.extend({ 427 558 element: element, 428 559 tag: 'li', // assumes li children, override with tag: 'tagname' 560 dropOnEmpty: false, 561 tree: false, 562 treeTag: 'ul', 429 563 overlap: 'vertical', // one of 'vertical', 'horizontal' 430 564 constraint: 'vertical', // one of 'vertical', 'horizontal', false … … 433 567 only: false, 434 568 hoverclass: null, 435 onChange: function() {}, 436 onUpdate: function() {} 569 ghosting: false, 570 scroll: false, 571 scrollSensitivity: 20, 572 scrollSpeed: 15, 573 format: /^[^_]*_(.*)$/, 574 onChange: Prototype.emptyFunction, 575 onUpdate: Prototype.emptyFunction 437 576 }, arguments[1] || {}); 438 577 439 578 // clear any old sortable with same element 440 579 this.destroy(element); 441 580 442 581 // build options for the draggables 443 582 var options_for_draggable = { 444 583 revert: true, 584 scroll: options.scroll, 585 scrollSpeed: options.scrollSpeed, 586 scrollSensitivity: options.scrollSensitivity, 587 ghosting: options.ghosting, 445 588 constraint: options.constraint, 446 handle: handle }; 589 handle: options.handle }; 590 447 591 if(options.starteffect) 448 592 options_for_draggable.starteffect = options.starteffect; 593 449 594 if(options.reverteffect) 450 595 options_for_draggable.reverteffect = options.reverteffect; 596 else 597 if(options.ghosting) options_for_draggable.reverteffect = function(element) { 598 element.style.top = 0; 599 element.style.left = 0; 600 }; 601 451 602 if(options.endeffect) 452 603 options_for_draggable.endeffect = options.endeffect; 604 453 605 if(options.zindex) 454 606 options_for_draggable.zindex = options.zindex; 455 607 456 608 // build options for the droppables 457 609 var options_for_droppable = { 458 610 overlap: options.overlap, 459 611 containment: options.containment, 612 tree: options.tree, 460 613 hoverclass: options.hoverclass, 461 onHover: function(element, dropon, overlap) { 462 if(overlap>0.5) { 463 if(dropon.previousSibling != element) { 464 var oldParentNode = element.parentNode; 465 element.style.visibility = "hidden"; // fix gecko rendering 466 dropon.parentNode.insertBefore(element, dropon); 467 if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) 468 oldParentNode.sortable.onChange(element); 469 if(dropon.parentNode.sortable) 470 dropon.parentNode.sortable.onChange(element); 471 } 472 } else { 473 var nextElement = dropon.nextSibling || null; 474 if(nextElement != element) { 475 var oldParentNode = element.parentNode; 476 element.style.visibility = "hidden"; // fix gecko rendering 477 dropon.parentNode.insertBefore(element, nextElement); 478 if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) 479 oldParentNode.sortable.onChange(element); 480 if(dropon.parentNode.sortable) 481 dropon.parentNode.sortable.onChange(element); 614 onHover: Sortable.onHover 615 //greedy: !options.dropOnEmpty 616 } 617 618 var options_for_tree = { 619 onHover: Sortable.onEmptyHover, 620 overlap: options.overlap, 621 containment: options.containment, 622 hoverclass: options.hoverclass 623 } 624 625 // fix for gecko engine 626 Element.cleanWhitespace(element); 627 628 options.draggables = []; 629 options.droppables = []; 630 631 // drop on empty handling 632 if(options.dropOnEmpty || options.tree) { 633 Droppables.add(element, options_for_tree); 634 options.droppables.push(element); 635 } 636 637 (this.findElements(element, options) || []).each( function(e) { 638 // handles are per-draggable 639 var handle = options.handle ? 640 Element.childrenWithClassName(e, options.handle)[0] : e; 641 options.draggables.push( 642 new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); 643 Droppables.add(e, options_for_droppable); 644 if(options.tree) e.treeNode = element; 645 options.droppables.push(e); 646 }); 647 648 if(options.tree) { 649 (Sortable.findTreeElements(element, options) || []).each( function(e) { 650 Droppables.add(e, options_for_tree); 651 e.treeNode = element; 652 options.droppables.push(e); 653 }); 654 } 655 656 // keep reference 657 this.sortables[element.id] = options; 658 659 // for onupdate 660 Draggables.addObserver(new SortableObserver(element, options.onUpdate)); 661 662 }, 663 664 // return all suitable-for-sortable elements in a guaranteed order 665 findElements: function(element, options) { 666 return Element.findChildren( 667 element, options.only, options.tree ? true : false, options.tag); 668 }, 669 670 findTreeElements: function(element, options) { 671 return Element.findChildren( 672 element, options.only, options.tree ? true : false, options.treeTag); 673 }, 674 675 onHover: function(element, dropon, overlap) { 676 if(Element.isParent(dropon, element)) return; 677 678 if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { 679 return; 680 } else if(overlap>0.5) { 681 Sortable.mark(dropon, 'before'); 682 if(dropon.previousSibling != element) { 683 var oldParentNode = element.parentNode; 684 element.style.visibility = "hidden"; // fix gecko rendering 685 dropon.parentNode.insertBefore(element, dropon); 686 if(dropon.parentNode!=oldParentNode) 687 Sortable.options(oldParentNode).onChange(element); 688 Sortable.options(dropon.parentNode).onChange(element); 689 } 690 } else { 691 Sortable.mark(dropon, 'after'); 692 var nextElement = dropon.nextSibling || null; 693 if(nextElement != element) { 694 var oldParentNode = element.parentNode; 695 element.style.visibility = "hidden"; // fix gecko rendering 696 dropon.parentNode.insertBefore(element, nextElement); 697 if(dropon.parentNode!=oldParentNode) 698 Sortable.options(oldParentNode).onChange(element); 699 Sortable.options(dropon.parentNode).onChange(element); 700 } 701 } 702 }, 703 704 onEmptyHover: function(element, dropon, overlap) { 705 var oldParentNode = element.parentNode; 706 var droponOptions = Sortable.options(dropon); 707 708 if(!Element.isParent(dropon, element)) { 709 var index; 710 711 var children = Sortable.findElements(dropon, {tag: droponOptions.tag}); 712 var child = null; 713 714 if(children) { 715 var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); 716 717 for (index = 0; index < children.length; index += 1) { 718 if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { 719 offset -= Element.offsetSize (children[index], droponOptions.overlap); 720 } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { 721 child = index + 1 < children.length ? children[index + 1] : null; 722 break; 723 } else { 724 child = children[index]; 725 break; 482 726 } 483 727 } 484 728 } 485 } 486 487 // fix for gecko engine 488 Element.cleanWhitespace(element); 489 490 options.draggables = []; 491 options.droppables = []; 492 493 // make it so 494 var elements = element.childNodes; 495 for (var i = 0; i < elements.length; i++) 496 if(elements[i].tagName && elements[i].tagName==options.tag.toUpperCase() && 497 (!options.only || (Element.Class.has(elements[i], options.only)))) { 498 499 // handles are per-draggable 500 var handle = options.handle ? 501 Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i]; 502 503 options.draggables.push(new Draggable(elements[i], Object.extend(options_for_draggable, { handle: handle }))); 504 505 Droppables.add(elements[i], options_for_droppable); 506 options.droppables.push(elements[i]); 507 508 } 509 510 // keep reference 511 this.sortables.push(options); 512 513 // for onupdate 514 Draggables.addObserver(new SortableObserver(element, options.onUpdate)); 515 516 }, 517 serialize: function(element) { 518 var element = $(element); 729 730 dropon.insertBefore(element, child); 731 732 Sortable.options(oldParentNode).onChange(element); 733 droponOptions.onChange(element); 734 } 735 }, 736 737 unmark: function() { 738 if(Sortable._marker) Element.hide(Sortable._marker); 739 }, 740 741 mark: function(dropon, position) { 742 // mark on ghosting only 743 var sortable = Sortable.options(dropon.parentNode); 744 if(sortable && !sortable.ghosting) return; 745 746 if(!Sortable._marker) { 747 Sortable._marker = $('dropmarker') || document.createElement('DIV'); 748 Element.hide(Sortable._marker); 749 Element.addClassName(Sortable._marker, 'dropmarker'); 750 Sortable._marker.style.position = 'absolute'; 751 document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); 752 } 753 var offsets = Position.cumulativeOffset(dropon); 754 Sortable._marker.style.left = offsets[0] + 'px'; 755 Sortable._marker.style.top = offsets[1] + 'px'; 756 757 if(position=='after') 758 if(sortable.overlap == 'horizontal') 759 Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px'; 760 else 761 Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; 762 763 Element.show(Sortable._marker); 764 }, 765 766 _tree: function(element, options, parent) { 767 var children = Sortable.findElements(element, options) || []; 768 769 for (var i = 0; i < children.length; ++i) { 770 var match = children[i].id.match(options.format); 771 772 if (!match) continue; 773 774 var child = { 775 id: encodeURIComponent(match ? match[1] : null), 776 element: element, 777 parent: parent, 778 children: new Array, 779 position: parent.children.length, 780 container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase()) 781 } 782 783 /* Get the element containing the children and recurse over it */ 784 if (child.container) 785 this._tree(child.container, options, child) 786 787 parent.children.push (child); 788 } 789 790 return parent; 791 }, 792 793 /* Finds the first element of the given tag type within a parent element. 794 Used for finding the first LI[ST] within a L[IST]I[TEM].*/ 795 _findChildrenElement: function (element, containerTag) { 796 if (element && element.hasChildNodes) 797 for (var i = 0; i < element.childNodes.length; ++i) 798 if (element.childNodes[i].tagName == containerTag) 799 return element.childNodes[i]; 800 801 return null; 802 }, 803 804 tree: function(element) { 805 element = $(element); 519 806 var sortableOptions = this.options(element); 520 807 var options = Object.extend({ 521 tag: sortableOptions.tag, 808 tag: sortableOptions.tag, 809 treeTag: sortableOptions.treeTag, 522 810 only: sortableOptions.only, 523 name: element.id 811 name: element.id, 812 format: sortableOptions.format 524 813 }, arguments[1] || {}); 525 814 526 var items = $(element).childNodes; 527 var queryComponents = new Array(); 528 529 for(var i=0; i<items.length; i++) 530 if(items[i].tagName && items[i].tagName==options.tag.toUpperCase() && 531 (!options.only || (Element.Class.has(items[i], options.only)))) 532 queryComponents.push( 533 encodeURIComponent(options.name) + "[]=" + 534 encodeURIComponent(items[i].id.split("_")[1])); 535 536 return queryComponents.join("&"); 815 var root = { 816 id: null, 817 parent: null, 818 children: new Array, 819 container: element, 820 position: 0 821 } 822 823 return Sortable._tree (element, options, root); 824 }, 825 826 /* Construct a [i] index for a particular node */ 827 _constructIndex: function(node) { 828 var index = ''; 829 do { 830 if (node.id) index = '[' + node.position + ']' + index; 831 } while ((node = node.parent) != null); 832 return index; 833 }, 834 835 sequence: function(element) { 836 element = $(element); 837 var options = Object.extend(this.options(element), arguments[1] || {}); 838 839 return $(this.findElements(element, options) || []).map( function(item) { 840 return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; 841 }); 842 }, 843 844 setSequence: function(element, new_sequence) { 845 element = $(element); 846 var options = Object.extend(this.options(element), arguments[2] || {}); 847 848 var nodeMap = {}; 849 this.findElements(element, options).each( function(n) { 850 if (n.id.match(options.format)) 851 nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; 852 n.parentNode.removeChild(n); 853 }); 854 855 new_sequence.each(function(ident) { 856 var n = nodeMap[ident]; 857 if (n) { 858 n[1].appendChild(n[0]); 859 delete nodeMap[ident]; 860 } 861 }); 862 }, 863 864 serialize: function(element) { 865 element = $(element); 866 var options = Object.extend(Sortable.options(element), arguments[1] || {}); 867 var name = encodeURIComponent( 868 (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); 869 870 if (options.tree) { 871 return Sortable.tree(element, arguments[1]).children.map( function (item) { 872 return [name + Sortable._constructIndex(item) + "=" + 873 encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); 874 }).flatten().join('&'); 875 } else { 876 return Sortable.sequence(element, arguments[1]).map( function(item) { 877 return name + "[]=" + encodeURIComponent(item); 878 }).join('&'); 879 } 537 880 } 538 } 881 } 882 883 /* Returns true if child is contained within element */ 884 Element.isParent = function(child, element) { 885 if (!child.parentNode || child == element) return false; 886 887 if (child.parentNode == element) return true; 888 889 return Element.isParent(child.parentNode, element); 890 } 891 892 Element.findChildren = function(element, only, recursive, tagName) { 893 if(!element.hasChildNodes()) return null; 894 tagName = tagName.toUpperCase(); 895 if(only) only = [only].flatten(); 896 var elements = []; 897 $A(element.childNodes).each( function(e) { 898 if(e.tagName && e.tagName.toUpperCase()==tagName && 899 (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) 900 elements.push(e); 901 if(recursive) { 902 var grandchildren = Element.findChildren(e, only, recursive, tagName); 903 if(grandchildren) elements.push(grandchildren); 904 } 905 }); 906 907 return (elements.length>0 ? elements.flatten() : []); 908 } 909 910 Element.offsetSize = function (element, type) { 911 if (type == 'vertical' || type == 'height') 912 return element.offsetHeight; 913 else 914 return element.offsetWidth; 915 } -
trunk/trax/data/public/javascripts/effects.js
r182 r226 1 // $Id$2 1 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 3 // 4 // Parts (c) 2005 Justin Palmer (http://encytemedia.com/) 5 // Parts (c) 2005 Mark Pilgrim (http://diveintomark.org/) 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining 8 // a copy of this software and associated documentation files (the 9 // "Software"), to deal in the Software without restriction, including 10 // without limitation the rights to use, copy, modify, merge, publish, 11 // distribute, sublicense, and/or sell copies of the Software, and to 12 // permit persons to whom the Software is furnished to do so, subject to 13 // the following conditions: 14 // 15 // The above copyright notice and this permission notice shall be 16 // included in all copies or substantial portions of the Software. 17 // 18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 22 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 24 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 26 27 Effect = {} 28 Effect2 = Effect; // deprecated 2 // Contributors: 3 // Justin Palmer (http://encytemedia.com/) 4 // Mark Pilgrim (http://diveintomark.org/) 5 // Martin Bialasinki 6 // 7 // See scriptaculous.js for full license. 8 9 // converts rgb() and #xxx to #xxxxxx format, 10 // returns self (or first argument) if not convertable 11 String.prototype.parseColor = function() { 12 var color = '#'; 13 if(this.slice(0,4) == 'rgb(') { 14 var cols = this.slice(4,this.length-1).split(','); 15 var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); 16 } else { 17 if(this.slice(0,1) == '#') { 18 if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); 19 if(this.length==7) color = this.toLowerCase(); 20 } 21 } 22 return(color.length==7 ? color : (arguments[0] || this)); 23 } 24 25 /*--------------------------------------------------------------------------*/ 26 27 Element.collectTextNodes = function(element) { 28 return $A($(element).childNodes).collect( function(node) { 29 return (node.nodeType==3 ? node.nodeValue : 30 (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); 31 }).flatten().join(''); 32 } 33 34 Element.collectTextNodesIgnoreClass = function(element, className) { 35 return $A($(element).childNodes).collect( function(node) { 36 return (node.nodeType==3 ? node.nodeValue : 37 ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 38 Element.collectTextNodesIgnoreClass(node, className) : '')); 39 }).flatten().join(''); 40 } 41 42 Element.setContentZoom = function(element, percent) { 43 element = $(element); 44 Element.setStyle(element, {fontSize: (percent/100) + 'em'}); 45 if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 46 } 47 48 Element.getOpacity = function(element){ 49 var opacity; 50 if (opacity = Element.getStyle(element, 'opacity')) 51 return parseFloat(opacity); 52 if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/)) 53 if(opacity[1]) return parseFloat(opacity[1]) / 100; 54 return 1.0; 55 } 56 57 Element.setOpacity = function(element, value){ 58 element= $(element); 59 if (value == 1){ 60 Element.setStyle(element, { opacity: 61 (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 62 0.999999 : null }); 63 if(/MSIE/.test(navigator.userAgent)) 64 Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')}); 65 } else { 66 if(value < 0.00001) value = 0; 67 Element.setStyle(element, {opacity: value}); 68 if(/MSIE/.test(navigator.userAgent)) 69 Element.setStyle(element, 70 { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + 71 'alpha(opacity='+value*100+')' }); 72 } 73 } 74 75 Element.getInlineOpacity = function(element){ 76 return $(element).style.opacity || ''; 77 } 78 79 Element.childrenWithClassName = function(element, className, findFirst) { 80 var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)"); 81 var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) { 82 return (c.className && c.className.match(classNameRegExp)); 83 }); 84 if(!results) results = []; 85 return results; 86 } 87 88 Element.forceRerendering = function(element) { 89 try { 90 element = $(element); 91 var n = document.createTextNode(' '); 92 element.appendChild(n); 93 element.removeChild(n); 94 } catch(e) { } 95 }; 96 97 /*--------------------------------------------------------------------------*/ 98 99 Array.prototype.call = function() { 100 var args = arguments; 101 this.each(function(f){ f.apply(this, args) }); 102 } 103 104 /*--------------------------------------------------------------------------*/ 105 106 var Effect = { 107 tagifyText: function(element) { 108 var tagifyStyle = 'position:relative'; 109 if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1'; 110 element = $(element); 111 $A(element.childNodes).each( function(child) { 112 if(child.nodeType==3) { 113 child.nodeValue.toArray().each( function(character) { 114 element.insertBefore( 115 Builder.node('span',{style: tagifyStyle}, 116 character == ' ' ? String.fromCharCode(160) : character), 117 child); 118 }); 119 Element.remove(child); 120 } 121 }); 122 }, 123 multiple: function(element, effect) { 124 var elements; 125 if(((typeof element == 'object') || 126 (typeof element == 'function')) && 127 (element.length)) 128 elements = element; 129 else 130 elements = $(element).childNodes; 131 132 var options = Object.extend({ 133 speed: 0.1, 134 delay: 0.0 135 }, arguments[2] || {}); 136 var masterDelay = options.delay; 137 138 $A(elements).each( function(element, index) { 139 new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); 140 }); 141 }, 142 PAIRS: { 143 'slide': ['SlideDown','SlideUp'], 144 'blind': ['BlindDown','BlindUp'], 145 'appear': ['Appear','Fade'] 146 }, 147 toggle: function(element, effect) { 148 element = $(element); 149 effect = (effect || 'appear').toLowerCase(); 150 var options = Object.extend({ 151 queue: { position:'end', scope:(element.id || 'global'), limit: 1 } 152 }, arguments[2] || {}); 153 Effect[element.visible() ? 154 Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); 155 } 156 }; 157 158 var Effect2 = Effect; // deprecated 29 159 30 160 /* ------------- transitions ------------- */ … … 42 172 } 43 173 Effect.Transitions.flicker = function(pos) { 44 return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random( 0.25);174 return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; 45 175 } 46 176 Effect.Transitions.wobble = function(pos) { … … 58 188 } 59 189 60 /* ------------- element ext -------------- */61 62 Element.makePositioned = function(element) {63 element = $(element);64 if(element.style.position == "")65 element.style.position = "relative";66 }67 68 Element.makeClipping = function(element) {69 element = $(element);70 element._overflow = element.style.overflow || 'visible';71 if(element._overflow!='hidden') element.style.overflow = 'hidden';72 }73 74 Element.undoClipping = function(element) {75 element = $(element);76 if(element._overflow!='hidden') element.style.overflow = element._overflow;77 }78 79 190 /* ------------- core effects ------------- */ 191 192 Effect.ScopedQueue = Class.create(); 193 Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { 194 initialize: function() { 195 this.effects = []; 196 this.interval = null; 197 }, 198 _each: function(iterator) { 199 this.effects._each(iterator); 200 }, 201 add: function(effect) { 202 var timestamp = new Date().getTime(); 203 204 var position = (typeof effect.options.queue == 'string') ? 205 effect.options.queue : effect.options.queue.position; 206 207 switch(position) { 208 case 'front': 209 // move unstarted effects after this effect 210 this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { 211 e.startOn += effect.finishOn; 212 e.finishOn += effect.finishOn; 213 }); 214 break; 215 case 'end': 216 // start effect after last queued effect has finished 217 timestamp = this.effects.pluck('finishOn').max() || timestamp; 218 break; 219 } 220 221 effect.startOn += timestamp; 222 effect.finishOn += timestamp; 223 224 if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) 225 this.effects.push(effect); 226 227 if(!this.interval) 228 this.interval = setInterval(this.loop.bind(this), 40); 229 }, 230 remove: function(effect) { 231 this.effects = this.effects.reject(function(e) { return e==effect }); 232 if(this.effects.length == 0) { 233 clearInterval(this.interval); 234 this.interval = null; 235 } 236 }, 237 loop: function() { 238 var timePos = new Date().getTime(); 239 this.effects.invoke('loop', timePos); 240 } 241 }); 242 243 Effect.Queues = { 244 instances: $H(), 245 get: function(queueName) { 246 if(typeof queueName != 'string') return queueName; 247 248 if(!this.instances[queueName]) 249 this.instances[queueName] = new Effect.ScopedQueue(); 250 251 return this.instances[queueName]; 252 } 253 } 254 Effect.Queue = Effect.Queues.get('global'); 255 256 Effect.DefaultOptions = { 257 transition: Effect.Transitions.sinoidal, 258 duration: 1.0, // seconds 259 fps: 25.0, // max. 25fps due to Effect.Queue implementation 260 sync: false, // true for combining 261 from: 0.0, 262 to: 1.0, 263 delay: 0.0, 264 queue: 'parallel' 265 } 80 266 81 267 Effect.Base = function() {}; 82 268 Effect.Base.prototype = { 83 setOptions: function(options) { 84 this.options = Object.extend({ 85 transition: Effect.Transitions.sinoidal, 86 duration: 1.0, // seconds 87 fps: 25.0, // max. 100fps 88 sync: false, // true for combining 89 from: 0.0, 90 to: 1.0 91 }, options || {}); 92 }, 269 position: null, 93 270 start: function(options) { 94 this. setOptions(options || {});271 this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {}); 95 272 this.currentFrame = 0; 96 this.startOn = new Date().getTime(); 273 this.state = 'idle'; 274 this.startOn = this.options.delay*1000; 97 275 this.finishOn = this.startOn + (this.options.duration*1000); 98 if(this.options.beforeStart) this.options.beforeStart(this); 99 if(!this.options.sync) this.loop(); 100 }, 101 loop: function() { 102 var timePos = new Date().getTime(); 103 if(timePos >= this.finishOn) { 104 this.render(1.0); 105 if(this.finish) this.finish(); 106 if(this.options.afterFinish) this.options.afterFinish(this); 107 return; 276 this.event('beforeStart'); 277 if(!this.options.sync) 278 Effect.Queues.get(typeof this.options.queue == 'string' ? 279 'global' : this.options.queue.scope).add(this); 280 }, 281 loop: function(timePos) { 282 if(timePos >= this.startOn) { 283 if(timePos >= this.finishOn) { 284 this.render(1.0); 285 this.cancel(); 286 this.event('beforeFinish'); 287 if(this.finish) this.finish(); 288 this.event('afterFinish'); 289 return; 290 } 291 var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); 292 var frame = Math.round(pos * this.options.fps * this.options.duration); 293 if(frame > this.currentFrame) { 294 this.render(pos); 295 this.currentFrame = frame; 296 } 108 297 } 109 var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); 110 var frame = Math.round(pos * this.options.fps * this.options.duration); 111 if(frame > this.currentFrame) { 112 this.render(pos); 113 this.currentFrame = frame; 298 }, 299 render: function(pos) { 300 if(this.state == 'idle') { 301 this.state = 'running'; 302 this.event('beforeSetup'); 303 if(this.setup) this.setup(); 304 this.event('afterSetup'); 114 305 } 115 this.timeout = setTimeout(this.loop.bind(this), 10);116 },117 render: function(pos) {118 if(this.options.transition) pos = this.options.transition(pos);119 pos *= (this.options.to-this.options.from);120 pos += this.options.from;121 if(this.options.beforeUpdate) this.options.beforeUpdate(this);122 if(this.update) this.update(pos);123 if(this.options.afterUpdate) this.options.afterUpdate(this);306 if(this.state == 'running') { 307 if(this.options.transition) pos = this.options.transition(pos); 308 pos *= (this.options.to-this.options.from); 309 pos += this.options.from; 310 this.position = pos; 311 this.event('beforeUpdate'); 312 if(this.update) this.update(pos); 313 this.event('afterUpdate'); 314 } 124 315 }, 125 316 cancel: function() { 126 if(this.timeout) clearTimeout(this.timeout); 317 if(!this.options.sync) 318 Effect.Queues.get(typeof this.options.queue == 'string' ? 319 'global' : this.options.queue.scope).remove(this); 320 this.state = 'finished'; 321 }, 322 event: function(eventName) { 323 if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); 324 if(this.options[eventName]) this.options[eventName](this); 325 }, 326 inspect: function() { 327 return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>'; 127 328 } 128 329 } … … 135 336 }, 136 337 update: function(position) { 137 for (var i = 0; i < this.effects.length; i++) 138 this.effects[i].render(position); 338 this.effects.invoke('render', position); 139 339 }, 140 340 finish: function(position) { 141 for (var i = 0; i < this.effects.length; i++) 142 if(this.effects[i].finish) this.effects[i].finish(position); 341 this.effects.each( function(effect) { 342 effect.render(1.0); 343 effect.cancel(); 344 effect.event('beforeFinish'); 345 if(effect.finish) effect.finish(position); 346 effect.event('afterFinish'); 347 }); 143 348 } 144 349 }); 145 350 146 // Internet Explorer caveat: works only on elements the have147 // a 'layout', meaning having a given width or height.148 // There is no way to safely set this automatically.149 351 Effect.Opacity = Class.create(); 150 352 Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { 151 353 initialize: function(element) { 152 354 this.element = $(element); 153 options = Object.extend({ 154 from: 0.0, 355 // make this work on IE on elements without 'layout' 356 if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout)) 357 this.element.setStyle({zoom: 1}); 358 var options = Object.extend({ 359 from: this.element.getOpacity() || 0.0, 155 360 to: 1.0 156 361 }, arguments[1] || {}); … … 158 363 }, 159 364 update: function(position) { 160 this.setOpacity(position); 161 }, 162 setOpacity: function(opacity) { 163 opacity = (opacity == 1) ? 0.99999 : opacity; 164 this.element.style.opacity = opacity; 165 this.element.style.filter = "alpha(opacity:"+opacity*100+")"; 365 this.element.setOpacity(position); 166 366 } 167 367 }); 168 368 169 Effect.MoveBy = Class.create(); 170 Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), { 171 initialize: function(element, toTop, toLeft) { 172 this.element = $(element); 173 this.originalTop = parseFloat(this.element.style.top || '0'); 174 this.originalLeft = parseFloat(this.element.style.left || '0'); 175 this.toTop = toTop; 176 this.toLeft = toLeft; 177 Element.makePositioned(this.element); 178 this.start(arguments[3]); 369 Effect.Move = Class.create(); 370 Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { 371 initialize: function(element) { 372 this.element = $(element); 373 var options = Object.extend({ 374 x: 0, 375 y: 0, 376 mode: 'relative' 377 }, arguments[1] || {}); 378 this.start(options); 379 }, 380 setup: function() { 381 // Bug in Opera: Opera returns the "real" position of a static element or 382 // relative element that does not have top/left explicitly set. 383 // ==> Always set top and left for position relative elements in your stylesheets 384 // (to 0 if you do not need them) 385 this.element.makePositioned(); 386 this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); 387 this.originalTop = parseFloat(this.element.getStyle('top') || '0'); 388 if(this.options.mode == 'absolute') { 389 // absolute movement, so we need to calc deltaX and deltaY 390 this.options.x = this.options.x - this.originalLeft; 391 this.options.y = this.options.y - this.originalTop; 392 } 179 393 }, 180 394 update: function(position) { 181 topd = this.toTop * position + this.originalTop; 182 leftd = this.toLeft * position + this.originalLeft; 183 this.setPosition(topd, leftd); 184 }, 185 setPosition: function(topd, leftd) { 186 this.element.style.top = topd + "px"; 187 this.element.style.left = leftd + "px"; 395 this.element.setStyle({ 396 left: this.options.x * position + this.originalLeft + 'px', 397 top: this.options.y * position + this.originalTop + 'px' 398 }); 188 399 } 189 400 }); 401 402 // for backwards compatibility 403 Effect.MoveBy = function(element, toTop, toLeft) { 404 return new Effect.Move(element, 405 Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); 406 }; 190 407 191 408 Effect.Scale = Class.create(); … … 193 410 initialize: function(element, percent) { 194 411 this.element = $(element) 195 options = Object.extend({412 var options = Object.extend({ 196 413 scaleX: true, 197 414 scaleY: true, … … 199 416 scaleFromCenter: false, 200 417 scaleMode: 'box', // 'box' or 'contents' or {} with provided values 201 scaleFrom: 100.0 418 scaleFrom: 100.0, 419 scaleTo: percent 202 420 }, arguments[2] || {}); 203 this.originalTop = this.element.offsetTop;204 this.originalLeft = this.element.offsetLeft;205 if(this.element.style.fontSize=="") this.sizeEm = 1.0;206 if(this.element.style.fontSize && this.element.style.fontSize.indexOf("em")>0)207 this.sizeEm = parseFloat(this.element.style.fontSize);208 this.factor = (percent/100.0) - (options.scaleFrom/100.0);209 if(options.scaleMode=='box') {210 this.originalHeight = this.element.clientHeight;211 this.originalWidth = this.element.clientWidth;212 } else213 if(options.scaleMode=='contents') {214 this.originalHeight = this.element.scrollHeight;215 this.originalWidth = this.element.scrollWidth;216 } else {217 this.originalHeight = options.scaleMode.originalHeight;218 this.originalWidth = options.scaleMode.originalWidth;219 }220 421 this.start(options); 221 422 }, 222 423 setup: function() { 424 this.restoreAfterFinish = this.options.restoreAfterFinish || false; 425 this.elementPositioning = this.element.getStyle('position'); 426 427 this.originalStyle = {}; 428 ['top','left','width','height','fontSize'].each( function(k) { 429 this.originalStyle[k] = this.element.style[k]; 430 }.bind(this)); 431 432 this.originalTop = this.element.offsetTop; 433 this.originalLeft = this.element.offsetLeft; 434 435 var fontSize = this.element.getStyle('font-size') || '100%'; 436 ['em','px','%'].each( function(fontSizeType) { 437 if(fontSize.indexOf(fontSizeType)>0) { 438 this.fontSize = parseFloat(fontSize); 439 this.fontSizeType = fontSizeType; 440 } 441 }.bind(this)); 442 443 this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; 444 445 this.dims = null; 446 if(this.options.scaleMode=='box') 447 this.dims = [this.element.offsetHeight, this.element.offsetWidth]; 448 if(/^content/.test(this.options.scaleMode)) 449 this.dims = [this.element.scrollHeight, this.element.scrollWidth]; 450 if(!this.dims) 451 this.dims = [this.options.scaleMode.originalHeight, 452 this.options.scaleMode.originalWidth]; 453 }, 223 454 update: function(position) { 224 currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); 225 if(this.options.scaleContent && this.sizeEm) 226 this.element.style.fontSize = this.sizeEm*currentScale + "em"; 227 this.setDimensions( 228 this.originalWidth * currentScale, 229 this.originalHeight * currentScale); 230 }, 231 232 setDimensions: function(width, height) { 233 if(this.options.scaleX) this.element.style.width = width + 'px'; 234 if(this.options.scaleY) this.element.style.height = height + 'px'; 455 var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); 456 if(this.options.scaleContent && this.fontSize) 457 this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); 458 this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); 459 }, 460 finish: function(position) { 461 if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); 462 }, 463 setDimensions: function(height, width) { 464 var d = {}; 465 if(this.options.scaleX) d.width = width + 'px'; 466 if(this.options.scaleY) d.height = height + 'px'; 235 467 if(this.options.scaleFromCenter) { 236 topd = (height - this.originalHeight)/2;237 leftd = (width - this.originalWidth)/2;238 if(this.element .style.position=='absolute') {239 if(this.options.scaleY) this.element.style.top = this.originalTop-topd + "px";240 if(this.options.scaleX) this.element.style.left = this.originalLeft-leftd + "px";468 var topd = (height - this.dims[0])/2; 469 var leftd = (width - this.dims[1])/2; 470 if(this.elementPositioning == 'absolute') { 471 if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; 472 if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; 241 473 } else { 242 if(this.options.scaleY) this.element.style.top = -topd + "px";243 if(this.options.scaleX) this.element.style.left = -leftd + "px";474 if(this.options.scaleY) d.top = -topd + 'px'; 475 if(this.options.scaleX) d.left = -leftd + 'px'; 244 476 } 245 477 } 478 this.element.setStyle(d); 246 479 } 247 480 }); … … 251 484 initialize: function(element) { 252 485 this.element = $(element); 253 254 // try to parse current background color as default for endcolor 255 // browser stores this as: "rgb(255, 255, 255)", convert to "#ffffff" format 256 var endcolor = "#ffffff"; 257 var current = this.element.style.backgroundColor; 258 if(current && current.slice(0,4) == "rgb(") { 259 endcolor = "#"; 260 var cols = current.slice(4,current.length-1).split(','); 261 var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3); } 262 263 var options = Object.extend({ 264 startcolor: "#ffff99", 265 endcolor: endcolor, 266 restorecolor: current 267 }, arguments[1] || {}); 268 486 var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); 487 this.start(options); 488 }, 489 setup: function() { 490 // Prevent executing on elements not in the layout flow 491 if(this.element.getStyle('display')=='none') { this.cancel(); return; } 492 // Disable background image during the effect 493 this.oldStyle = { 494 backgroundImage: this.element.getStyle('background-image') }; 495 this.element.setStyle({backgroundImage: 'none'}); 496 if(!this.options.endcolor) 497 this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); 498 if(!this.options.restorecolor) 499 this.options.restorecolor = this.element.getStyle('background-color'); 269 500 // init color calculations 270 this.colors_base = [ 271 parseInt(options.startcolor.slice(1,3),16), 272 parseInt(options.startcolor.slice(3,5),16), 273 parseInt(options.startcolor.slice(5),16) ]; 274 this.colors_delta = [ 275 parseInt(options.endcolor.slice(1,3),16)-this.colors_base[0], 276 parseInt(options.endcolor.slice(3,5),16)-this.colors_base[1], 277 parseInt(options.endcolor.slice(5),16)-this.colors_base[2] ]; 278 279 this.start(options); 501 this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); 502 this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); 280 503 }, 281 504 update: function(position) { 282 var colors = [ 283 Math.round(this.colors_base[0]+(this.colors_delta[0]*position)), 284 Math.round(this.colors_base[1]+(this.colors_delta[1]*position)), 285 Math.round(this.colors_base[2]+(this.colors_delta[2]*position)) ]; 286 this.element.style.backgroundColor = "#" + 287 colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart(); 505 this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ 506 return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); 288 507 }, 289 508 finish: function() { 290 this.element.style.backgroundColor = this.options.restorecolor; 509 this.element.setStyle(Object.extend(this.oldStyle, { 510 backgroundColor: this.options.restorecolor 511 })); 291 512 } 292 513 }); … … 296 517 initialize: function(element) { 297 518 this.element = $(element); 519 this.start(arguments[1] || {}); 520 }, 521 setup: function() { 298 522 Position.prepare(); 299 523 var offsets = Position.cumulativeOffset(this.element); 524 if(this.options.offset) offsets[1] += this.options.offset; 300 525 var max = window.innerHeight ? 301 526 window.height - window.innerHeight : … … 304 529 document.documentElement.clientHeight : document.body.clientHeight); 305 530 this.scrollStart = Position.deltaY; 306 this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; 307 this.start(arguments[1] || {}); 531 this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; 308 532 }, 309 533 update: function(position) { … … 314 538 }); 315 539 316 /* ------------- prepackagedeffects ------------- */540 /* ------------- combination effects ------------- */ 317 541 318 542 Effect.Fade = function(element) { 319 options = Object.extend({ 320 from: 1.0, 543 element = $(element); 544 var oldOpacity = element.getInlineOpacity(); 545 var options = Object.extend({ 546 from: element.getOpacity() || 1.0, 321 547 to: 0.0, 322 afterFinish: function(effect) 323 { Element.hide(effect.element); 324 effect.setOpacity(1); } 325 }, arguments[1] || {}); 548 afterFinishInternal: function(effect) { 549 if(effect.options.to!=0) return; 550 effect.element.hide(); 551 effect.element.setStyle({opacity: oldOpacity}); 552 }}, arguments[1] || {}); 326 553 return new Effect.Opacity(element,options); 327 554 } 328 555 329 556 Effect.Appear = function(element) { 330 options = Object.extend({ 331 from: 0.0, 557 element = $(element); 558 var options = Object.extend({ 559 from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), 332 560 to: 1.0, 333 beforeStart: function(effect) 334 { effect.setOpacity(0); 335 Element.show(effect.element); }, 336 afterUpdate: function(effect) 337 { Element.show(effect.element); } 338 }, arguments[1] || {}); 561 // force Safari to render floated elements properly 562 afterFinishInternal: function(effect) { 563 effect.element.forceRerendering(); 564 }, 565 beforeSetup: function(effect) { 566 effect.element.setOpacity(effect.options.from); 567 effect.element.show(); 568 }}, arguments[1] || {}); 339 569 return new Effect.Opacity(element,options); 340 570 } 341 571 342 572 Effect.Puff = function(element) { 573 element = $(element); 574 var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position') }; 343 575 return new Effect.Parallel( 344 [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true }), 345 new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], 346 { duration: 1.0, 347 afterUpdate: function(effect) 348 { effect.effects[0].element.style.position = 'absolute'; }, 349 afterFinish: function(effect) 350 { Element.hide(effect.effects[0].element); } 351 } 576 [ new Effect.Scale(element, 200, 577 { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 578 new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 579 Object.extend({ duration: 1.0, 580 beforeSetupInternal: function(effect) { 581 effect.effects[0].element.setStyle({position: 'absolute'}); }, 582 afterFinishInternal: function(effect) { 583 effect.effects[0].element.hide(); 584 effect.effects[0].element.setStyle(oldStyle); } 585 }, arguments[1] || {}) 352 586 ); 353 587 } 354 588 355 589 Effect.BlindUp = function(element) { 356 Element.makeClipping(element); 590 element = $(element); 591 element.makeClipping(); 357 592 return new Effect.Scale(element, 0, 358 593 Object.extend({ scaleContent: false, 359 594 scaleX: false, 360 afterFinish: function(effect)361 {362 Element.hide(effect.element);363 Element.undoClipping(effect.element);364 }595 restoreAfterFinish: true, 596 afterFinishInternal: function(effect) { 597 effect.element.hide(); 598 effect.element.undoClipping(); 599 } 365 600 }, arguments[1] || {}) 366 601 ); … … 368 603 369 604 Effect.BlindDown = function(element) { 370 $(element).style.height = '0px'; 371 Element.makeClipping(element); 372 Element.show(element); 605 element = $(element); 606 var elementDimensions = element.getDimensions(); 373 607 return new Effect.Scale(element, 100, 374 608 Object.extend({ scaleContent: false, 375 scaleX: false, 376 scaleMode: 'contents', 609 scaleX: false, 377 610 scaleFrom: 0, 378 afterFinish: function(effect) { 379 Element.undoClipping(effect.element); 611 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, 612 restoreAfterFinish: true, 613 afterSetup: function(effect) { 614 effect.element.makeClipping(); 615 effect.element.setStyle({height: '0px'}); 616 effect.element.show(); 617 }, 618 afterFinishInternal: function(effect) { 619 effect.element.undoClipping(); 380 620 } 381 621 }, arguments[1] || {}) … … 384 624 385 625 Effect.SwitchOff = function(element) { 386 return new Effect.Appear(element, 387 { duration: 0.4, 388 transition: Effect.Transitions.flicker, 389 afterFinish: function(effect) 390 { effect.element.style.overflow = 'hidden'; 391 new Effect.Scale(effect.element, 1, 392 { duration: 0.3, scaleFromCenter: true, 393 scaleX: false, scaleContent: false, 394 afterUpdate: function(effect) { 395 if(effect.element.style.position=="") 396 effect.element.style.position = 'relative'; }, 397 afterFinish: function(effect) { Element.hide(effect.element); } 398 } ) 626 element = $(element); 627 var oldOpacity = element.getInlineOpacity(); 628 return new Effect.Appear(element, { 629 duration: 0.4, 630 from: 0, 631 transition: Effect.Transitions.flicker, 632 afterFinishInternal: function(effect) { 633 new Effect.Scale(effect.element, 1, { 634 duration: 0.3, scaleFromCenter: true, 635 scaleX: false, scaleContent: false, restoreAfterFinish: true, 636 beforeSetup: function(effect) { 637 effect.element.makePositioned(); 638 effect.element.makeClipping(); 639 }, 640 afterFinishInternal: function(effect) { 641 effect.element.hide(); 642 effect.element.undoClipping(); 643 effect.element.undoPositioned(); 644 effect.element.setStyle({opacity: oldOpacity}); 645 } 646 }) 647 } 648 }); 649 } 650 651 Effect.DropOut = function(element) { 652 element = $(element); 653 var oldStyle = { 654 top: element.getStyle('top'), 655 left: element.getStyle('left'), 656 opacity: element.getInlineOpacity() }; 657 return new Effect.Parallel( 658 [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 659 new Effect.Opacity(element, { sync: true, to: 0.0 }) ], 660 Object.extend( 661 { duration: 0.5, 662 beforeSetup: function(effect) { 663 effect.effects[0].element.makePositioned(); 664 }, 665 afterFinishInternal: function(effect) { 666 effect.effects[0].element.hide(); 667 effect.effects[0].element.undoPositioned(); 668 effect.effects[0].element.setStyle(oldStyle); 669 } 670 }, arguments[1] || {})); 671 } 672 673 Effect.Shake = function(element) { 674 element = $(element); 675 var oldStyle = { 676 top: element.getStyle('top'), 677 left: element.getStyle('left') }; 678 return new Effect.Move(element, 679 { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { 680 new Effect.Move(effect.element, 681 { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { 682 new Effect.Move(effect.element, 683 { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { 684 new Effect.Move(effect.element, 685 { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { 686 new Effect.Move(effect.element, 687 { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { 688 new Effect.Move(effect.element, 689 { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { 690 effect.element.undoPositioned(); 691 effect.element.setStyle(oldStyle); 692 }}) }}) }}) }}) }}) }}); 693 } 694 695 Effect.SlideDown = function(element) { 696 element = $(element); 697 element.cleanWhitespace(); 698 // SlideDown need to have the content of the element wrapped in a container element with fixed height! 699 var oldInnerBottom = $(element.firstChild).getStyle('bottom'); 700 var elementDimensions = element.getDimensions(); 701 return new Effect.Scale(element, 100, Object.extend({ 702 scaleContent: false, 703 scaleX: false, 704 scaleFrom: window.opera ? 0 : 1, 705 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, 706 restoreAfterFinish: true, 707 afterSetup: function(effect) { 708 effect.element.makePositioned(); 709 effect.element.firstChild.makePositioned(); 710 if(window.opera) effect.element.setStyle({top: ''}); 711 effect.element.makeClipping(); 712 effect.element.setStyle({height: '0px'}); 713 effect.element.show(); }, 714 afterUpdateInternal: function(effect) { 715 effect.element.firstChild.setStyle({bottom: 716 (effect.dims[0] - effect.element.clientHeight) + 'px' }); 717 }, 718 afterFinishInternal: function(effect) { 719 effect.element.undoClipping(); 720 // IE will crash if child is undoPositioned first 721 if(/MSIE/.test(navigator.userAgent)){ 722 effect.element.undoPositioned(); 723 effect.element.firstChild.undoPositioned(); 724 }else{ 725 effect.element.firstChild.undoPositioned(); 726 effect.element.undoPositioned(); 399 727 } 400 } ); 401 } 402 403 Effect.DropOut = function(element) { 404 return new Effect.Parallel( 405 [ new Effect.MoveBy(element, 100, 0, { sync: true }), 406 new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], 407 { duration: 0.5, 408 afterFinish: function(effect) 409 { Element.hide(effect.effects[0].element); } 410 }); 411 } 412 413 Effect.Shake = function(element) { 414 return new Effect.MoveBy(element, 0, 20, 415 { duration: 0.05, afterFinish: function(effect) { 416 new Effect.MoveBy(effect.element, 0, -40, 417 { duration: 0.1, afterFinish: function(effect) { 418 new Effect.MoveBy(effect.element, 0, 40, 419 { duration: 0.1, afterFinish: function(effect) { 420 new Effect.MoveBy(effect.element, 0, -40, 421 { duration: 0.1, afterFinish: function(effect) { 422 new Effect.MoveBy(effect.element, 0, 40, 423 { duration: 0.1, afterFinish: function(effect) { 424 new Effect.MoveBy(effect.element, 0, -20, 425 { duration: 0.05, afterFinish: function(effect) { 426 }}) }}) }}) }}) }}) }}); 427 } 428 429 Effect.SlideDown = function(element) { 430 element = $(element); 431 element.style.height = '0px'; 432 Element.makeClipping(element); 433 Element.cleanWhitespace(element); 434 Element.makePositioned(element.firstChild); 435 Element.show(element); 436 return new Effect.Scale(element, 100, 728 effect.element.firstChild.setStyle({bottom: oldInnerBottom}); } 729 }, arguments[1] || {}) 730 ); 731 } 732 733 Effect.SlideUp = function(element) { 734 element = $(element); 735 element.cleanWhitespace(); 736 var oldInnerBottom = $(element.firstChild).getStyle('bottom'); 737 return new Effect.Scale(element, window.opera ? 0 : 1, 437 738 Object.extend({ scaleContent: false, 438 739 scaleX: false, 439 scaleMode: 'contents', 440 scaleFrom: 0, 441 afterUpdate: function(effect) 442 { effect.element.firstChild.style.bottom = 443 (effect.originalHeight - effect.element.clientHeight) + 'px'; }, 444 afterFinish: function(effect) 445 { Element.undoClipping(effect.element); } 446 }, arguments[1] || {}) 447 ); 448 } 449 450 Effect.SlideUp = function(element) { 451 element = $(element); 452 Element.makeClipping(element); 453 Element.cleanWhitespace(element); 454 Element.makePositioned(element.firstChild); 455 Element.show(element); 456 return new Effect.Scale(element, 0, 457 Object.extend({ scaleContent: false, 458 scaleX: false, 459 afterUpdate: function(effect) 460 { effect.element.firstChild.style.bottom = 461 (effect.originalHeight - effect.element.clientHeight) + 'px'; }, 462 afterFinish: function(effect) 463 { 464 Element.hide(effect.element); 465 Element.undoClipping(effect.element); 466 } 740 scaleMode: 'box', 741 scaleFrom: 100, 742 restoreAfterFinish: true, 743 beforeStartInternal: function(effect) { 744 effect.element.makePositioned(); 745 effect.element.firstChild.makePositioned(); 746 if(window.opera) effect.element.setStyle({top: ''}); 747 effect.element.makeClipping(); 748 effect.element.show(); }, 749 afterUpdateInternal: function(effect) { 750 effect.element.firstChild.setStyle({bottom: 751 (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, 752 afterFinishInternal: function(effect) { 753 effect.element.hide(); 754 effect.element.undoClipping(); 755 effect.element.firstChild.undoPositioned(); 756 effect.element.undoPositioned(); 757 effect.element.setStyle({bottom: oldInnerBottom}); } 467 758 }, arguments[1] || {}) 468 759 ); 469 760 } 470 761 762 // Bug in opera makes the TD containing this element expand for a instance after finish 471 763 Effect.Squish = function(element) { 472 return new Effect.Scale(element, 0, 473 { afterFinish: function(effect) { Element.hide(effect.element); } }); 764 return new Effect.Scale(element, window.opera ? 1 : 0, 765 { restoreAfterFinish: true, 766 beforeSetup: function(effect) { 767 effect.element.makeClipping(effect.element); }, 768 afterFinishInternal: function(effect) { 769 effect.element.hide(effect.element); 770 effect.element.undoClipping(effect.element); } 771 }); 474 772 } 475 773 476 774 Effect.Grow = function(element) { 477 775 element = $(element); 478 var options = arguments[1] || {}; 479 480 var originalWidth = element.clientWidth; 481 var originalHeight = element.clientHeight; 482 element.style.overflow = 'hidden'; 483 Element.show(element); 484 485 var direction = options.direction || 'center'; 486 var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; 487 var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; 488 var opacityTransition = options.opacityTransition || Effect.Transitions.full; 489 776 var options = Object.extend({ 777 direction: 'center', 778 moveTransition: Effect.Transitions.sinoidal, 779 scaleTransition: Effect.Transitions.sinoidal, 780 opacityTransition: Effect.Transitions.full 781 }, arguments[1] || {}); 782 var oldStyle = { 783 top: element.style.top, 784 left: element.style.left, 785 height: element.style.height, 786 width: element.style.width, 787 opacity: element.getInlineOpacity() }; 788 789 var dims = element.getDimensions(); 490 790 var initialMoveX, initialMoveY; 491 791 var moveX, moveY; 492 792 493 switch ( direction) {793 switch (options.direction) { 494 794 case 'top-left': 495 795 initialMoveX = initialMoveY = moveX = moveY = 0; 496 796 break; 497 797 case 'top-right': 498 initialMoveX = originalWidth;798 initialMoveX = dims.width; 499 799 initialMoveY = moveY = 0; 500 moveX = - originalWidth;800 moveX = -dims.width; 501 801 break; 502 802 case 'bottom-left': 503 803 initialMoveX = moveX = 0; 504 initialMoveY = originalHeight;505 moveY = - originalHeight;804 initialMoveY = dims.height; 805 moveY = -dims.height; 506 806 break; 507 807 case 'bottom-right': 508 initialMoveX = originalWidth;509 initialMoveY = originalHeight;510 moveX = - originalWidth;511 moveY = - originalHeight;808 initialMoveX = dims.width; 809 initialMoveY = dims.height; 810 moveX = -dims.width; 811 moveY = -dims.height; 512 812 break; 513 813 case 'center': 514 initialMoveX = originalWidth / 2;515 initialMoveY = originalHeight / 2;516 moveX = - originalWidth / 2;517 moveY = - originalHeight / 2;814 initialMoveX = dims.width / 2; 815 initialMoveY = dims.height / 2; 816 moveX = -dims.width / 2; 817 moveY = -dims.height / 2; 518 818 break; 519 819 } 520 820 521 return new Effect.MoveBy(element, initialMoveY, initialMoveX, { 821 return new Effect.Move(element, { 822 x: initialMoveX, 823 y: initialMoveY, 522 824 duration: 0.01, 523 beforeUpdate: function(effect) { $(element).style.height = '0px'; }, 524 afterFinish: function(effect) { 825 beforeSetup: function(effect) { 826 effect.element.hide(); 827 effect.element.makeClipping(); 828 effect.element.makePositioned(); 829 }, 830 afterFinishInternal: function(effect) { 525 831 new Effect.Parallel( 526 [ new Effect.Opacity(element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }), 527 new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }), 528 new Effect.Scale(element, 100, { 529 scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth }, 530 sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })], 531 options); } 532 }); 832 [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), 833 new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), 834 new Effect.Scale(effect.element, 100, { 835 scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 836 sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) 837 ], Object.extend({ 838 beforeSetup: function(effect) { 839 effect.effects[0].element.setStyle({height: '0px'}); 840 effect.effects[0].element.show(); 841 }, 842 afterFinishInternal: function(effect) { 843 effect.effects[0].element.undoClipping(); 844 effect.effects[0].element.undoPositioned(); 845 effect.effects[0].element.setStyle(oldStyle); 846 } 847 }, options) 848 ) 849 } 850 }); 533 851 } 534 852 535 853 Effect.Shrink = function(element) { 536 854 element = $(element); 537 var options = arguments[1] || {}; 538 539 var originalWidth = element.clientWidth; 540 var originalHeight = element.clientHeight; 541 element.style.overflow = 'hidden'; 542 Element.show(element); 543 544 var direction = options.direction || 'center'; 545 var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; 546 var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; 547 var opacityTransition = options.opacityTransition || Effect.Transitions.none; 548 855 var options = Object.extend({ 856 direction: 'center', 857 moveTransition: Effect.Transitions.sinoidal, 858 scaleTransition: Effect.Transitions.sinoidal, 859 opacityTransition: Effect.Transitions.none 860 }, arguments[1] || {}); 861 var oldStyle = { 862 top: element.style.top, 863 left: element.style.left, 864 height: element.style.height, 865 width: element.style.width, 866 opacity: element.getInlineOpacity() }; 867 868 var dims = element.getDimensions(); 549 869 var moveX, moveY; 550 870 551 switch ( direction) {871 switch (options.direction) { 552 872 case 'top-left': 553 873 moveX = moveY = 0; 554 874 break; 555 875 case 'top-right': 556 moveX = originalWidth;876 moveX = dims.width; 557 877 moveY = 0; 558 878 break; 559 879 case 'bottom-left': 560 880 moveX = 0; 561 moveY = originalHeight;881 moveY = dims.height; 562 882 break; 563 883 case 'bottom-right': 564 moveX = originalWidth;565 moveY = originalHeight;884 moveX = dims.width; 885 moveY = dims.height; 566 886 break; 567 887 case 'center': 568 moveX = originalWidth / 2;569 moveY = originalHeight / 2;888 moveX = dims.width / 2; 889 moveY = dims.height / 2; 570 890 break; 571 891 } 572 892 573 893 return new Effect.Parallel( 574 [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }), 575 new Effect.Scale(element, 0, { sync: true, transition: moveTransition }), 576 new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: scaleTransition }) ], 577 options); 894 [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), 895 new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), 896 new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) 897 ], Object.extend({ 898 beforeStartInternal: function(effect) { 899 effect.effects[0].element.makePositioned(); 900 effect.effects[0].element.makeClipping(); }, 901 afterFinishInternal: function(effect) { 902 effect.effects[0].element.hide(); 903 effect.effects[0].element.undoClipping(); 904 effect.effects[0].element.undoPositioned(); 905 effect.effects[0].element.setStyle(oldStyle); } 906 }, options) 907 ); 578 908 } 579 909 580 910 Effect.Pulsate = function(element) { 911 element = $(element); 581 912 var options = arguments[1] || {}; 913 var oldOpacity = element.getInlineOpacity(); 582 914 var transition = options.transition || Effect.Transitions.sinoidal; 583 915 var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; 584 916 reverser.bind(transition); 585 917 return new Effect.Opacity(element, 586 Object.extend(Object.extend({ duration: 3.0, 587 afterFinish: function(effect) { Element.show(effect.element); }918 Object.extend(Object.extend({ duration: 3.0, from: 0, 919 afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } 588 920 }, options), {transition: reverser})); 589 921 } 590 922 591 923 Effect.Fold = function(element) { 592 $(element).style.overflow = 'hidden'; 593 return new Effect.Scale(element, 5, Object.extend({ 594 scaleContent: false, 595 scaleTo: 100, 596 scaleX: false, 597 afterFinish: function(effect) { 598 new Effect.Scale(element, 1, { 599 scaleContent: false, 600 scaleTo: 0, 601 scaleY: false, 602 afterFinish: function(effect) { Element.hide(effect.element) } }); 603 }}, arguments[1] || {})); 604 } 605 606 // old: new Effect.ContentZoom(element, percent) 607 // new: Element.setContentZoom(element, percent) 608 609 Element.setContentZoom = function(element, percent) { 610 var element = $(element); 611 element.style.fontSize = (percent/100) + "em"; 612 if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 613 } 924 element = $(element); 925 var oldStyle = { 926 top: element.style.top, 927 left: element.style.left, 928 width: element.style.width, 929 height: element.style.height }; 930 Element.makeClipping(element); 931 return new Effect.Scale(element, 5, Object.extend({ 932 scaleContent: false, 933 scaleX: false, 934 afterFinishInternal: function(effect) { 935 new Effect.Scale(element, 1, { 936 scaleContent: false, 937 scaleY: false, 938 afterFinishInternal: function(effect) { 939 effect.element.hide(); 940 effect.element.undoClipping(); 941 effect.element.setStyle(oldStyle); 942 } }); 943 }}, arguments[1] || {})); 944 }; 945 946 ['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', 947 'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each( 948 function(f) { Element.Methods[f] = Element[f]; } 949 ); 950 951 Element.Methods.visualEffect = function(element, effect, options) { 952 s = effect.gsub(/_/, '-').camelize(); 953 effect_class = s.charAt(0).toUpperCase() + s.substring(1); 954 new Effect[effect_class](element, options); 955 return $(element); 956 }; 957 958 Element.addMethods(); -
trunk/trax/data/public/javascripts/prototype.js
r182 r226 1 // $Id$ 2 /* Prototype JavaScript framework, version 1.3.0 1 /* Prototype JavaScript framework, version 1.5.0_rc0 3 2 * (c) 2005 Sam Stephenson <sam@conio.net> 4 3 * 5 * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff6 * against the source tree, available from the Prototype darcs repository.7 *8 4 * Prototype is freely distributable under the terms of an MIT-style license. 9 *10 5 * For details, see the Prototype web site: http://prototype.conio.net/ 11 6 * … … 13 8 14 9 var Prototype = { 15 Version: '1.3.0', 16 emptyFunction: function() {} 10 Version: '1.5.0_rc0', 11 ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)', 12 13 emptyFunction: function() {}, 14 K: function(x) {return x} 17 15 } 18 16 19 17 var Class = { 20 18 create: function() { 21 return function() { 19 return function() { 22 20 this.initialize.apply(this, arguments); 23 21 } … … 28 26 29 27 Object.extend = function(destination, source) { 30 for ( property in source) {28 for (var property in source) { 31 29 destination[property] = source[property]; 32 30 } … … 34 32 } 35 33 36 Object.prototype.extend = function(object) { 37 return Object.extend.apply(this, [this, object]); 38 } 39 40 Function.prototype.bind = function(object) { 41 var __method = this; 34 Object.inspect = function(object) { 35 try { 36 if (object == undefined) return 'undefined'; 37 if (object == null) return 'null'; 38 return object.inspect ? object.inspect() : object.toString(); 39 } catch (e) { 40 if (e instanceof RangeError) return '...'; 41 throw e; 42 } 43 } 44 45 Function.prototype.bind = function() { 46 var __method = this, args = $A(arguments), object = args.shift(); 42 47 return function() { 43 __method.apply(object, arguments);48 return __method.apply(object, args.concat($A(arguments))); 44 49 } 45 50 } … … 48 53 var __method = this; 49 54 return function(event) { 50 __method.call(object, event || window.event); 51 } 52 } 53 54 Number.prototype.toColorPart = function() { 55 var digits = this.toString(16); 56 if (this < 16) return '0' + digits; 57 return digits; 58 } 55 return __method.call(object, event || window.event); 56 } 57 } 58 59 Object.extend(Number.prototype, { 60 toColorPart: function() { 61 var digits = this.toString(16); 62 if (this < 16) return '0' + digits; 63 return digits; 64 }, 65 66 succ: function() { 67 return this + 1; 68 }, 69 70 times: function(iterator) { 71 $R(0, this, true).each(iterator); 72 return this; 73 } 74 }); 59 75 60 76 var Try = { … … 92 108 onTimerEvent: function() { 93 109 if (!this.currentlyExecuting) { 94 try { 110 try { 95 111 this.currentlyExecuting = true; 96 this.callback(); 97 } finally { 112 this.callback(); 113 } finally { 98 114 this.currentlyExecuting = false; 99 115 } … … 101 117 } 102 118 } 103 104 /*--------------------------------------------------------------------------*/ 105 106 function $() { 107 var elements = new Array(); 108 109 for (var i = 0; i < arguments.length; i++) { 110 var element = arguments[i]; 111 if (typeof element == 'string') 112 element = document.getElementById(element); 113 114 if (arguments.length == 1) 115 return element; 116 117 elements.push(element); 118 } 119 120 return elements; 121 } 122 123 if (!Array.prototype.push) { 124 Array.prototype.push = function() { 125 var startLength = this.length; 126 for (var i = 0; i < arguments.length; i++) 127 this[startLength + i] = arguments[i]; 128 return this.length; 129 } 130 } 131 132 if (!Function.prototype.apply) { 133 // Based on code from http://www.youngpup.net/ 134 Function.prototype.apply = function(object, parameters) { 135 var parameterStrings = new Array(); 136 if (!object) object = window; 137 if (!parameters) parameters = new Array(); 138 139 for (var i = 0; i < parameters.length; i++) 140 parameterStrings[i] = 'parameters[' + i + ']'; 141 142 object.__apply__ = this; 143 var result = eval('object.__apply__(' + 144 parameterStrings[i].join(', ') + ')'); 145 object.__apply__ = null; 146 119 Object.extend(String.prototype, { 120 gsub: function(pattern, replacement) { 121 var result = '', source = this, match; 122 replacement = arguments.callee.prepareReplacement(replacement); 123 124 while (source.length > 0) { 125 if (match = source.match(pattern)) { 126 result += source.slice(0, match.index); 127 result += (replacement(match) || '').toString(); 128 source = source.slice(match.index + match[0].length); 129 } else { 130 result += source, source = ''; 131 } 132 } 147 133 return result; 148 } 149 } 150 151 String.prototype.extend({ 134 }, 135 136 sub: function(pattern, replacement, count) { 137 replacement = this.gsub.prepareReplacement(replacement); 138 count = count === undefined ? 1 : count; 139 140 return this.gsub(pattern, function(match) { 141 if (--count < 0) return match[0]; 142 return replacement(match); 143 }); 144 }, 145 146 scan: function(pattern, iterator) { 147 this.gsub(pattern, iterator); 148 return this; 149 }, 150 151 truncate: function(length, truncation) { 152 length = length || 30; 153 truncation = truncation === undefined ? '...' : truncation; 154 return this.length > length ? 155 this.slice(0, length - truncation.length) + truncation : this; 156 }, 157 158 strip: function() { 159 return this.replace(/^\s+/, '').replace(/\s+$/, ''); 160 }, 161 152 162 stripTags: function() { 153 163 return this.replace(/<\/?[^>]+>/gi, ''); 164 }, 165 166 stripScripts: function() { 167 return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); 168 }, 169 170 extractScripts: function() { 171 var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); 172 var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); 173 return (this.match(matchAll) || []).map(function(scriptTag) { 174 return (scriptTag.match(matchOne) || ['', ''])[1]; 175 }); 176 }, 177 178 evalScripts: function() { 179 return this.extractScripts().map(function(script) { return eval(script) }); 154 180 }, 155 181 … … 164 190 var div = document.createElement('div'); 165 191 div.innerHTML = this.stripTags(); 166 return div.childNodes[0].nodeValue; 192 return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; 193 }, 194 195 toQueryParams: function() { 196 var pairs = this.match(/^\??(.*)$/)[1].split('&'); 197 return pairs.inject({}, function(params, pairString) { 198 var pair = pairString.split('='); 199 params[pair[0]] = pair[1]; 200 return params; 201 }); 202 }, 203 204 toArray: function() { 205 return this.split(''); 206 }, 207 208 camelize: function() { 209 var oStringList = this.split('-'); 210 if (oStringList.length == 1) return oStringList[0]; 211 212 var camelizedString = this.indexOf('-') == 0 213 ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) 214 : oStringList[0]; 215 216 for (var i = 1, len = oStringList.length; i < len; i++) { 217 var s = oStringList[i]; 218 camelizedString += s.charAt(0).toUpperCase() + s.substring(1); 219 } 220 221 return camelizedString; 222 }, 223 224 inspect: function() { 225 return "'" + this.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + "'"; 167 226 } 168 227 }); 228 229 String.prototype.gsub.prepareReplacement = function(replacement) { 230 if (typeof replacement == 'function') return replacement; 231 var template = new Template(replacement); 232 return function(match) { return template.evaluate(match) }; 233 } 234 235 String.prototype.parseQuery = String.prototype.toQueryParams; 236 237 var Template = Class.create(); 238 Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; 239 Template.prototype = { 240 initialize: function(template, pattern) { 241 this.template = template.toString(); 242 this.pattern = pattern || Template.Pattern; 243 }, 244 245 evaluate: function(object) { 246 return this.template.gsub(this.pattern, function(match) { 247 var before = match[1]; 248 if (before == '\\') return match[2]; 249 return before + (object[match[3]] || '').toString(); 250 }); 251 } 252 } 253 254 var $break = new Object(); 255 var $continue = new Object(); 256 257 var Enumerable = { 258 each: function(iterator) { 259 var index = 0; 260 try { 261 this._each(function(value) { 262 try { 263 iterator(value, index++); 264 } catch (e) { 265 if (e != $continue) throw e; 266 } 267 }); 268 } catch (e) { 269 if (e != $break) throw e; 270 } 271 }, 272 273 all: function(iterator) { 274 var result = true; 275 this.each(function(value, index) { 276 result = result && !!(iterator || Prototype.K)(value, index); 277 if (!result) throw $break; 278 }); 279 return result; 280 }, 281 282 any: function(iterator) { 283 var result = true; 284 this.each(function(value, index) { 285 if (result = !!(iterator || Prototype.K)(value, index)) 286 throw $break; 287 }); 288 return result; 289 }, 290 291 collect: function(iterator) { 292 var results = []; 293 this.each(function(value, index) { 294 results.push(iterator(value, index)); 295 }); 296 return results; 297 }, 298 299 detect: function (iterator) { 300 var result; 301 this.each(function(value, index) { 302 if (iterator(value, index)) { 303 result = value; 304 throw $break; 305 } 306 }); 307 return result; 308 }, 309 310 findAll: function(iterator) { 311 var results = []; 312 this.each(function(value, index) { 313 if (iterator(value, index)) 314 results.push(value); 315 }); 316 return results; 317 }, 318 319 grep: function(pattern, iterator) { 320 var results = []; 321 this.each(function(value, index) { 322 var stringValue = value.toString(); 323 if (stringValue.match(pattern)) 324 results.push((iterator || Prototype.K)(value, index)); 325 }) 326 return results; 327 }, 328 329 include: function(object) { 330 var found = false; 331 this.each(function(value) { 332 if (value == object) { 333 found = true; 334 throw $break; 335 } 336 }); 337 return found; 338 }, 339 340 inject: function(memo, iterator) { 341 this.each(function(value, index) { 342 memo = iterator(memo, value, index); 343 }); 344 return memo; 345 }, 346 347 invoke: function(method) { 348 var args = $A(arguments).slice(1); 349 return this.collect(function(value) { 350 return value[method].apply(value, args); 351 }); 352 }, 353 354 max: function(iterator) { 355 var result; 356 this.each(function(value, index) { 357 value = (iterator || Prototype.K)(value, index); 358 if (result == undefined || value >= result) 359 result = value; 360 }); 361 return result; 362 }, 363 364 min: function(iterator) { 365 var result; 366 this.each(function(value, index) { 367 value = (iterator || Prototype.K)(value, index); 368 if (result == undefined || value < result) 369 result = value; 370 }); 371 return result; 372 }, 373 374 partition: function(iterator) { 375 var trues = [], falses = []; 376 this.each(function(value, index) { 377 ((iterator || Prototype.K)(value, index) ? 378 trues : falses).push(value); 379 }); 380 return [trues, falses]; 381 }, 382 383 pluck: function(property) { 384 var results = []; 385 this.each(function(value, index) { 386 results.push(value[property]); 387 }); 388 return results; 389 }, 390 391 reject: function(iterator) { 392 var results = []; 393 this.each(function(value, index) { 394 if (!iterator(value, index)) 395 results.push(value); 396 }); 397 return results; 398 }, 399 400 sortBy: function(iterator) { 401 return this.collect(function(value, index) { 402 return {value: value, criteria: iterator(value, index)}; 403 }).sort(function(left, right) { 404 var a = left.criteria, b = right.criteria; 405 return a < b ? -1 : a > b ? 1 : 0; 406 }).pluck('value'); 407 }, 408 409 toArray: function() { 410 return this.collect(Prototype.K); 411 }, 412 413 zip: function() { 414 var iterator = Prototype.K, args = $A(arguments); 415 if (typeof args.last() == 'function') 416 iterator = args.pop(); 417 418 var collections = [this].concat(args).map($A); 419 return this.map(function(value, index) { 420 return iterator(collections.pluck(index)); 421 }); 422 }, 423 424 inspect: function() { 425 return '#<Enumerable:' + this.toArray().inspect() + '>'; 426 } 427 } 428 429 Object.extend(Enumerable, { 430 map: Enumerable.collect, 431 find: Enumerable.detect, 432 select: Enumerable.findAll, 433 member: Enumerable.include, 434 entries: Enumerable.toArray 435 }); 436 var $A = Array.from = function(iterable) { 437 if (!iterable) return []; 438 if (iterable.toArray) { 439 return iterable.toArray(); 440 } else { 441 var results = []; 442 for (var i = 0; i < iterable.length; i++) 443 results.push(iterable[i]); 444 return results; 445 } 446 } 447 448 Object.extend(Array.prototype, Enumerable); 449 450 if (!Array.prototype._reverse) 451 Array.prototype._reverse = Array.prototype.reverse; 452 453 Object.extend(Array.prototype, { 454 _each: function(iterator) { 455 for (var i = 0; i < this.length; i++) 456 iterator(this[i]); 457 }, 458 459 clear: function() { 460 this.length = 0; 461 return this; 462 }, 463 464 first: function() { 465 return this[0]; 466 }, 467 468 last: function() { 469 return this[this.length - 1]; 470 }, 471 472 compact: function() { 473 return this.select(function(value) { 474 return value != undefined || value != null; 475 }); 476 }, 477 478 flatten: function() { 479 return this.inject([], function(array, value) { 480 return array.concat(value && value.constructor == Array ? 481 value.flatten() : [value]); 482 }); 483 }, 484 485 without: function() { 486 var values = $A(arguments); 487 return this.select(function(value) { 488 return !values.include(value); 489 }); 490 }, 491 492 indexOf: function(object) { 493 for (var i = 0; i < this.length; i++) 494 if (this[i] == object) return i; 495 return -1; 496 }, 497 498 reverse: function(inline) { 499 return (inline !== false ? this : this.toArray())._reverse(); 500 }, 501 502 inspect: function() { 503 return '[' + this.map(Object.inspect).join(', ') + ']'; 504 } 505 }); 506 var Hash = { 507 _each: function(iterator) { 508 for (var key in this) { 509 var value = this[key]; 510 if (typeof value == 'function') continue; 511 512 var pair = [key, value]; 513 pair.key = key; 514 pair.value = value; 515 iterator(pair); 516 } 517 }, 518 519 keys: function() { 520 return this.pluck('key'); 521 }, 522 523 values: function() { 524 return this.pluck('value'); 525 }, 526 527 merge: function(hash) { 528 return $H(hash).inject($H(this), function(mergedHash, pair) { 529 mergedHash[pair.key] = pair.value; 530 return mergedHash; 531 }); 532 }, 533 534 toQueryString: function() { 535 return this.map(function(pair) { 536 return pair.map(encodeURIComponent).join('='); 537 }).join('&'); 538 }, 539 540 inspect: function() { 541 return '#<Hash:{' + this.map(function(pair) { 542 return pair.map(Object.inspect).join(': '); 543 }).join(', ') + '}>'; 544 } 545 } 546 547 function $H(object) { 548 var hash = Object.extend({}, object || {}); 549 Object.extend(hash, Enumerable); 550 Object.extend(hash, Hash); 551 return hash; 552 } 553 ObjectRange = Class.create(); 554 Object.extend(ObjectRange.prototype, Enumerable); 555 Object.extend(ObjectRange.prototype, { 556 initialize: function(start, end, exclusive) { 557 this.start = start; 558 this.end = end; 559 this.exclusive = exclusive; 560 }, 561 562 _each: function(iterator) { 563 var value = this.start; 564 do { 565 iterator(value); 566 value = value.succ(); 567 } while (this.include(value)); 568 }, 569 570 include: function(value) { 571 if (value < this.start) 572 return false; 573 if (this.exclusive) 574 return value < this.end; 575 return value <= this.end; 576 } 577 }); 578 579 var $R = function(start, end, exclusive) { 580 return new ObjectRange(start, end, exclusive); 581 } 169 582 170 583 var Ajax = { 171 584 getTransport: function() { 172 585 return Try.these( 586 function() {return new XMLHttpRequest()}, 173 587 function() {return new ActiveXObject('Msxml2.XMLHTTP')}, 174 function() {return new ActiveXObject('Microsoft.XMLHTTP')}, 175 function() {return new XMLHttpRequest()} 588 function() {return new ActiveXObject('Microsoft.XMLHTTP')} 176 589 ) || false; 177 } 178 } 590 }, 591 592 activeRequestCount: 0 593 } 594 595 Ajax.Responders = { 596 responders: [], 597 598 _each: function(iterator) { 599 this.responders._each(iterator); 600 }, 601 602 register: function(responderToAdd) { 603 if (!this.include(responderToAdd)) 604 this.responders.push(responderToAdd); 605 }, 606 607 unregister: function(responderToRemove) { 608 this.responders = this.responders.without(responderToRemove); 609 }, 610 611 dispatch: function(callback, request, transport, json) { 612 this.each(function(responder) { 613 if (responder[callback] && typeof responder[callback] == 'function') { 614 try { 615 responder[callback].apply(responder, [request, transport, json]); 616 } catch (e) {} 617 } 618 }); 619 } 620 }; 621 622 Object.extend(Ajax.Responders, Enumerable); 623 624 Ajax.Responders.register({ 625 onCreate: function() { 626 Ajax.activeRequestCount++; 627 }, 628 629 onComplete: function() { 630 Ajax.activeRequestCount--; 631 } 632 }); 179 633 180 634 Ajax.Base = function() {}; … … 184 638 method: 'post', 185 639 asynchronous: true, 640 contentType: 'application/x-www-form-urlencoded', 186 641 parameters: '' 187 }.extend(options || {}); 642 } 643 Object.extend(this.options, options || {}); 188 644 }, 189 645 190 646 responseIsSuccess: function() { 191 647 return this.transport.status == undefined 192 || this.transport.status == 0 648 || this.transport.status == 0 193 649 || (this.transport.status >= 200 && this.transport.status < 300); 194 650 }, … … 200 656 201 657 Ajax.Request = Class.create(); 202 Ajax.Request.Events = 658 Ajax.Request.Events = 203 659 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; 204 660 205 Ajax.Request.prototype = (new Ajax.Base()).extend({661 Ajax.Request.prototype = Object.extend(new Ajax.Base(), { 206 662 initialize: function(url, options) { 207 663 this.transport = Ajax.getTransport(); … … 215 671 216 672 try { 217 if (this.options.method == 'get') 218 url += '?' + parameters; 219 220 this.transport.open(this.options.method, url, 673 this.url = url; 674 if (this.options.method == 'get' && parameters.length > 0) 675 this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; 676 677 Ajax.Responders.dispatch('onCreate', this, this.transport); 678 679 this.transport.open(this.options.method, this.url, 221 680 this.options.asynchronous); 222 681 … … 232 691 233 692 } catch (e) { 693 this.dispatchException(e); 234 694 } 235 695 }, 236 696 237 697 setRequestHeaders: function() { 238 var requestHeaders = 698 var requestHeaders = 239 699 ['X-Requested-With', 'XMLHttpRequest', 240 'X-Prototype-Version', Prototype.Version]; 700 'X-Prototype-Version', Prototype.Version, 701 'Accept', 'text/javascript, text/html, application/xml, text/xml, */*']; 241 702 242 703 if (this.options.method == 'post') { 243 requestHeaders.push('Content-type', 244 'application/x-www-form-urlencoded'); 704 requestHeaders.push('Content-type', this.options.contentType); 245 705 246 706 /* Force "Connection: close" for Mozilla browsers to work around 247 707 * a bug where XMLHttpReqeuest sends an incorrect Content-length 248 * header. See Mozilla Bugzilla #246651. 708 * header. See Mozilla Bugzilla #246651. 249 709 */ 250 710 if (this.transport.overrideMimeType) … … 265 725 }, 266 726 727 header: function(name) { 728 try { 729 return this.transport.getResponseHeader(name); 730 } catch (e) {} 731 }, 732 733 evalJSON: function() { 734 try { 735 return eval('(' + this.header('X-JSON') + ')'); 736 } catch (e) {} 737 }, 738 739 evalResponse: function() { 740 try { 741 return eval(this.transport.responseText); 742 } catch (e) { 743 this.dispatchException(e); 744 } 745 }, 746 267 747 respondToReadyState: function(readyState) { 268 748 var event = Ajax.Request.Events[readyState]; 269 270 if (event == 'Complete' && this.responseIsFailure()) 271 (this.options['on' + this.transport.status] 272 || this.options.onFailure 273 || Prototype.emptyFunction)(this.transport); 274 275 (this.options['on' + event] || Prototype.emptyFunction)(this.transport); 749 var transport = this.transport, json = this.evalJSON(); 750 751 if (event == 'Complete') { 752 try { 753 (this.options['on' + this.transport.status] 754 || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] 755 || Prototype.emptyFunction)(transport, json); 756 } catch (e) { 757 this.dispatchException(e); 758 } 759 760 if ((this.header('Content-type') || '').match(/^text\/javascript/i)) 761 this.evalResponse(); 762 } 763 764 try { 765 (this.options['on' + event] || Prototype.emptyFunction)(transport, json); 766 Ajax.Responders.dispatch('on' + event, this, transport, json); 767 } catch (e) { 768 this.dispatchException(e); 769 } 770 771 /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ 772 if (event == 'Complete') 773 this.transport.onreadystatechange = Prototype.emptyFunction; 774 }, 775 776 dispatchException: function(exception) { 777 (this.options.onException || Prototype.emptyFunction)(this, exception); 778 Ajax.Responders.dispatch('onException', this, exception); 276 779 } 277 780 }); 278 781 279 782 Ajax.Updater = Class.create(); 280 Ajax.Updater.ScriptFragment = '(?:<script.*?>)((\n|.)*?)(?:<\/script>)'; 281 282 Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({ 783 784 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { 283 785 initialize: function(container, url, options) { 284 786 this.containers = { … … 292 794 293 795 var onComplete = this.options.onComplete || Prototype.emptyFunction; 294 this.options.onComplete = (function( ) {796 this.options.onComplete = (function(transport, object) { 295 797 this.updateContent(); 296 onComplete(t his.transport);798 onComplete(transport, object); 297 799 }).bind(this); 298 800 … … 303 805 var receiver = this.responseIsSuccess() ? 304 806 this.containers.success : this.containers.failure; 305 306 var match = new RegExp(Ajax.Updater.ScriptFragment, 'img'); 307 var response = this.transport.responseText.replace(match, '');308 var scripts = this.transport.responseText.match(match);807 var response = this.transport.responseText; 808 809 if (!this.options.evalScripts) 810 response = response.stripScripts(); 309 811 310 812 if (receiver) { … … 312 814 new this.options.insertion(receiver, response); 313 815 } else { 314 receiver.innerHTML = response;816 Element.update(receiver, response); 315 817 } 316 818 } … … 318 820 if (this.responseIsSuccess()) { 319 821 if (this.onComplete) 320 setTimeout((function() {this.onComplete( 321 this.transport)}).bind(this), 10); 322 } 323 324 if (this.options.evalScripts && scripts) { 325 match = new RegExp(Ajax.Updater.ScriptFragment, 'im'); 326 setTimeout((function() { 327 for (var i = 0; i < scripts.length; i++) 328 eval(scripts[i].match(match)[1]); 329 }).bind(this), 10); 822 setTimeout(this.onComplete.bind(this), 10); 330 823 } 331 824 } … … 333 826 334 827 Ajax.PeriodicalUpdater = Class.create(); 335 Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({828 Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { 336 829 initialize: function(container, url, options) { 337 830 this.setOptions(options); … … 339 832 340 833 this.frequency = (this.options.frequency || 2); 341 this.decay = 1;834 this.decay = (this.options.decay || 1); 342 835 343 836 this.updater = {}; … … 356 849 this.updater.onComplete = undefined; 357 850 clearTimeout(this.timer); 358 (this.onComplete || Ajax.emptyFunction).apply(this, arguments);851 (this.onComplete || Prototype.emptyFunction).apply(this, arguments); 359 852 }, 360 853 361 854 updateComplete: function(request) { 362 855 if (this.options.decay) { 363 this.decay = (request.responseText == this.lastText ? 856 this.decay = (request.responseText == this.lastText ? 364 857 this.decay * this.options.decay : 1); 365 858 366 859 this.lastText = request.responseText; 367 860 } 368 this.timer = setTimeout(this.onTimerEvent.bind(this), 861 this.timer = setTimeout(this.onTimerEvent.bind(this), 369 862 this.decay * this.frequency * 1000); 370 863 }, … … 374 867 } 375 868 }); 376 377 document.getElementsByClassName = function(className) { 378 var children = document.getElementsByTagName('*') || document.all; 379 var elements = new Array(); 380 381 for (var i = 0; i < children.length; i++) { 382 var child = children[i]; 383 var classNames = child.className.split(' '); 384 for (var j = 0; j < classNames.length; j++) { 385 if (classNames[j] == className) { 386 elements.push(child); 387 break; 388 } 389 } 390 } 391 392 return elements; 869 function $() { 870 var results = [], element; 871 for (var i = 0; i < arguments.length; i++) { 872 element = arguments[i]; 873 if (typeof element == 'string') 874 element = document.getElementById(element); 875 results.push(Element.extend(element)); 876 } 877 return results.length < 2 ? results[0] : results; 878 } 879 880 document.getElementsByClassName = function(className, parentElement) { 881 var children = ($(parentElement) || document.body).getElementsByTagName('*'); 882 return $A(children).inject([], function(elements, child) { 883 if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) 884 elements.push(Element.extend(child)); 885 return elements; 886 }); 393 887 } 394 888 395 889 /*--------------------------------------------------------------------------*/ 396 890 397 if (!window.Element) {891 if (!window.Element) 398 892 var Element = new Object(); 399 } 400 401 Object.extend(Element, { 893 894 Element.extend = function(element) { 895 if (!element) return; 896 if (_nativeExtensions) return element; 897 898 if (!element._extended && element.tagName && element != window) { 899 var methods = Element.Methods, cache = Element.extend.cache; 900 for (property in methods) { 901 var value = methods[property]; 902 if (typeof value == 'function') 903 element[property] = cache.findOrStore(value); 904 } 905 } 906 907 element._extended = true; 908 return element; 909 } 910 911 Element.extend.cache = { 912 findOrStore: function(value) { 913 return this[value] = this[value] || function() { 914 return value.apply(null, [this].concat($A(arguments))); 915 } 916 } 917 } 918 919 Element.Methods = { 920 visible: function(element) { 921 return $(element).style.display != 'none'; 922 }, 923 402 924 toggle: function() { 403 925 for (var i = 0; i < arguments.length; i++) { 404 926 var element = $(arguments[i]); 405 element.style.display = 406 (element.style.display == 'none' ? '' : 'none'); 927 Element[Element.visible(element) ? 'hide' : 'show'](element); 407 928 } 408 929 }, … … 426 947 element.parentNode.removeChild(element); 427 948 }, 428 949 950 update: function(element, html) { 951 $(element).innerHTML = html.stripScripts(); 952 setTimeout(function() {html.evalScripts()}, 10); 953 }, 954 955 replace: function(element, html) { 956 element = $(element); 957 if (element.outerHTML) { 958 element.outerHTML = html.stripScripts(); 959 } else { 960 var range = element.ownerDocument.createRange(); 961 range.selectNodeContents(element); 962 element.parentNode.replaceChild( 963 range.createContextualFragment(html.stripScripts()), element); 964 } 965 setTimeout(function() {html.evalScripts()}, 10); 966 }, 967 429 968 getHeight: function(element) { 430 969 element = $(element); 431 return element.offsetHeight; 970 return element.offsetHeight; 971 }, 972 973 classNames: function(element) { 974 return new Element.ClassNames(element); 432 975 }, 433 976 434 977 hasClassName: function(element, className) { 435 element = $(element); 436 if (!element) 437 return; 438 var a = element.className.split(' '); 439 for (var i = 0; i < a.length; i++) { 440 if (a[i] == className) 441 return true; 442 } 443 return false; 978 if (!(element = $(element))) return; 979 return Element.classNames(element).include(className); 444 980 }, 445 981 446 982 addClassName: function(element, className) { 447 element = $(element); 448 Element.removeClassName(element, className); 449 element.className += ' ' + className; 983 if (!(element = $(element))) return; 984 return Element.classNames(element).add(className); 450 985 }, 451 986 452 987 removeClassName: function(element, className) { 453 element = $(element); 454 if (!element) 455 return; 456 var newClassName = ''; 457 var a = element.className.split(' '); 458 for (var i = 0; i < a.length; i++) { 459 if (a[i] != className) { 460 if (i > 0) 461 newClassName += ' '; 462 newClassName += a[i]; 463 } 464 } 465 element.className = newClassName; 466 }, 467 988 if (!(element = $(element))) return; 989 return Element.classNames(element).remove(className); 990 }, 991 468 992 // removes whitespace-only text node children 469 993 cleanWhitespace: function(element) { 470 varelement = $(element);994 element = $(element); 471 995 for (var i = 0; i < element.childNodes.length; i++) { 472 996 var node = element.childNodes[i]; 473 if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) 997 if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) 474 998 Element.remove(node); 475 999 } 476 } 477 }); 1000 }, 1001 1002 empty: function(element) { 1003 return $(element).innerHTML.match(/^\s*$/); 1004 }, 1005 1006 childOf: function(element, ancestor) { 1007 element = $(element), ancestor = $(ancestor); 1008 while (element = element.parentNode) 1009 if (element == ancestor) return true; 1010 return false; 1011 }, 1012 1013 scrollTo: function(element) { 1014 element = $(element); 1015 var x = element.x ? element.x : element.offsetLeft, 1016 y = element.y ? element.y : element.offsetTop; 1017 window.scrollTo(x, y); 1018 }, 1019 1020 getStyle: function(element, style) { 1021 element = $(element); 1022 var value = element.style[style.camelize()]; 1023 if (!value) { 1024 if (document.defaultView && document.defaultView.getComputedStyle) { 1025 var css = document.defaultView.getComputedStyle(element, null); 1026 value = css ? css.getPropertyValue(style) : null; 1027 } else if (element.currentStyle) { 1028 value = element.currentStyle[style.camelize()]; 1029 } 1030 } 1031 1032 if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) 1033 if (Element.getStyle(element, 'position') == 'static') value = 'auto'; 1034 1035 return value == 'auto' ? null : value; 1036 }, 1037 1038 setStyle: function(element, style) { 1039 element = $(element); 1040 for (var name in style) 1041 element.style[name.camelize()] = style[name]; 1042 }, 1043 1044 getDimensions: function(element) { 1045 element = $(element); 1046 if (Element.getStyle(element, 'display') != 'none') 1047 return {width: element.offsetWidth, height: element.offsetHeight}; 1048 1049 // All *Width and *Height properties give 0 on elements with display none, 1050 // so enable the element temporarily 1051 var els = element.style; 1052 var originalVisibility = els.visibility; 1053 var originalPosition = els.position; 1054 els.visibility = 'hidden'; 1055 els.position = 'absolute'; 1056 els.display = ''; 1057 var originalWidth = element.clientWidth; 1058 var originalHeight = element.clientHeight; 1059 els.display = 'none'; 1060 els.position = originalPosition; 1061 els.visibility = originalVisibility; 1062 return {width: originalWidth, height: originalHeight}; 1063 }, 1064 1065 makePositioned: function(element) { 1066 element = $(element); 1067 var pos = Element.getStyle(element, 'position'); 1068 if (pos == 'static' || !pos) { 1069 element._madePositioned = true; 1070 element.style.position = 'relative'; 1071 // Opera returns the offset relative to the positioning context, when an 1072 // element is position relative but top and left have not been defined 1073 if (window.opera) { 1074 element.style.top = 0; 1075 element.style.left = 0; 1076 } 1077 } 1078 }, 1079 1080 undoPositioned: function(element) { 1081 element = $(element); 1082 if (element._madePositioned) { 1083 element._madePositioned = undefined; 1084 element.style.position = 1085 element.style.top = 1086 element.style.left = 1087 element.style.bottom = 1088 element.style.right = ''; 1089 } 1090 }, 1091 1092 makeClipping: function(element) { 1093 element = $(element); 1094 if (element._overflow) return; 1095 element._overflow = element.style.overflow; 1096 if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') 1097 element.style.overflow = 'hidden'; 1098 }, 1099 1100 undoClipping: function(element) { 1101 element = $(element); 1102 if (element._overflow) return; 1103 element.style.overflow = element._overflow; 1104 element._overflow = undefined; 1105 } 1106 } 1107 1108 Object.extend(Element, Element.Methods); 1109 1110 var _nativeExtensions = false; 1111 1112 if(!HTMLElement && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) { 1113 var HTMLElement = {} 1114 HTMLElement.prototype = document.createElement('div').__proto__; 1115 } 1116 1117 Element.addMethods = function(methods) { 1118 Object.extend(Element.Methods, methods || {}); 1119 1120 if(typeof HTMLElement != 'undefined') { 1121 var methods = Element.Methods, cache = Element.extend.cache; 1122 for (property in methods) { 1123 var value = methods[property]; 1124 if (typeof value == 'function') 1125 HTMLElement.prototype[property] = cache.findOrStore(value); 1126 } 1127 _nativeExtensions = true; 1128 } 1129 } 1130 1131 Element.addMethods(); 478 1132 479 1133 var Toggle = new Object(); … … 489 1143 initialize: function(element, content) { 490 1144 this.element = $(element); 491 this.content = content ;492 1145 this.content = content.stripScripts(); 1146 493 1147 if (this.adjacency && this.element.insertAdjacentHTML) { 494 this.element.insertAdjacentHTML(this.adjacency, this.content); 1148 try { 1149 this.element.insertAdjacentHTML(this.adjacency, this.content); 1150 } catch (e) { 1151 var tagName = this.element.tagName.toLowerCase(); 1152 if (tagName == 'tbody' || tagName == 'tr') { 1153 this.insertContent(this.contentFromAnonymousTable()); 1154 } else { 1155 throw e; 1156 } 1157 } 495 1158 } else { 496 1159 this.range = this.element.ownerDocument.createRange(); 497 1160 if (this.initializeRange) this.initializeRange(); 498 this.fragment = this.range.createContextualFragment(this.content); 499 this.insertContent(); 500 } 1161 this.insertContent([this.range.createContextualFragment(this.content)]); 1162 } 1163 1164 setTimeout(function() {content.evalScripts()}, 10); 1165 }, 1166 1167 contentFromAnonymousTable: function() { 1168 var div = document.createElement('div'); 1169 div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>'; 1170 return $A(div.childNodes[0].childNodes[0].childNodes); 501 1171 } 502 1172 } … … 505 1175 506 1176 Insertion.Before = Class.create(); 507 Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({1177 Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { 508 1178 initializeRange: function() { 509 1179 this.range.setStartBefore(this.element); 510 1180 }, 511 512 insertContent: function() { 513 this.element.parentNode.insertBefore(this.fragment, this.element); 1181 1182 insertContent: function(fragments) { 1183 fragments.each((function(fragment) { 1184 this.element.parentNode.insertBefore(fragment, this.element); 1185 }).bind(this)); 514 1186 } 515 1187 }); 516 1188 517 1189 Insertion.Top = Class.create(); 518 Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({1190 Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { 519 1191 initializeRange: function() { 520 1192 this.range.selectNodeContents(this.element); 521 1193 this.range.collapse(true); 522 1194 }, 523 524 insertContent: function() { 525 this.element.insertBefore(this.fragment, this.element.firstChild); 1195 1196 insertContent: function(fragments) { 1197 fragments.reverse(false).each((function(fragment) { 1198 this.element.insertBefore(fragment, this.element.firstChild); 1199 }).bind(this)); 526 1200 } 527 1201 }); 528 1202 529 1203 Insertion.Bottom = Class.create(); 530 Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({1204 Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { 531 1205 initializeRange: function() { 532 1206 this.range.selectNodeContents(this.element); 533 1207 this.range.collapse(this.element); 534 1208 }, 535 536 insertContent: function() { 537 this.element.appendChild(this.fragment); 1209 1210 insertContent: function(fragments) { 1211 fragments.each((function(fragment) { 1212 this.element.appendChild(fragment); 1213 }).bind(this)); 538 1214 } 539 1215 }); 540 1216 541 1217 Insertion.After = Class.create(); 542 Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({1218 Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { 543 1219 initializeRange: function() { 544 1220 this.range.setStartAfter(this.element); 545 1221 }, 546 547 insertContent: function() { 548 this.element.parentNode.insertBefore(this.fragment, 549 this.element.nextSibling); 1222 1223 insertContent: function(fragments) { 1224 fragments.each((function(fragment) { 1225 this.element.parentNode.insertBefore(fragment, 1226 this.element.nextSibling); 1227 }).bind(this)); 550 1228 } 551 1229 }); 552 1230 1231 /*--------------------------------------------------------------------------*/ 1232 1233 Element.ClassNames = Class.create(); 1234 Element.ClassNames.prototype = { 1235 initialize: function(element) { 1236 this.element = $(element); 1237 }, 1238 1239 _each: function(iterator) { 1240 this.element.className.split(/\s+/).select(function(name) { 1241 return name.length > 0; 1242 })._each(iterator); 1243 }, 1244 1245 set: function(className) { 1246 this.element.className = className; 1247 }, 1248 1249 add: function(classNameToAdd) { 1250 if (this.include(classNameToAdd)) return; 1251 this.set(this.toArray().concat(classNameToAdd).join(' ')); 1252 }, 1253 1254 remove: function(classNameToRemove) { 1255 if (!this.include(classNameToRemove)) return; 1256 this.set(this.select(function(className) { 1257 return className != classNameToRemove; 1258 }).join(' ')); 1259 }, 1260 1261 toString: function() { 1262 return this.toArray().join(' '); 1263 } 1264 } 1265 1266 Object.extend(Element.ClassNames.prototype, Enumerable); 1267 var Selector = Class.create(); 1268 Selector.prototype = { 1269 initialize: function(expression) { 1270 this.params = {classNames: []}; 1271 this.expression = expression.toString().strip(); 1272 this.parseExpression(); 1273 this.compileMatcher(); 1274 }, 1275 1276 parseExpression: function() { 1277 function abort(message) { throw 'Parse error in selector: ' + message; } 1278 1279 if (this.expression == '') abort('empty expression'); 1280 1281 var params = this.params, expr = this.expression, match, modifier, clause, rest; 1282 while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { 1283 params.attributes = params.attributes || []; 1284 params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); 1285 expr = match[1]; 1286 } 1287 1288 if (expr == '*') return this.params.wildcard = true; 1289 1290 while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { 1291 modifier = match[1], clause = match[2], rest = match[3]; 1292 switch (modifier) { 1293 case '#': params.id = clause; break; 1294 case '.': params.classNames.push(clause); break; 1295 case '': 1296 case undefined: params.tagName = clause.toUpperCase(); break; 1297 default: abort(expr.inspect()); 1298 } 1299 expr = rest; 1300 } 1301 1302 if (expr.length > 0) abort(expr.inspect()); 1303 }, 1304 1305 buildMatchExpression: function() { 1306 var params = this.params, conditions = [], clause; 1307 1308 if (params.wildcard) 1309 conditions.push('true'); 1310 if (clause = params.id) 1311 conditions.push('element.id == ' + clause.inspect()); 1312 if (clause = params.tagName) 1313 conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); 1314 if ((clause = params.classNames).length > 0) 1315 for (var i = 0; i < clause.length; i++) 1316 conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')'); 1317 if (clause = params.attributes) { 1318 clause.each(function(attribute) { 1319 var value = 'element.getAttribute(' + attribute.name.inspect() + ')'; 1320 var splitValueBy = function(delimiter) { 1321 return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; 1322 } 1323 1324 switch (attribute.operator) { 1325 case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break; 1326 case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; 1327 case '|=': conditions.push( 1328 splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() 1329 ); break; 1330 case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break; 1331 case '': 1332 case undefined: conditions.push(value + ' != null'); break; 1333 default: throw 'Unknown operator ' + attribute.operator + ' in selector'; 1334 } 1335 }); 1336 } 1337 1338 return conditions.join(' && '); 1339 }, 1340 1341 compileMatcher: function() { 1342 this.match = new Function('element', 'if (!element.tagName) return false; \ 1343 return ' + this.buildMatchExpression()); 1344 }, 1345 1346 findElements: function(scope) { 1347 var element; 1348 1349 if (element = $(this.params.id)) 1350 if (this.match(element)) 1351 if (!scope || Element.childOf(element, scope)) 1352 return [element]; 1353 1354 scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); 1355 1356 var results = []; 1357 for (var i = 0; i < scope.length; i++) 1358 if (this.match(element = scope[i])) 1359 results.push(Element.extend(element)); 1360 1361 return results; 1362 }, 1363 1364 toString: function() { 1365 return this.expression; 1366 } 1367 } 1368 1369 function $$() { 1370 return $A(arguments).map(function(expression) { 1371 return expression.strip().split(/\s+/).inject([null], function(results, expr) { 1372 var selector = new Selector(expr); 1373 return results.map(selector.findElements.bind(selector)).flatten(); 1374 }); 1375 }).flatten(); 1376 } 553 1377 var Field = { 554 1378 clear: function() { … … 560 1384 $(element).focus(); 561 1385 }, 562 1386 563 1387 present: function() { 564 1388 for (var i = 0; i < arguments.length; i++) … … 566 1390 return true; 567 1391 }, 568 1392 569 1393 select: function(element) { 570 1394 $(element).select(); 571 1395 }, 572 1396 573 1397 activate: function(element) { 574 $(element).focus(); 575 $(element).select(); 1398 element = $(element); 1399 element.focus(); 1400 if (element.select) 1401 element.select(); 576 1402 } 577 1403 } … … 583 1409 var elements = Form.getElements($(form)); 584 1410 var queryComponents = new Array(); 585 1411 586 1412 for (var i = 0; i < elements.length; i++) { 587 1413 var queryComponent = Form.Element.serialize(elements[i]); … … 589 1415 queryComponents.push(queryComponent); 590 1416 } 591 1417 592 1418 return queryComponents.join('&'); 593 1419 }, 594 1420 595 1421 getElements: function(form) { 596 varform = $(form);1422 form = $(form); 597 1423 var elements = new Array(); 598 1424 599 for ( tagName in Form.Element.Serializers) {1425 for (var tagName in Form.Element.Serializers) { 600 1426 var tagElements = form.getElementsByTagName(tagName); 601 1427 for (var j = 0; j < tagElements.length; j++) … … 604 1430 return elements; 605 1431 }, 606 1432 607 1433 getInputs: function(form, typeName, name) { 608 varform = $(form);1434 form = $(form); 609 1435 var inputs = form.getElementsByTagName('input'); 610 1436 611 1437 if (!typeName && !name) 612 1438 return inputs; 613 1439 614 1440 var matchingInputs = new Array(); 615 1441 for (var i = 0; i < inputs.length; i++) { 616 1442 var input = inputs[i]; 617 1443 if ((typeName && input.type != typeName) || 618 (name && input.name != name)) 1444 (name && input.name != name)) 619 1445 continue; 620 1446 matchingInputs.push(input); … … 641 1467 }, 642 1468 1469 findFirstElement: function(form) { 1470 return Form.getElements(form).find(function(element) { 1471 return element.type != 'hidden' && !element.disabled && 1472 ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); 1473 }); 1474 }, 1475 643 1476 focusFirstElement: function(form) { 644 var form = $(form); 645 var elements = Form.getElements(form); 646 for (var i = 0; i < elements.length; i++) { 647 var element = elements[i]; 648 if (element.type != 'hidden' && !element.disabled) { 649 Field.activate(element); 650 break; 651 } 652 } 1477 Field.activate(Form.findFirstElement(form)); 653 1478 }, 654 1479 … … 660 1485 Form.Element = { 661 1486 serialize: function(element) { 662 varelement = $(element);1487 element = $(element); 663 1488 var method = element.tagName.toLowerCase(); 664 1489 var parameter = Form.Element.Serializers[method](element); 665 666 if (parameter) 667 return encodeURIComponent(parameter[0]) + '=' + 668 encodeURIComponent(parameter[1]); 669 }, 670 1490 1491 if (parameter) { 1492 var key = encodeURIComponent(parameter[0]); 1493 if (key.length == 0) return; 1494 1495 if (parameter[1].constructor != Array) 1496 parameter[1] = [parameter[1]]; 1497 1498 return parameter[1].map(function(value) { 1499 return key + '=' + encodeURIComponent(value); 1500 }).join('&'); 1501 } 1502 }, 1503 671 1504 getValue: function(element) { 672 varelement = $(element);1505 element = $(element); 673 1506 var method = element.tagName.toLowerCase(); 674 1507 var parameter = Form.Element.Serializers[method](element); 675 676 if (parameter) 1508 1509 if (parameter) 677 1510 return parameter[1]; 678 1511 } … … 687 1520 case 'text': 688 1521 return Form.Element.Serializers.textarea(element); 689 case 'checkbox': 1522 case 'checkbox': 690 1523 case 'radio': 691 1524 return Form.Element.Serializers.inputSelector(element); … … 704 1537 705 1538 select: function(element) { 706 var value = ''; 707 if (element.type == 'select-one') { 708 var index = element.selectedIndex; 709 if (index >= 0) 710 value = element.options[index].value || element.options[index].text; 711 } else { 712 value = new Array(); 713 for (var i = 0; i < element.length; i++) { 714 var opt = element.options[i]; 715 if (opt.selected) 716 value.push(opt.value || opt.text); 717 } 1539 return Form.Element.Serializers[element.type == 'select-one' ? 1540 'selectOne' : 'selectMany'](element); 1541 }, 1542 1543 selectOne: function(element) { 1544 var value = '', opt, index = element.selectedIndex; 1545 if (index >= 0) { 1546 opt = element.options[index]; 1547 value = opt.value || opt.text; 1548 } 1549 return [element.name, value]; 1550 }, 1551 1552 selectMany: function(element) { 1553 var value = []; 1554 for (var i = 0; i < element.length; i++) { 1555 var opt = element.options[i]; 1556 if (opt.selected) 1557 value.push(opt.value || opt.text); 718 1558 } 719 1559 return [element.name, value]; … … 733 1573 this.element = $(element); 734 1574 this.callback = callback; 735 1575 736 1576 this.lastValue = this.getValue(); 737 1577 this.registerCallback(); 738 1578 }, 739 1579 740 1580 registerCallback: function() { 741 1581 setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); 742 1582 }, 743 1583 744 1584 onTimerEvent: function() { 745 1585 var value = this.getValue(); … … 752 1592 753 1593 Form.Element.Observer = Class.create(); 754 Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({1594 Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { 755 1595 getValue: function() { 756 1596 return Form.Element.getValue(this.element); … … 759 1599 760 1600 Form.Observer = Class.create(); 761 Form.Observer.prototype = (new Abstract.TimedObserver()).extend({1601 Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { 762 1602 getValue: function() { 763 1603 return Form.serialize(this.element); … … 772 1612 this.element = $(element); 773 1613 this.callback = callback; 774 1614 775 1615 this.lastValue = this.getValue(); 776 1616 if (this.element.tagName.toLowerCase() == 'form') … … 779 1619 this.registerCallback(this.element); 780 1620 }, 781 1621 782 1622 onElementEvent: function() { 783 1623 var value = this.getValue(); … … 787 1627 } 788 1628 }, 789 1629 790 1630 registerFormCallbacks: function() { 791 1631 var elements = Form.getElements(this.element); … … 793 1633 this.registerCallback(elements[i]); 794 1634 }, 795 1635 796 1636 registerCallback: function(element) { 797 1637 if (element.type) { 798 1638 switch (element.type.toLowerCase()) { 799 case 'checkbox': 1639 case 'checkbox': 800 1640 case 'radio': 801 element.target = this; 802 element.prev_onclick = element.onclick || Prototype.emptyFunction; 803 element.onclick = function() { 804 this.prev_onclick(); 805 this.target.onElementEvent(); 806 } 1641 Event.observe(element, 'click', this.onElementEvent.bind(this)); 807 1642 break; 808 1643 case 'password': … … 811 1646 case 'select-one': 812 1647 case 'select-multiple': 813 element.target = this; 814 element.prev_onchange = element.onchange || Prototype.emptyFunction; 815 element.onchange = function() { 816 this.prev_onchange(); 817 this.target.onElementEvent(); 818 } 1648 Event.observe(element, 'change', this.onElementEvent.bind(this)); 819 1649 break; 820 1650 } 821 } 1651 } 822 1652 } 823 1653 } 824 1654 825 1655 Form.Element.EventObserver = Class.create(); 826 Form.Element.EventObserver.prototype = (new Abstract.EventObserver()).extend({1656 Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { 827 1657 getValue: function() { 828 1658 return Form.Element.getValue(this.element); … … 831 1661 832 1662 Form.EventObserver = Class.create(); 833 Form.EventObserver.prototype = (new Abstract.EventObserver()).extend({1663 Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { 834 1664 getValue: function() { 835 1665 return Form.serialize(this.element); 836 1666 } 837 1667 }); 838 839 840 1668 if (!window.Event) { 841 1669 var Event = new Object(); … … 863 1691 864 1692 pointerX: function(event) { 865 return event.pageX || (event.clientX + 1693 return event.pageX || (event.clientX + 866 1694 (document.documentElement.scrollLeft || document.body.scrollLeft)); 867 1695 }, 868 1696 869 1697 pointerY: function(event) { 870 return event.pageY || (event.clientY + 1698 return event.pageY || (event.clientY + 871 1699 (document.documentElement.scrollTop || document.body.scrollTop)); 872 1700 }, 873 1701 874 1702 stop: function(event) { 875 if (event.preventDefault) { 876 event.preventDefault(); 877 event.stopPropagation(); 1703 if (event.preventDefault) { 1704 event.preventDefault(); 1705 event.stopPropagation(); 878 1706 } else { 879 1707 event.returnValue = false; 1708 event.cancelBubble = true; 880 1709 } 881 1710 }, … … 891 1720 }, 892 1721 1722 observers: false, 1723 1724 _observeAndCache: function(element, name, observer, useCapture) { 1725 if (!this.observers) this.observers = []; 1726 if (element.addEventListener) { 1727 this.observers.push([element, name, observer, useCapture]); 1728 element.addEventListener(name, observer, useCapture); 1729 } else if (element.attachEvent) { 1730 this.observers.push([element, name, observer, useCapture]); 1731 element.attachEvent('on' + name, observer); 1732 } 1733 }, 1734 1735 unloadCache: function() { 1736 if (!Event.observers) return; 1737 for (var i = 0; i < Event.observers.length; i++) { 1738 Event.stopObserving.apply(this, Event.observers[i]); 1739 Event.observers[i][0] = null; 1740 } 1741 Event.observers = false; 1742 }, 1743 893 1744 observe: function(element, name, observer, useCapture) { 894 1745 var element = $(element); 895 1746 useCapture = useCapture || false; 896 897 if (name == 'keypress') { 898 if (navigator.appVersion.indexOf('AppleWebKit') > 0) { 899 element.addEventListener('keydown', observer, useCapture); 900 return; 901 } 902 if (element.addEventListener) { 903 element.addEventListener('keypress', observer, useCapture); 904 } else if (element.attachEvent) { 905 element.attachEvent('onkeydown', observer); 906 } 907 } else { 908 if (element.addEventListener) { 909 element.addEventListener(name, observer, useCapture); 910 } else if (element.attachEvent) { 911 element.attachEvent('on' + name, observer); 912 } 913 } 1747 1748 if (name == 'keypress' && 1749 (navigator.appVersion.match(/Konqueror|Safari|KHTML/) 1750 || element.attachEvent)) 1751 name = 'keydown'; 1752 1753 this._observeAndCache(element, name, observer, useCapture); 914 1754 }, 915 1755 … … 917 1757 var element = $(element); 918 1758 useCapture = useCapture || false; 919 920 if (name == 'keypress') { 921 if (navigator.appVersion.indexOf('AppleWebKit') > 0) { 922 element.removeEventListener('keydown', observer, useCapture); 923 return; 924 } 925 if (element.removeEventListener) { 926 element.removeEventListener('keypress', observer, useCapture); 927 } else if (element.detachEvent) { 928 element.detachEvent('onkeydown', observer); 929 } 930 } else { 931 if (element.removeEventListener) { 932 element.removeEventListener(name, observer, useCapture); 933 } else if (element.detachEvent) { 934 element.detachEvent('on' + name, observer); 935 } 1759 1760 if (name == 'keypress' && 1761 (navigator.appVersion.match(/Konqueror|Safari|KHTML/) 1762 || element.detachEvent)) 1763 name = 'keydown'; 1764 1765 if (element.removeEventListener) { 1766 element.removeEventListener(name, observer, useCapture); 1767 } else if (element.detachEvent) { 1768 element.detachEvent('on' + name, observer); 936 1769 } 937 1770 } 938 1771 }); 939 1772 1773 /* prevent memory leaks in IE */ 1774 if (navigator.appVersion.match(/\bMSIE\b/)) 1775 Event.observe(window, 'unload', Event.unloadCache, false); 940 1776 var Position = { 941 942 1777 // set to true if needed, warning: firefox performance problems 943 1778 // NOT neeeded for page scrolling, only if draggable contained in 944 1779 // scrollable elements 945 includeScrollOffsets: false, 1780 includeScrollOffsets: false, 946 1781 947 1782 // must be called before calling withinIncludingScrolloffset, every time the 948 1783 // page is scrolled 949 1784 prepare: function() { 950 this.deltaX = window.pageXOffset 951 || document.documentElement.scrollLeft 952 || document.body.scrollLeft 1785 this.deltaX = window.pageXOffset 1786 || document.documentElement.scrollLeft 1787 || document.body.scrollLeft 953 1788 || 0; 954 this.deltaY = window.pageYOffset 955 || document.documentElement.scrollTop 956 || document.body.scrollTop 1789 this.deltaY = window.pageYOffset 1790 || document.documentElement.scrollTop 1791 || document.body.scrollTop 957 1792 || 0; 958 1793 }, … … 962 1797 do { 963 1798 valueT += element.scrollTop || 0; 964 valueL += element.scrollLeft || 0; 1799 valueL += element.scrollLeft || 0; 965 1800 element = element.parentNode; 966 1801 } while (element); … … 978 1813 }, 979 1814 1815 positionedOffset: function(element) { 1816 var valueT = 0, valueL = 0; 1817 do { 1818 valueT += element.offsetTop || 0; 1819 valueL += element.offsetLeft || 0; 1820 element = element.offsetParent; 1821 if (element) { 1822 p = Element.getStyle(element, 'position'); 1823 if (p == 'relative' || p == 'absolute') break; 1824 } 1825 } while (element); 1826 return [valueL, valueT]; 1827 }, 1828 1829 offsetParent: function(element) { 1830 if (element.offsetParent) return element.offsetParent; 1831 if (element == document.body) return element; 1832 1833 while ((element = element.parentNode) && element != document.body) 1834 if (Element.getStyle(element, 'position') != 'static') 1835 return element; 1836 1837 return document.body; 1838 }, 1839 980 1840 // caches x/y coordinate pair to use with overlap 981 1841 within: function(element, x, y) { … … 988 1848 return (y >= this.offset[1] && 989 1849 y < this.offset[1] + element.offsetHeight && 990 x >= this.offset[0] && 1850 x >= this.offset[0] && 991 1851 x < this.offset[0] + element.offsetWidth); 992 1852 }, … … 1001 1861 return (this.ycomp >= this.offset[1] && 1002 1862 this.ycomp < this.offset[1] + element.offsetHeight && 1003 this.xcomp >= this.offset[0] && 1863 this.xcomp >= this.offset[0] && 1004 1864 this.xcomp < this.offset[0] + element.offsetWidth); 1005 1865 }, 1006 1866 1007 1867 // within must be called directly before 1008 overlap: function(mode, element) { 1009 if (!mode) return 0; 1010 if (mode == 'vertical') 1011 return ((this.offset[1] + element.offsetHeight) - this.ycomp) / 1868 overlap: function(mode, element) { 1869 if (!mode) return 0; 1870 if (mode == 'vertical') 1871 return ((this.offset[1] + element.offsetHeight) - this.ycomp) / 1012 1872 element.offsetHeight; 1013 1873 if (mode == 'horizontal') 1014 return ((this.offset[0] + element.offsetWidth) - this.xcomp) / 1874 return ((this.offset[0] + element.offsetWidth) - this.xcomp) / 1015 1875 element.offsetWidth; 1016 1876 }, … … 1025 1885 target.style.width = source.offsetWidth + 'px'; 1026 1886 target.style.height = source.offsetHeight + 'px'; 1027 } 1028 } 1887 }, 1888 1889 page: function(forElement) { 1890 var valueT = 0, valueL = 0; 1891 1892 var element = forElement; 1893 do { 1894 valueT += element.offsetTop || 0; 1895 valueL += element.offsetLeft || 0; 1896 1897 // Safari fix 1898 if (element.offsetParent==document.body) 1899 if (Element.getStyle(element,'position')=='absolute') break; 1900 1901 } while (element = element.offsetParent); 1902 1903 element = forElement; 1904 do { 1905 valueT -= element.scrollTop || 0; 1906 valueL -= element.scrollLeft || 0; 1907 } while (element = element.parentNode); 1908 1909 return [valueL, valueT]; 1910 }, 1911 1912 clone: function(source, target) { 1913 var options = Object.extend({ 1914 setLeft: true, 1915 setTop: true, 1916 setWidth: true, 1917 setHeight: true, 1918 offsetTop: 0, 1919 offsetLeft: 0 1920 }, arguments[2] || {}) 1921 1922 // find page position of source 1923 source = $(source); 1924 var p = Position.page(source); 1925 1926 // find coordinate system to use 1927 target = $(target); 1928 var delta = [0, 0]; 1929 var parent = null; 1930 // delta [0,0] will do fine with position: fixed elements, 1931 // position:absolute needs offsetParent deltas 1932 if (Element.getStyle(target,'position') == 'absolute') { 1933 parent = Position.offsetParent(target); 1934 delta = Position.page(parent); 1935 } 1936 1937 // correct by body offsets (fixes Safari) 1938 if (parent == document.body) { 1939 delta[0] -= document.body.offsetLeft; 1940 delta[1] -= document.body.offsetTop; 1941 } 1942 1943 // set position 1944 if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; 1945 if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; 1946 if(options.setWidth) target.style.width = source.offsetWidth + 'px'; 1947 if(options.setHeight) target.style.height = source.offsetHeight + 'px'; 1948 }, 1949 1950 absolutize: function(element) { 1951 element = $(element); 1952 if (element.style.position == 'absolute') return; 1953 Position.prepare(); 1954 1955 var offsets = Position.positionedOffset(element); 1956 var top = offsets[1]; 1957 var left = offsets[0]; 1958 var width = element.clientWidth; 1959 var height = element.clientHeight; 1960 1961 element._originalLeft = left - parseFloat(element.style.left || 0); 1962 element._originalTop = top - parseFloat(element.style.top || 0); 1963 element._originalWidth = element.style.width; 1964 element._originalHeight = element.style.height; 1965 1966 element.style.position = 'absolute'; 1967 element.style.top = top + 'px';; 1968 element.style.left = left + 'px';; 1969 element.style.width = width + 'px';; 1970 element.style.height = height + 'px';; 1971 }, 1972 1973 relativize: function(element) { 1974 element = $(element); 1975 if (element.style.position == 'relative') return; 1976 Position.prepare(); 1977 1978 element.style.position = 'relative'; 1979 var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); 1980 var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); 1981 1982 element.style.top = top + 'px'; 1983 element.style.left = left + 'px'; 1984 element.style.height = element._originalHeight; 1985 element.style.width = element._originalWidth; 1986 } 1987 } 1988 1989 // Safari returns margins on body which is incorrect if the child is absolutely 1990 // positioned. For performance reasons, redefine Position.cumulativeOffset for 1991 // KHTML/WebKit only. 1992 if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { 1993 Position.cumulativeOffset = function(element) { 1994 var valueT = 0, valueL = 0; 1995 do { 1996 valueT += element.offsetTop || 0; 1997 valueL += element.offsetLeft || 0; 1998 if (element.offsetParent == document.body) 1999 if (Element.getStyle(element, 'position') == 'absolute') break; 2000 2001 element = element.offsetParent; 2002 } while (element); 2003 2004 return [valueL, valueT]; 2005 } 2006 }
